Skip to content

Commit fce1031

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 34ac179 commit fce1031

File tree

7 files changed

+347
-4
lines changed

7 files changed

+347
-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: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,38 @@ INSTANTIATE_TEST_SUITE_P(
104104
},
105105
},
106106
"with_absolute_endpoint_uri",
107-
}),
107+
}
108+
#if NIX_WITH_AWS_AUTH
109+
,
110+
ParsedS3URLTestCase{
111+
"s3://my-bucket/key?use-transfer-acceleration=true",
112+
{
113+
.bucket = "my-bucket",
114+
.key = {"key"},
115+
.use_transfer_acceleration = true,
116+
},
117+
"with_transfer_acceleration",
118+
},
119+
ParsedS3URLTestCase{
120+
"s3://my-bucket/key?use-transfer-acceleration=false",
121+
{
122+
.bucket = "my-bucket",
123+
.key = {"key"},
124+
.use_transfer_acceleration = false,
125+
},
126+
"with_transfer_acceleration_false",
127+
},
128+
ParsedS3URLTestCase{
129+
"s3://my-bucket/key?use-transfer-acceleration=1",
130+
{
131+
.bucket = "my-bucket",
132+
.key = {"key"},
133+
.use_transfer_acceleration = true,
134+
},
135+
"with_transfer_acceleration_numeric",
136+
}
137+
#endif
138+
),
108139
[](const ::testing::TestParamInfo<ParsedS3URLTestCase> & info) { return info.param.description; });
109140

110141
// Parameterized test for invalid S3 URLs
@@ -138,6 +169,62 @@ INSTANTIATE_TEST_SUITE_P(
138169
InvalidS3URLTestCase{"s3://bucket", "error: URI has a missing or invalid key", "missing_key"}),
139170
[](const ::testing::TestParamInfo<InvalidS3URLTestCase> & info) { return info.param.description; });
140171

172+
#if NIX_WITH_AWS_AUTH
173+
// =============================================================================
174+
// Transfer Acceleration Bucket Name Validation Tests
175+
// =============================================================================
176+
177+
struct InvalidTransferAccelerationBucketTestCase
178+
{
179+
std::string url;
180+
std::string expectedErrorSubstring;
181+
std::string description;
182+
};
183+
184+
class InvalidTransferAccelerationBucketTest
185+
: public ::testing::WithParamInterface<InvalidTransferAccelerationBucketTestCase>,
186+
public ::testing::Test
187+
{};
188+
189+
TEST_P(InvalidTransferAccelerationBucketTest, rejectsInvalidBucketNames)
190+
{
191+
const auto & testCase = GetParam();
192+
auto parsed = ParsedS3URL::parse(parseURL(testCase.url));
193+
194+
ASSERT_THAT(
195+
[&parsed]() { parsed.toHttpsUrl(); },
196+
::testing::ThrowsMessage<Error>(testing::HasSubstrIgnoreANSIMatcher(testCase.expectedErrorSubstring)));
197+
}
198+
199+
INSTANTIATE_TEST_SUITE_P(
200+
InvalidBucketNames,
201+
InvalidTransferAccelerationBucketTest,
202+
::testing::Values(
203+
InvalidTransferAccelerationBucketTestCase{
204+
"s3://bucket.with.dots/key?use-transfer-acceleration=true",
205+
"is not compatible with S3 Transfer Acceleration",
206+
"bucket_with_dots",
207+
},
208+
InvalidTransferAccelerationBucketTestCase{
209+
"s3://my.bucket.name/key?use-transfer-acceleration=true",
210+
"is not compatible with S3 Transfer Acceleration",
211+
"bucket_with_multiple_dots",
212+
},
213+
InvalidTransferAccelerationBucketTestCase{
214+
"s3://bucket.name/key?use-transfer-acceleration=true",
215+
"is not compatible with S3 Transfer Acceleration",
216+
"bucket_with_single_dot",
217+
},
218+
InvalidTransferAccelerationBucketTestCase{
219+
"s3://bucket/key?use-transfer-acceleration=true&endpoint=minio.local",
220+
"cannot be used with custom endpoints",
221+
"acceleration_with_custom_endpoint",
222+
}),
223+
[](const ::testing::TestParamInfo<InvalidTransferAccelerationBucketTestCase> & info) {
224+
return info.param.description;
225+
});
226+
#endif
227+
141228
// =============================================================================
142229
// S3 URL to HTTPS Conversion Tests
143230
// =============================================================================
@@ -272,7 +359,53 @@ INSTANTIATE_TEST_SUITE_P(
272359
},
273360
"https://s3.eu-west-1.amazonaws.com/versioned-bucket/path/to/object?versionId=version456",
274361
"with_region_and_versionId",
275-
}),
362+
},
363+
#if NIX_WITH_AWS_AUTH
364+
S3ToHttpsConversionTestCase{
365+
ParsedS3URL{
366+
.bucket = "my-cache",
367+
.key = {"nix", "store", "abc123.nar.xz"},
368+
.use_transfer_acceleration = true,
369+
},
370+
ParsedURL{
371+
.scheme = "https",
372+
.authority = ParsedURL::Authority{.host = "my-cache.s3-accelerate.amazonaws.com"},
373+
.path = {"", "nix", "store", "abc123.nar.xz"},
374+
},
375+
"https://my-cache.s3-accelerate.amazonaws.com/nix/store/abc123.nar.xz",
376+
"transfer_acceleration_enabled",
377+
},
378+
S3ToHttpsConversionTestCase{
379+
ParsedS3URL{
380+
.bucket = "tokyo-cache",
381+
.key = {"key.txt"},
382+
.region = "ap-northeast-1",
383+
.use_transfer_acceleration = true,
384+
},
385+
ParsedURL{
386+
.scheme = "https",
387+
.authority = ParsedURL::Authority{.host = "tokyo-cache.s3-accelerate.amazonaws.com"},
388+
.path = {"", "key.txt"},
389+
},
390+
"https://tokyo-cache.s3-accelerate.amazonaws.com/key.txt",
391+
"transfer_acceleration_with_region",
392+
},
393+
S3ToHttpsConversionTestCase{
394+
ParsedS3URL{
395+
.bucket = "my-bucket",
396+
.key = {"file"},
397+
.use_transfer_acceleration = false,
398+
},
399+
ParsedURL{
400+
.scheme = "https",
401+
.authority = ParsedURL::Authority{.host = "s3.us-east-1.amazonaws.com"},
402+
.path = {"", "my-bucket", "file"},
403+
},
404+
"https://s3.us-east-1.amazonaws.com/my-bucket/file",
405+
"transfer_acceleration_explicitly_disabled",
406+
}
407+
#endif
408+
),
276409
[](const ::testing::TestParamInfo<S3ToHttpsConversionTestCase> & info) { return info.param.description; });
277410

278411
} // namespace nix

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,38 @@ struct S3BinaryCacheStoreConfig : HttpBinaryCacheStoreConfig
9393
Default is 100 MiB. Only takes effect when multipart-upload is enabled.
9494
)"};
9595

96+
#if NIX_WITH_AWS_AUTH
97+
const Setting<bool> use_transfer_acceleration{
98+
this,
99+
false,
100+
"use-transfer-acceleration",
101+
R"(
102+
Whether to use AWS S3 Transfer Acceleration for faster uploads and downloads
103+
by routing transfers through CloudFront edge locations. This is particularly
104+
useful when accessing S3 buckets from geographically distant locations.
105+
106+
When enabled, requests are routed to `bucket-name.s3-accelerate.amazonaws.com`
107+
instead of the standard regional endpoint.
108+
109+
> **Note**
110+
>
111+
> Transfer Acceleration does not support bucket names with dots (periods).
112+
> Bucket names like `my.bucket.name` will not work with this setting.
113+
>
114+
> This setting only applies to AWS S3 buckets. It has no effect when using
115+
> custom endpoints for S3-compatible services.
116+
>
117+
> Additional charges apply for Transfer Acceleration. See AWS documentation
118+
> for pricing details.
119+
)"};
120+
#endif
121+
96122
/**
97123
* Set of settings that are part of the S3 URI itself.
98124
* These are needed for region specification and other S3-specific settings.
99125
*/
100-
const std::set<const AbstractSetting *> s3UriSettings = {&profile, &region, &scheme, &endpoint};
126+
const std::set<const AbstractSetting *> s3UriSettings = {
127+
&profile, &region, &scheme, &endpoint, &use_transfer_acceleration};
101128

102129
static const std::string name()
103130
{

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

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

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

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

87+
### S3 Transfer Acceleration
88+
89+
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.
90+
91+
To enable transfer acceleration, add the `use-transfer-acceleration=true` parameter to your S3 URL:
92+
93+
```console
94+
$ nix copy nixpkgs.hello \
95+
--to 's3://example-nix-cache?use-transfer-acceleration=true&region=ap-northeast-1'
96+
```
97+
98+
#### Requirements for Transfer Acceleration
99+
100+
- Bucket names cannot contain dots (`.`) - e.g., `my.bucket.name` will not work
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+
87108
### Examples
88109

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

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+
105133
)"

0 commit comments

Comments
 (0)