Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/113989.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 113989
summary: Add `max_multipart_parts` setting to S3 repository
area: Snapshot/Restore
type: enhancement
issues: []
16 changes: 13 additions & 3 deletions docs/reference/snapshot-restore/repository-s3.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,11 @@ multiple deployments may share the same bucket.

`chunk_size`::

(<<byte-units,byte value>>) Big files can be broken down into chunks during snapshotting if needed.
Specify the chunk size as a value and unit, for example:
`1TB`, `1GB`, `10MB`. Defaults to the maximum size of a blob in the S3 which is `5TB`.
(<<byte-units,byte value>>) The maximum size of object that {es} will write to the repository
when creating a snapshot. Files which are larger than `chunk_size` will be chunked into several
smaller objects. {es} may also split a file across multiple objects to satisfy other constraints
such as the `max_multipart_parts` limit. Defaults to `5TB` which is the
https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html[maximum size of an object in AWS S3].

`compress`::

Expand Down Expand Up @@ -292,6 +294,14 @@ include::repository-shared-settings.asciidoc[]
size allowed by S3. Defaults to `100mb` or `5%` of JVM heap, whichever is
smaller.

`max_multipart_parts` ::

(<<number,integer>>) The maximum number of parts that {es} will write during a multipart upload
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest fixing this as per #114093 here rather than doing two changes.

of a single object. Files which are larger than `buffer_size × max_multipart_parts` will be
chunked into several smaller objects. {es} may also split a file across multiple objects to
satisfy other constraints such as the `chunk_size` limit. Defaults to `10000` which is the
https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html[maximum number of parts in a multipart upload in AWS S3].

`canned_acl`::

The S3 repository supports all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ class S3Repository extends MeteredBlobStoreRepository {
MAX_FILE_SIZE_USING_MULTIPART
);

/**
* Maximum parts number for multipart upload. (see https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html)
*/
static final Setting<Integer> MAX_MULTIPART_PARTS = Setting.intSetting("max_multipart_parts", 10_000, 1, 10_000);

/**
* Sets the S3 storage class type for the backup files. Values may be standard, reduced_redundancy,
* standard_ia, onezone_ia and intelligent_tiering. Defaults to standard.
Expand Down Expand Up @@ -254,7 +259,9 @@ class S3Repository extends MeteredBlobStoreRepository {
}

this.bufferSize = BUFFER_SIZE_SETTING.get(metadata.settings());
this.chunkSize = CHUNK_SIZE_SETTING.get(metadata.settings());
var maxChunkSize = CHUNK_SIZE_SETTING.get(metadata.settings());
var maxPartsNum = MAX_MULTIPART_PARTS.get(metadata.settings());
this.chunkSize = objectSizeLimit(maxChunkSize, bufferSize, maxPartsNum);

// We make sure that chunkSize is bigger or equal than/to bufferSize
if (this.chunkSize.getBytes() < bufferSize.getBytes()) {
Expand Down Expand Up @@ -303,6 +310,20 @@ private static Map<String, String> buildLocation(RepositoryMetadata metadata) {
return Map.of("base_path", BASE_PATH_SETTING.get(metadata.settings()), "bucket", BUCKET_SETTING.get(metadata.settings()));
}

/**
* Calculates S3 object size limit based on 2 constraints: maximum object(chunk) size
* and maximum number of parts for multipart upload.
* https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html
*
* @param chunkSize s3 object size
* @param bufferSize s3 multipart upload part size
* @param maxPartsNum s3 multipart upload max parts number
*/
private static ByteSizeValue objectSizeLimit(ByteSizeValue chunkSize, ByteSizeValue bufferSize, int maxPartsNum) {
var bytes = Math.min(chunkSize.getBytes(), bufferSize.getBytes() * maxPartsNum);
return ByteSizeValue.ofBytes(bytes);
}

/**
* Holds a reference to delayed repository operation {@link Scheduler.Cancellable} so it can be cancelled should the repository be
* closed concurrently.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,37 @@ public void testAnalysisFailureDetail() {
}
}

// ensures that chunkSize is limited to chunk_size setting, when buffer_size * parts_num is bigger
public void testChunkSizeLimit() {
var meta = new RepositoryMetadata(
"dummy-repo",
"mock",
Settings.builder()
.put(S3Repository.BUCKET_SETTING.getKey(), "bucket")
.put(S3Repository.CHUNK_SIZE_SETTING.getKey(), "1GB")
.put(S3Repository.BUFFER_SIZE_SETTING.getKey(), "100MB")
.put(S3Repository.MAX_MULTIPART_PARTS.getKey(), 10_000) // ~1TB
.build()
);
try (var repo = createS3Repo(meta)) {
assertEquals(ByteSizeValue.ofGb(1), repo.chunkSize());
}
}

// ensures that chunkSize is limited to buffer_size * parts_num, when chunk_size setting is bigger
public void testPartsNumLimit() {
var meta = new RepositoryMetadata(
"dummy-repo",
"mock",
Settings.builder()
.put(S3Repository.BUCKET_SETTING.getKey(), "bucket")
.put(S3Repository.CHUNK_SIZE_SETTING.getKey(), "5TB")
.put(S3Repository.BUFFER_SIZE_SETTING.getKey(), "100MB")
.put(S3Repository.MAX_MULTIPART_PARTS.getKey(), 10_000)
.build()
);
try (var repo = createS3Repo(meta)) {
assertEquals(ByteSizeValue.ofMb(1_000_000), repo.chunkSize());
}
}
}
Loading