From 268c4c24ab31efe178bb05e1a45829440058e87b Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Costa Date: Tue, 21 Oct 2025 06:19:17 +0000 Subject: [PATCH] feat(libstore): add S3 multipart upload configuration settings Add three configuration settings to S3BinaryCacheStoreConfig to control multipart upload behavior: - multipart-upload (bool, default false): Enable/disable multipart uploads - multipart-chunk-size (uint64_t, default 5 MiB): Size of each upload part - multipart-threshold (uint64_t, default 100 MiB): Minimum file size for multipart The feature is disabled by default. --- .../nix/store/s3-binary-cache-store.hh | 31 +++++++++++++++++++ src/libstore/s3-binary-cache-store.cc | 27 ++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/libstore/include/nix/store/s3-binary-cache-store.hh b/src/libstore/include/nix/store/s3-binary-cache-store.hh index 288ca41a059..17f464e967f 100644 --- a/src/libstore/include/nix/store/s3-binary-cache-store.hh +++ b/src/libstore/include/nix/store/s3-binary-cache-store.hh @@ -61,6 +61,37 @@ struct S3BinaryCacheStoreConfig : HttpBinaryCacheStoreConfig > addressing instead of virtual host based addressing. )"}; + const Setting multipartUpload{ + this, + false, + "multipart-upload", + R"( + Whether to use multipart uploads for large files. When enabled, + files exceeding the multipart threshold will be uploaded in + multiple parts, which is required for files larger than 5 GiB and + can improve performance and reliability for large uploads. + )"}; + + const Setting multipartChunkSize{ + this, + 5 * 1024 * 1024, + "multipart-chunk-size", + R"( + The size (in bytes) of each part in multipart uploads. Must be + at least 5 MiB (AWS S3 requirement). Larger chunk sizes reduce the + number of requests but use more memory. Default is 5 MiB. + )"}; + + const Setting multipartThreshold{ + this, + 100 * 1024 * 1024, + "multipart-threshold", + R"( + The minimum file size (in bytes) for using multipart uploads. + Files smaller than this threshold will use regular PUT requests. + Default is 100 MiB. Only takes effect when multipart-upload is enabled. + )"}; + /** * Set of settings that are part of the S3 URI itself. * These are needed for region specification and other S3-specific settings. diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc index 0b37ac5d7a6..8a09f86c38f 100644 --- a/src/libstore/s3-binary-cache-store.cc +++ b/src/libstore/s3-binary-cache-store.cc @@ -1,6 +1,8 @@ #include "nix/store/s3-binary-cache-store.hh" #include "nix/store/http-binary-cache-store.hh" #include "nix/store/store-registration.hh" +#include "nix/util/error.hh" +#include "nix/util/logging.hh" #include #include @@ -27,6 +29,31 @@ S3BinaryCacheStoreConfig::S3BinaryCacheStoreConfig( cacheUri.query[key] = value; } } + + const uint64_t AWS_MIN_PART_SIZE = 5 * 1024 * 1024; + const uint64_t AWS_MAX_PART_SIZE = 5ULL * 1024 * 1024 * 1024; + + if (multipartChunkSize < AWS_MIN_PART_SIZE) { + throw UsageError( + "multipart-chunk-size must be at least 5 MiB (5242880 bytes), got %d bytes (%d MiB)", + multipartChunkSize.get(), + multipartChunkSize.get() / (1024 * 1024)); + } + + if (multipartChunkSize > AWS_MAX_PART_SIZE) { + throw UsageError( + "multipart-chunk-size must be at most 5 GiB (5368709120 bytes), got %d bytes (%d GiB)", + multipartChunkSize.get(), + multipartChunkSize.get() / (1024 * 1024 * 1024)); + } + + if (multipartUpload && multipartThreshold < multipartChunkSize) { + warn( + "multipart-threshold (%d MiB) is less than multipart-chunk-size (%d MiB), " + "which may result in single-part multipart uploads", + multipartThreshold.get() / (1024 * 1024), + multipartChunkSize.get() / (1024 * 1024)); + } } std::string S3BinaryCacheStoreConfig::getHumanReadableURI() const