Skip to content

Commit 7f7eb15

Browse files
committed
feat(libstore): support S3 Transfer Acceleration
Implements AWS S3 Transfer Acceleration for binary cache stores to improve upload and download performance when accessing S3 buckets from geographically distant locations. Transfers are routed through CloudFront edge locations for better performance. Usage: nix copy nixpkgs.hello \ --to 's3://my-cache?region=ap-northeast-1&use-transfer-acceleration=true' Requirements: - DNS-compliant bucket names (3-63 chars, lowercase, numbers, hyphens) - No dots in bucket names - Transfer Acceleration enabled on the S3 bucket - Only applies to AWS S3 (ignored for custom endpoints)
1 parent f84b336 commit 7f7eb15

File tree

8 files changed

+368
-2
lines changed

8 files changed

+368
-2
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
synopsis: "S3 Transfer Acceleration support for faster remote cache access"
3+
issues: [12973]
4+
prs: [14277]
5+
---
6+
7+
S3 binary cache stores now support AWS S3 Transfer Acceleration for faster
8+
uploads and downloads when accessing buckets from geographically distant
9+
locations.
10+
11+
Transfer Acceleration routes S3 requests through CloudFront edge locations,
12+
which can significantly improve performance for users far from the bucket's
13+
region. For example, US-based users accessing Tokyo-region buckets can see
14+
substantial speed improvements.
15+
16+
To enable transfer acceleration, add `use-transfer-acceleration=true` to your
17+
S3 URL:
18+
19+
```console
20+
$ nix copy nixpkgs.hello \
21+
--to 's3://my-cache?region=ap-northeast-1&use-transfer-acceleration=true'
22+
```
23+
24+
Requirements:
25+
- Bucket names must be DNS-compliant (3-63 characters, lowercase letters,
26+
numbers, and hyphens only)
27+
- Bucket names with dots are not supported
28+
- Transfer Acceleration must be enabled on the S3 bucket
29+
- Additional AWS charges apply for Transfer Acceleration
30+
31+
This feature only applies to AWS S3 buckets and has no effect when using custom
32+
endpoints for S3-compatible services.

src/libstore-tests/s3-binary-cache-store.cc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,68 @@ TEST(S3BinaryCacheStore, parameterFiltering)
122122
EXPECT_EQ(ref.params["priority"], "10");
123123
}
124124

125+
/**
126+
* Test that transfer acceleration parameter is properly preserved
127+
*/
128+
TEST(S3BinaryCacheStore, transferAccelerationParameter)
129+
{
130+
StringMap params;
131+
params["use-transfer-acceleration"] = "true";
132+
133+
S3BinaryCacheStoreConfig config("s3", "my-cache", params);
134+
135+
// Transfer acceleration param should be in cacheUri.query
136+
EXPECT_EQ(
137+
config.cacheUri,
138+
(ParsedURL{
139+
.scheme = "s3",
140+
.authority = ParsedURL::Authority{.host = "my-cache"},
141+
.query = (StringMap) {{"use-transfer-acceleration", "true"}},
142+
}));
143+
144+
// And the config setting should be set
145+
EXPECT_EQ(config.use_transfer_acceleration.get(), true);
146+
}
147+
148+
/**
149+
* Test that transfer acceleration works with other S3 parameters
150+
*/
151+
TEST(S3BinaryCacheStore, transferAccelerationWithOtherParams)
152+
{
153+
StringMap params;
154+
params["region"] = "ap-northeast-1";
155+
params["use-transfer-acceleration"] = "true";
156+
params["profile"] = "production";
157+
158+
S3BinaryCacheStoreConfig config("s3", "tokyo-cache", params);
159+
160+
EXPECT_EQ(
161+
config.cacheUri,
162+
(ParsedURL{
163+
.scheme = "s3",
164+
.authority = ParsedURL::Authority{.host = "tokyo-cache"},
165+
.query =
166+
(StringMap) {
167+
{"region", "ap-northeast-1"},
168+
{"use-transfer-acceleration", "true"},
169+
{"profile", "production"},
170+
},
171+
}));
172+
173+
EXPECT_EQ(config.region.get(), "ap-northeast-1");
174+
EXPECT_EQ(config.use_transfer_acceleration.get(), true);
175+
EXPECT_EQ(config.profile.get(), "production");
176+
}
177+
178+
/**
179+
* Test default value for transfer acceleration
180+
*/
181+
TEST(S3BinaryCacheStore, transferAccelerationDefaultValue)
182+
{
183+
S3BinaryCacheStoreConfig config("s3", "test-bucket", {});
184+
185+
// Default should be false
186+
EXPECT_EQ(config.use_transfer_acceleration.get(), false);
187+
}
188+
125189
} // namespace nix

src/libstore-tests/s3-url.cc

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,33 @@ INSTANTIATE_TEST_SUITE_P(
8585
},
8686
},
8787
"with_absolute_endpoint_uri",
88+
},
89+
ParsedS3URLTestCase{
90+
"s3://my-bucket/key?use-transfer-acceleration=true",
91+
{
92+
.bucket = "my-bucket",
93+
.key = {"key"},
94+
.use_transfer_acceleration = true,
95+
},
96+
"with_transfer_acceleration",
97+
},
98+
ParsedS3URLTestCase{
99+
"s3://my-bucket/key?use-transfer-acceleration=false",
100+
{
101+
.bucket = "my-bucket",
102+
.key = {"key"},
103+
.use_transfer_acceleration = false,
104+
},
105+
"with_transfer_acceleration_false",
106+
},
107+
ParsedS3URLTestCase{
108+
"s3://my-bucket/key?use-transfer-acceleration=1",
109+
{
110+
.bucket = "my-bucket",
111+
.key = {"key"},
112+
.use_transfer_acceleration = true,
113+
},
114+
"with_transfer_acceleration_numeric",
88115
}),
89116
[](const ::testing::TestParamInfo<ParsedS3URLTestCase> & info) { return info.param.description; });
90117

@@ -119,6 +146,75 @@ INSTANTIATE_TEST_SUITE_P(
119146
InvalidS3URLTestCase{"s3://bucket", "error: URI has a missing or invalid key", "missing_key"}),
120147
[](const ::testing::TestParamInfo<InvalidS3URLTestCase> & info) { return info.param.description; });
121148

149+
// =============================================================================
150+
// Transfer Acceleration Bucket Name Validation Tests
151+
// =============================================================================
152+
153+
struct InvalidTransferAccelerationBucketTestCase
154+
{
155+
std::string url;
156+
std::string expectedErrorSubstring;
157+
std::string description;
158+
};
159+
160+
class InvalidTransferAccelerationBucketTest
161+
: public ::testing::WithParamInterface<InvalidTransferAccelerationBucketTestCase>,
162+
public ::testing::Test
163+
{};
164+
165+
TEST_P(InvalidTransferAccelerationBucketTest, rejectsInvalidBucketNames)
166+
{
167+
const auto & testCase = GetParam();
168+
auto parsed = ParsedS3URL::parse(parseURL(testCase.url));
169+
170+
ASSERT_THAT(
171+
[&parsed]() { parsed.toHttpsUrl(); },
172+
::testing::ThrowsMessage<Error>(testing::HasSubstrIgnoreANSIMatcher(testCase.expectedErrorSubstring)));
173+
}
174+
175+
INSTANTIATE_TEST_SUITE_P(
176+
InvalidBucketNames,
177+
InvalidTransferAccelerationBucketTest,
178+
::testing::Values(
179+
InvalidTransferAccelerationBucketTestCase{
180+
"s3://bucket.with.dots/key?use-transfer-acceleration=true",
181+
"is not compatible with S3 Transfer Acceleration",
182+
"bucket_with_dots",
183+
},
184+
InvalidTransferAccelerationBucketTestCase{
185+
"s3://BucketWithCaps/key?use-transfer-acceleration=true",
186+
"is not compatible with S3 Transfer Acceleration",
187+
"bucket_with_uppercase",
188+
},
189+
InvalidTransferAccelerationBucketTestCase{
190+
"s3://ab/key?use-transfer-acceleration=true",
191+
"is not compatible with S3 Transfer Acceleration",
192+
"bucket_too_short",
193+
},
194+
InvalidTransferAccelerationBucketTestCase{
195+
"s3://this-is-a-very-long-bucket-name-that-exceeds-the-maximum-length-limit/key?use-transfer-acceleration=true",
196+
"is not compatible with S3 Transfer Acceleration",
197+
"bucket_too_long",
198+
},
199+
InvalidTransferAccelerationBucketTestCase{
200+
"s3://bucket_with_underscore/key?use-transfer-acceleration=true",
201+
"is not compatible with S3 Transfer Acceleration",
202+
"bucket_with_underscore",
203+
},
204+
InvalidTransferAccelerationBucketTestCase{
205+
"s3://-starts-with-hyphen/key?use-transfer-acceleration=true",
206+
"is not compatible with S3 Transfer Acceleration",
207+
"bucket_starts_with_hyphen",
208+
},
209+
InvalidTransferAccelerationBucketTestCase{
210+
"s3://ends-with-hyphen-/key?use-transfer-acceleration=true",
211+
"is not compatible with S3 Transfer Acceleration",
212+
"bucket_ends_with_hyphen",
213+
}),
214+
[](const ::testing::TestParamInfo<InvalidTransferAccelerationBucketTestCase> & info) {
215+
return info.param.description;
216+
});
217+
122218
// =============================================================================
123219
// S3 URL to HTTPS Conversion Tests
124220
// =============================================================================
@@ -222,6 +318,64 @@ INSTANTIATE_TEST_SUITE_P(
222318
},
223319
"https://s3.ap-southeast-2.amazonaws.com/bucket/path/to/file.txt",
224320
"complex_path_and_region",
321+
},
322+
S3ToHttpsConversionTestCase{
323+
ParsedS3URL{
324+
.bucket = "my-cache",
325+
.key = {"nix", "store", "abc123.nar.xz"},
326+
.use_transfer_acceleration = true,
327+
},
328+
ParsedURL{
329+
.scheme = "https",
330+
.authority = ParsedURL::Authority{.host = "my-cache.s3-accelerate.amazonaws.com"},
331+
.path = {"", "nix", "store", "abc123.nar.xz"},
332+
},
333+
"https://my-cache.s3-accelerate.amazonaws.com/nix/store/abc123.nar.xz",
334+
"transfer_acceleration_enabled",
335+
},
336+
S3ToHttpsConversionTestCase{
337+
ParsedS3URL{
338+
.bucket = "tokyo-cache",
339+
.key = {"key.txt"},
340+
.region = "ap-northeast-1",
341+
.use_transfer_acceleration = true,
342+
},
343+
ParsedURL{
344+
.scheme = "https",
345+
.authority = ParsedURL::Authority{.host = "tokyo-cache.s3-accelerate.amazonaws.com"},
346+
.path = {"", "key.txt"},
347+
},
348+
"https://tokyo-cache.s3-accelerate.amazonaws.com/key.txt",
349+
"transfer_acceleration_with_region",
350+
},
351+
S3ToHttpsConversionTestCase{
352+
ParsedS3URL{
353+
.bucket = "my-bucket",
354+
.key = {"file"},
355+
.use_transfer_acceleration = false,
356+
},
357+
ParsedURL{
358+
.scheme = "https",
359+
.authority = ParsedURL::Authority{.host = "s3.us-east-1.amazonaws.com"},
360+
.path = {"", "my-bucket", "file"},
361+
},
362+
"https://s3.us-east-1.amazonaws.com/my-bucket/file",
363+
"transfer_acceleration_explicitly_disabled",
364+
},
365+
S3ToHttpsConversionTestCase{
366+
ParsedS3URL{
367+
.bucket = "bucket",
368+
.key = {"key"},
369+
.use_transfer_acceleration = true,
370+
.endpoint = ParsedURL::Authority{.host = "minio.local"},
371+
},
372+
ParsedURL{
373+
.scheme = "https",
374+
.authority = ParsedURL::Authority{.host = "minio.local"},
375+
.path = {"", "bucket", "key"},
376+
},
377+
"https://minio.local/bucket/key",
378+
"transfer_acceleration_with_custom_endpoint_ignored",
225379
}),
226380
[](const ::testing::TestParamInfo<S3ToHttpsConversionTestCase> & info) { return info.param.description; });
227381

src/libstore/include/nix/store/s3-binary-cache-store.hh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,31 @@ public:
6363
> addressing instead of virtual host based addressing.
6464
)"};
6565

66+
const Setting<bool> use_transfer_acceleration{
67+
this,
68+
false,
69+
"use-transfer-acceleration",
70+
R"(
71+
Whether to use AWS S3 Transfer Acceleration for faster uploads and downloads
72+
by routing transfers through CloudFront edge locations. This is particularly
73+
useful when accessing S3 buckets from geographically distant locations.
74+
75+
When enabled, requests are routed to `bucket-name.s3-accelerate.amazonaws.com`
76+
instead of the standard regional endpoint.
77+
78+
> **Note**
79+
>
80+
> Transfer Acceleration requires DNS-compliant bucket names (3-63 characters,
81+
> lowercase letters, numbers, and hyphens only). Bucket names with dots are
82+
> not supported.
83+
>
84+
> This setting only applies to AWS S3 buckets. It has no effect when using
85+
> custom endpoints for S3-compatible services.
86+
>
87+
> Additional charges apply for Transfer Acceleration. See AWS documentation
88+
> for pricing details.
89+
)"};
90+
6691
static const std::string name()
6792
{
6893
return "S3 Binary Cache Store";

src/libstore/include/nix/store/s3-url.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct ParsedS3URL
2626
std::optional<std::string> profile;
2727
std::optional<std::string> region;
2828
std::optional<std::string> scheme;
29+
std::optional<bool> use_transfer_acceleration;
2930
/**
3031
* The endpoint can be either missing, be an absolute URI (with a scheme like `http:`)
3132
* or an authority (so an IP address or a registered name).

src/libstore/s3-binary-cache-store.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ S3BinaryCacheStoreConfig::S3BinaryCacheStoreConfig(
2222
assert(cacheUri.query.empty());
2323

2424
// Only copy S3-specific parameters to the URL query
25-
static const std::set<std::string> s3Params = {"region", "endpoint", "profile", "scheme"};
25+
static const std::set<std::string> s3Params = {
26+
"region", "endpoint", "profile", "scheme", "use-transfer-acceleration"};
2627
for (const auto & [key, value] : params) {
2728
if (s3Params.contains(key)) {
2829
cacheUri.query[key] = value;

src/libstore/s3-binary-cache-store.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,28 @@ Your account will need an IAM policy to support uploading to the bucket:
8383
}
8484
```
8585

86+
### S3 Transfer Acceleration
87+
88+
For faster uploads and downloads when accessing S3 buckets from geographically distant locations, you can enable AWS S3 Transfer Acceleration. This routes transfers through CloudFront edge locations for improved performance.
89+
90+
To enable transfer acceleration, add the `use-transfer-acceleration=true` parameter to your S3 URL:
91+
92+
```console
93+
$ nix copy nixpkgs.hello \
94+
--to 's3://example-nix-cache?use-transfer-acceleration=true&region=ap-northeast-1'
95+
```
96+
97+
#### Requirements for Transfer Acceleration
98+
99+
- Your bucket name must be DNS-compliant (3-63 characters, lowercase letters, numbers, and hyphens only)
100+
- Bucket names containing dots (`.`) are not supported
101+
- Transfer Acceleration must be enabled on your S3 bucket (see [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html))
102+
- Additional charges apply for Transfer Acceleration (see AWS pricing)
103+
104+
> **Note**
105+
>
106+
> Transfer Acceleration only works with AWS S3 buckets. It has no effect when using custom endpoints for S3-compatible services like MinIO.
107+
86108
### Examples
87109

88110
With bucket policies and authentication set up as described above, uploading works via [`nix copy`](@docroot@/command-ref/new-cli/nix3-copy.md) (experimental).
@@ -101,4 +123,11 @@ With bucket policies and authentication set up as described above, uploading wor
101123
's3://example-nix-cache?profile=cache-upload&scheme=https&endpoint=minio.example.com'
102124
```
103125

126+
- To use S3 Transfer Acceleration for faster transfers from distant locations:
127+
128+
```console
129+
$ nix copy nixpkgs.hello \
130+
--to 's3://my-cache?profile=cache-upload&region=ap-northeast-1&use-transfer-acceleration=true'
131+
```
132+
104133
)"

0 commit comments

Comments
 (0)