-
Notifications
You must be signed in to change notification settings - Fork 18
Add request coalescing and relevant configuration #356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,26 +48,33 @@ public class ReadVectoredTest extends IntegrationTestBase { | |
|
|
||
| @ParameterizedTest | ||
| @MethodSource("vectoredReads") | ||
| void testVectoredReads(S3ClientKind s3ClientKind, IntFunction<ByteBuffer> allocate) | ||
| void testVectoredReads( | ||
| S3ClientKind s3ClientKind, | ||
| IntFunction<ByteBuffer> allocate, | ||
| AALInputStreamConfigurationKind configurationKind) | ||
| throws IOException { | ||
| testReadVectored( | ||
| s3ClientKind, S3Object.RANDOM_1GB, AALInputStreamConfigurationKind.DEFAULT, allocate); | ||
| testReadVectored(s3ClientKind, S3Object.RANDOM_1GB, configurationKind, allocate); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("vectoredReads") | ||
| void testVectoredReadsInSingleBlock(S3ClientKind s3ClientKind, IntFunction<ByteBuffer> allocate) | ||
| void testVectoredReadsInSingleBlock( | ||
| S3ClientKind s3ClientKind, | ||
| IntFunction<ByteBuffer> allocate, | ||
| AALInputStreamConfigurationKind configurationKind) | ||
| throws IOException { | ||
| testReadVectoredInSingleBlock( | ||
| s3ClientKind, S3Object.RANDOM_1GB, AALInputStreamConfigurationKind.DEFAULT, allocate); | ||
| testReadVectoredInSingleBlock(s3ClientKind, S3Object.RANDOM_1GB, configurationKind, allocate); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("vectoredReads") | ||
| void testVectoredReadsForSequentialRanges( | ||
| S3ClientKind s3ClientKind, IntFunction<ByteBuffer> allocate) throws IOException { | ||
| S3ClientKind s3ClientKind, | ||
| IntFunction<ByteBuffer> allocate, | ||
| AALInputStreamConfigurationKind configurationKind) | ||
| throws IOException { | ||
| testReadVectoredForSequentialRanges( | ||
| s3ClientKind, S3Object.RANDOM_1GB, AALInputStreamConfigurationKind.DEFAULT, allocate); | ||
| s3ClientKind, S3Object.RANDOM_1GB, configurationKind, allocate); | ||
| } | ||
|
|
||
| @Test | ||
|
|
@@ -281,13 +288,18 @@ static Stream<Arguments> vectoredReads() { | |
| s3ClientKinds.add(S3ClientKind.SDK_V2_JAVA_ASYNC); | ||
| s3ClientKinds.add(S3ClientKind.SDK_V2_JAVA_SYNC); | ||
|
|
||
| List<AALInputStreamConfigurationKind> configurationKinds = new ArrayList<>(); | ||
| configurationKinds.add(AALInputStreamConfigurationKind.DEFAULT); | ||
| configurationKinds.add(AALInputStreamConfigurationKind.NO_REQUEST_COALESCING); | ||
|
|
||
| List<IntFunction<ByteBuffer>> allocate = new ArrayList<>(); | ||
| allocate.add(ByteBuffer::allocate); | ||
| allocate.add(ByteBuffer::allocateDirect); | ||
|
|
||
| for (S3ClientKind s3ClientKind : s3ClientKinds) { | ||
| for (IntFunction<ByteBuffer> allocator : allocate) { | ||
| testCases.add(Arguments.of(s3ClientKind, allocator)); | ||
| for (AALInputStreamConfigurationKind configurationKind : configurationKinds) | ||
| testCases.add(Arguments.of(s3ClientKind, allocator, configurationKind)); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -300,26 +312,26 @@ static Stream<Arguments> vectoredReads() { | |
| * | ||
| * @param s3ClientKind S3 client kind to use | ||
| * @param s3Object S3 object to read | ||
| * @param AALInputStreamConfigurationKind configuration kind | ||
| * @param configurationKind configuration kind | ||
| * @param allocate method to allocate the buffer, can be direct or non-direct | ||
| * @throws IOException on any IOException | ||
| */ | ||
| protected void testReadVectored( | ||
| @NonNull S3ClientKind s3ClientKind, | ||
| @NonNull S3Object s3Object, | ||
| @NonNull AALInputStreamConfigurationKind AALInputStreamConfigurationKind, | ||
| @NonNull AALInputStreamConfigurationKind configurationKind, | ||
| @NonNull IntFunction<ByteBuffer> allocate) | ||
| throws IOException { | ||
|
|
||
| try (S3AALClientStreamReader s3AALClientStreamReader = | ||
| getStreamReader(s3ClientKind, AALInputStreamConfigurationKind)) { | ||
| getStreamReader(s3ClientKind, configurationKind)) { | ||
|
|
||
| S3SeekableInputStream s3SeekableInputStream = | ||
| s3AALClientStreamReader.createReadStream(s3Object, OpenStreamInformation.ofDefaults()); | ||
|
|
||
| List<ObjectRange> objectRanges = new ArrayList<>(); | ||
| objectRanges.add(new ObjectRange(new CompletableFuture<>(), 50, ONE_MB)); | ||
| objectRanges.add(new ObjectRange(new CompletableFuture<>(), 2 * ONE_MB, 800)); | ||
| objectRanges.add(new ObjectRange(new CompletableFuture<>(), 3 * ONE_MB, 800)); | ||
|
|
||
| // a range that should be within a single block | ||
| objectRanges.add(new ObjectRange(new CompletableFuture<>(), 200 * ONE_MB, 8 * ONE_MB)); | ||
|
|
@@ -340,7 +352,8 @@ protected void testReadVectored( | |
| objectRange.getByteBuffer().join(); | ||
| } | ||
|
|
||
| // Range [50MB - 51MB, 2MB - 2.8MB] will make 2 GET requests | ||
| // Range [50 - 1MB, 3MB - 3.8MB] will make 2 GET requests -- will not be coalesced with 1MB | ||
| // tolerance | ||
| // Range [200MB - 208MB] will make a single GET as it is an 8MB block. | ||
| // Range [260MB - 284MB] will make 3 GET requests | ||
| assertEquals( | ||
|
|
@@ -357,12 +370,12 @@ protected void testReadVectored( | |
| protected void testReadVectoredInSingleBlock( | ||
| @NonNull S3ClientKind s3ClientKind, | ||
| @NonNull S3Object s3Object, | ||
| @NonNull AALInputStreamConfigurationKind AALInputStreamConfigurationKind, | ||
| @NonNull AALInputStreamConfigurationKind configurationKind, | ||
| @NonNull IntFunction<ByteBuffer> allocate) | ||
| throws IOException { | ||
|
|
||
| try (S3AALClientStreamReader s3AALClientStreamReader = | ||
| this.getStreamReader(s3ClientKind, AALInputStreamConfigurationKind)) { | ||
| this.getStreamReader(s3ClientKind, configurationKind)) { | ||
|
|
||
| S3SeekableInputStream s3SeekableInputStream = | ||
| s3AALClientStreamReader.createReadStream(s3Object, OpenStreamInformation.ofDefaults()); | ||
|
|
@@ -391,12 +404,12 @@ protected void testReadVectoredInSingleBlock( | |
| protected void testReadVectoredForSequentialRanges( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i see that this tests range coalescing, but it would be good to add a range coalescing test as well, which will test ranges are within that 1MB distance. eg: [0-4MB, 4.3MB - 5MB, 5.9MB - 7MB].
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @ahmarsuhail. Do test under IOPlanTest cover your concern. I was trying to put corner cases for actual coalesing business logic there instead of here. |
||
| @NonNull S3ClientKind s3ClientKind, | ||
| @NonNull S3Object s3Object, | ||
| @NonNull AALInputStreamConfigurationKind AALInputStreamConfigurationKind, | ||
| @NonNull AALInputStreamConfigurationKind configurationKind, | ||
| @NonNull IntFunction<ByteBuffer> allocate) | ||
| throws IOException { | ||
|
|
||
| try (S3AALClientStreamReader s3AALClientStreamReader = | ||
| this.getStreamReader(s3ClientKind, AALInputStreamConfigurationKind)) { | ||
| this.getStreamReader(s3ClientKind, configurationKind)) { | ||
|
|
||
| S3SeekableInputStream s3SeekableInputStream = | ||
| s3AALClientStreamReader.createReadStream(s3Object, OpenStreamInformation.ofDefaults()); | ||
|
|
@@ -416,12 +429,18 @@ protected void testReadVectoredForSequentialRanges( | |
| objectRange.getByteBuffer().join(); | ||
| } | ||
|
|
||
| int expectedRequests = | ||
| (configurationKind == AALInputStreamConfigurationKind.NO_REQUEST_COALESCING) | ||
| ? objectRanges.size() | ||
| : 3; | ||
|
|
||
| assertEquals( | ||
| 5, | ||
| expectedRequests, | ||
| s3AALClientStreamReader | ||
| .getS3SeekableInputStreamFactory() | ||
| .getMetrics() | ||
| .get(MetricKey.GET_REQUEST_COUNT)); | ||
| verifyStreamContents(objectRanges, s3AALClientStreamReader, s3Object); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,8 @@ public class PhysicalIOConfiguration { | |
| private static final long DEFAULT_READ_BUFFER_SIZE = 128 * ONE_KB; | ||
| private static final long DEFAULT_TARGET_REQUEST_SIZE = 8 * ONE_MB; | ||
| private static final double DEFAULT_REQUEST_TOLERANCE_RATIO = 1.4; | ||
| private static final boolean DEFAULT_COALESCE_REQUEST = true; | ||
| private static final long DEFAULT_COALESCE_REQUEST_TOLERANCE = ONE_MB; | ||
|
|
||
| /** | ||
| * Capacity, in blobs. {@link PhysicalIOConfiguration#DEFAULT_MEMORY_CAPACITY_BYTES} by default. | ||
|
|
@@ -175,6 +177,16 @@ public class PhysicalIOConfiguration { | |
|
|
||
| private static final String REQUEST_TOLERANCE_RATIO_KEY = "request.tolerance.ratio"; | ||
|
|
||
| /** Flag to enable request Coalescing */ | ||
| @Builder.Default private boolean requestCoalesce = DEFAULT_COALESCE_REQUEST; | ||
|
|
||
| private static final String REQUEST_COALESCE_KEY = "request.coalesce"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why add it as a config? when would we not want to do this?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: We may rename like |
||
|
|
||
| /** Number of bytes to read and swallow when merging two requests */ | ||
| @Builder.Default private long requestCoalesceTolerance = DEFAULT_COALESCE_REQUEST_TOLERANCE; | ||
|
|
||
| private static final String REQUEST_COALESCE_TOLERANCE_KEY = "request.coalesce.tolerance"; | ||
|
|
||
| /** Default set of settings for {@link PhysicalIO} */ | ||
| public static final PhysicalIOConfiguration DEFAULT = PhysicalIOConfiguration.builder().build(); | ||
|
|
||
|
|
@@ -225,6 +237,10 @@ public static PhysicalIOConfiguration fromConfiguration(ConnectorConfiguration c | |
| configuration.getLong(TARGET_REQUEST_SIZE_KEY, DEFAULT_TARGET_REQUEST_SIZE)) | ||
| .requestToleranceRatio( | ||
| configuration.getDouble(REQUEST_TOLERANCE_RATIO_KEY, DEFAULT_REQUEST_TOLERANCE_RATIO)) | ||
| .requestCoalesce(configuration.getBoolean(REQUEST_COALESCE_KEY, DEFAULT_COALESCE_REQUEST)) | ||
| .requestCoalesceTolerance( | ||
| configuration.getLong( | ||
| REQUEST_COALESCE_TOLERANCE_KEY, DEFAULT_COALESCE_REQUEST_TOLERANCE)) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -251,6 +267,9 @@ public static PhysicalIOConfiguration fromConfiguration(ConnectorConfiguration c | |
| * @param readBufferSize Size of the maximum buffer for read operations | ||
| * @param targetRequestSize Target S3 request size, in bytes | ||
| * @param requestToleranceRatio Request tolerance ratio | ||
| * @param requestCoalesce Flag to enable request Coalescing | ||
| * @param requestCoalesceTolerance Number of bytes to read and swallow when merging two requests | ||
| * @throws IllegalArgumentException if any of the parameters are invalid | ||
| */ | ||
| @Builder | ||
| private PhysicalIOConfiguration( | ||
|
|
@@ -271,7 +290,9 @@ private PhysicalIOConfiguration( | |
| int threadPoolSize, | ||
| long readBufferSize, | ||
| long targetRequestSize, | ||
| double requestToleranceRatio) { | ||
| double requestToleranceRatio, | ||
| boolean requestCoalesce, | ||
| long requestCoalesceTolerance) { | ||
| Preconditions.checkArgument(memoryCapacityBytes > 0, "`memoryCapacityBytes` must be positive"); | ||
| Preconditions.checkArgument( | ||
| memoryCleanupFrequencyMilliseconds > 0, | ||
|
|
@@ -319,6 +340,8 @@ private PhysicalIOConfiguration( | |
| this.readBufferSize = readBufferSize; | ||
| this.targetRequestSize = targetRequestSize; | ||
| this.requestToleranceRatio = requestToleranceRatio; | ||
| this.requestCoalesce = requestCoalesce; | ||
| this.requestCoalesceTolerance = requestCoalesceTolerance; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -345,7 +368,8 @@ public String toString() { | |
| builder.append("\treadBufferSize: " + readBufferSize + "\n"); | ||
| builder.append("\ttargetRequestSize: " + targetRequestSize + "\n"); | ||
| builder.append("\trequestToleranceRatio: " + requestToleranceRatio + "\n"); | ||
|
|
||
| builder.append("\trequestCoalesce: " + requestCoalesce + "\n"); | ||
| builder.append("\trequestCoalesceTolerance: " + requestCoalesceTolerance + "\n"); | ||
| return builder.toString(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we squash some of these tests down using parameterized tests?