Skip to content

Commit 69c0bb5

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 6420879 commit 69c0bb5

File tree

8 files changed

+353
-4
lines changed

8 files changed

+353
-4
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 cannot contain dots (periods)
26+
- Transfer Acceleration must be enabled on the S3 bucket
27+
- Additional AWS charges apply for Transfer Acceleration
28+
29+
This feature only applies to AWS S3 buckets and has no effect when using custom
30+
endpoints for S3-compatible services.

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

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

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

src/libstore-tests/s3-url.cc

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

91121
// Parameterized test for invalid S3 URLs
@@ -119,6 +149,62 @@ INSTANTIATE_TEST_SUITE_P(
119149
InvalidS3URLTestCase{"s3://bucket", "error: URI has a missing or invalid key", "missing_key"}),
120150
[](const ::testing::TestParamInfo<InvalidS3URLTestCase> & info) { return info.param.description; });
121151

152+
#if NIX_WITH_AWS_AUTH
153+
// =============================================================================
154+
// Transfer Acceleration Bucket Name Validation Tests
155+
// =============================================================================
156+
157+
struct InvalidTransferAccelerationBucketTestCase
158+
{
159+
std::string url;
160+
std::string expectedErrorSubstring;
161+
std::string description;
162+
};
163+
164+
class InvalidTransferAccelerationBucketTest
165+
: public ::testing::WithParamInterface<InvalidTransferAccelerationBucketTestCase>,
166+
public ::testing::Test
167+
{};
168+
169+
TEST_P(InvalidTransferAccelerationBucketTest, rejectsInvalidBucketNames)
170+
{
171+
const auto & testCase = GetParam();
172+
auto parsed = ParsedS3URL::parse(parseURL(testCase.url));
173+
174+
ASSERT_THAT(
175+
[&parsed]() { parsed.toHttpsUrl(); },
176+
::testing::ThrowsMessage<Error>(testing::HasSubstrIgnoreANSIMatcher(testCase.expectedErrorSubstring)));
177+
}
178+
179+
INSTANTIATE_TEST_SUITE_P(
180+
InvalidBucketNames,
181+
InvalidTransferAccelerationBucketTest,
182+
::testing::Values(
183+
InvalidTransferAccelerationBucketTestCase{
184+
"s3://bucket.with.dots/key?use-transfer-acceleration=true",
185+
"is not compatible with S3 Transfer Acceleration",
186+
"bucket_with_dots",
187+
},
188+
InvalidTransferAccelerationBucketTestCase{
189+
"s3://my.bucket.name/key?use-transfer-acceleration=true",
190+
"is not compatible with S3 Transfer Acceleration",
191+
"bucket_with_multiple_dots",
192+
},
193+
InvalidTransferAccelerationBucketTestCase{
194+
"s3://bucket.name/key?use-transfer-acceleration=true",
195+
"is not compatible with S3 Transfer Acceleration",
196+
"bucket_with_single_dot",
197+
},
198+
InvalidTransferAccelerationBucketTestCase{
199+
"s3://bucket/key?use-transfer-acceleration=true&endpoint=minio.local",
200+
"cannot be used with custom endpoints",
201+
"acceleration_with_custom_endpoint",
202+
}),
203+
[](const ::testing::TestParamInfo<InvalidTransferAccelerationBucketTestCase> & info) {
204+
return info.param.description;
205+
});
206+
#endif
207+
122208
// =============================================================================
123209
// S3 URL to HTTPS Conversion Tests
124210
// =============================================================================
@@ -222,7 +308,53 @@ INSTANTIATE_TEST_SUITE_P(
222308
},
223309
"https://s3.ap-southeast-2.amazonaws.com/bucket/path/to/file.txt",
224310
"complex_path_and_region",
225-
}),
311+
}
312+
#if NIX_WITH_AWS_AUTH
313+
, S3ToHttpsConversionTestCase{
314+
ParsedS3URL{
315+
.bucket = "my-cache",
316+
.key = {"nix", "store", "abc123.nar.xz"},
317+
.use_transfer_acceleration = true,
318+
},
319+
ParsedURL{
320+
.scheme = "https",
321+
.authority = ParsedURL::Authority{.host = "my-cache.s3-accelerate.amazonaws.com"},
322+
.path = {"", "nix", "store", "abc123.nar.xz"},
323+
},
324+
"https://my-cache.s3-accelerate.amazonaws.com/nix/store/abc123.nar.xz",
325+
"transfer_acceleration_enabled",
326+
},
327+
S3ToHttpsConversionTestCase{
328+
ParsedS3URL{
329+
.bucket = "tokyo-cache",
330+
.key = {"key.txt"},
331+
.region = "ap-northeast-1",
332+
.use_transfer_acceleration = true,
333+
},
334+
ParsedURL{
335+
.scheme = "https",
336+
.authority = ParsedURL::Authority{.host = "tokyo-cache.s3-accelerate.amazonaws.com"},
337+
.path = {"", "key.txt"},
338+
},
339+
"https://tokyo-cache.s3-accelerate.amazonaws.com/key.txt",
340+
"transfer_acceleration_with_region",
341+
},
342+
S3ToHttpsConversionTestCase{
343+
ParsedS3URL{
344+
.bucket = "my-bucket",
345+
.key = {"file"},
346+
.use_transfer_acceleration = false,
347+
},
348+
ParsedURL{
349+
.scheme = "https",
350+
.authority = ParsedURL::Authority{.host = "s3.us-east-1.amazonaws.com"},
351+
.path = {"", "my-bucket", "file"},
352+
},
353+
"https://s3.us-east-1.amazonaws.com/my-bucket/file",
354+
"transfer_acceleration_explicitly_disabled",
355+
}
356+
#endif
357+
),
226358
[](const ::testing::TestParamInfo<S3ToHttpsConversionTestCase> & info) { return info.param.description; });
227359

228360
} // namespace nix

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

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

66+
#if NIX_WITH_AWS_AUTH
67+
const Setting<bool> use_transfer_acceleration{
68+
this,
69+
false,
70+
"use-transfer-acceleration",
71+
R"(
72+
Whether to use AWS S3 Transfer Acceleration for faster uploads and downloads
73+
by routing transfers through CloudFront edge locations. This is particularly
74+
useful when accessing S3 buckets from geographically distant locations.
75+
76+
When enabled, requests are routed to `bucket-name.s3-accelerate.amazonaws.com`
77+
instead of the standard regional endpoint.
78+
79+
> **Note**
80+
>
81+
> Transfer Acceleration does not support bucket names with dots (periods).
82+
> Bucket names like `my.bucket.name` will not work with this setting.
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+
#endif
91+
6692
static const std::string name()
6793
{
6894
return "S3 Binary Cache Store";

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ struct ParsedS3URL
2626
std::optional<std::string> profile;
2727
std::optional<std::string> region;
2828
std::optional<std::string> scheme;
29+
#if NIX_WITH_AWS_AUTH
30+
std::optional<bool> use_transfer_acceleration;
31+
#endif
2932
/**
3033
* The endpoint can be either missing, be an absolute URI (with a scheme like `http:`)
3134
* or an authority (so an IP address or a registered name).

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ 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"
27+
#if NIX_WITH_AWS_AUTH
28+
, "use-transfer-acceleration"
29+
#endif
30+
};
2631
for (const auto & [key, value] : params) {
2732
if (s3Params.contains(key)) {
2833
cacheUri.query[key] = value;

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ 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+
- Bucket names cannot contain dots (`.`) - e.g., `my.bucket.name` will not work
100+
- Transfer Acceleration must be enabled on your S3 bucket (see [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html))
101+
- Additional charges apply for Transfer Acceleration (see AWS pricing)
102+
103+
> **Note**
104+
>
105+
> Transfer Acceleration only works with AWS S3 buckets. It has no effect when using custom endpoints for S3-compatible services like MinIO.
106+
86107
### Examples
87108

88109
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 +122,11 @@ With bucket policies and authentication set up as described above, uploading wor
101122
's3://example-nix-cache?profile=cache-upload&scheme=https&endpoint=minio.example.com'
102123
```
103124

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

0 commit comments

Comments
 (0)