• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1**Design:** New Feature, **Status:** [In Development](../../../README.md)
2
3# Design Document (Automatic Request Batching)
4
5## Introduction
6
7* * *
8Some customers have described a need for batch write operations across multiple AWS services but the lack of these features either serve as blockers to adoption of the v2 SDK or limit SDK usability for customers. Specifically, this feature was implemented in v1 for the SQS service in the form of the `AmazonSQSBufferedAsyncClient` but equivalent functionality has not been ported over to v2.
9
10However, since batch write operations are already included in many AWS services, a general automatic batching solution could not only be implemented in SQS but in any service that might benefit from it. On top of features included in v1, additional simplifications and abstractions can also be included in the batch manager to simplify how customers interact with batching throughout the SDK. Therefore the batching manager hopes to benefit customers by reducing cost, improving performance, and/or simplifying implementation.
11
12This document proposes how this general approach should be implemented in the Java SDK v2.
13
14
15## Design Review
16
17* * *
18Look at decision log here: https://github.com/aws/aws-sdk-java-v2/blob/master/docs/design/core/batch-utilities/DecisionLog.md
19
20The Java SDK team has decided to implement a separate batch manager for the time being. Further discussion is required surrounding separate utilities vs implementing directly on the client.
21
22## Overview
23
24* * *
25The batch manager proposed in this document will work similarly to v1’s `AmazonSQSBufferedAsyncClient`. Calls made through the manager will first be buffered before being sent as a batch request to the respective service. Additional functionality will also be implemented in v2, such as the ability to automatically batch an array of items by the manager.
26
27Client-side buffering will be implemented generically and allows up to the maximum requests for the respective service (ex. max 10 requests for SQS). Doing so will decrease the cost of using these AWS services by reducing the number of sent requests.
28
29## Proposed APIs
30
31* * *
32The v2 SDK will support a batch manager for both sync and async clients that can leverage batch calls.
33
34### Instantiation
35
36**Option 1: Instantiating from an existing client**
37
38```
39// Sync Batch Manager
40SqsClient sqs = SqsClient.create();
41SqsBatchManager sqsBatch = sqs.batchManager();
42
43// Async Batch Manager
44SqsAsyncClient sqsAsync = SqsAsyncClient.create();
45SqsAsyncBatchManager sqsAsyncBatch = sqsAsync.batchManager();
46```
47
48**Option 2: Instantiating from batch manager builder**
49
50```
51// Sync Batch Manager
52SqsBatchManager sqsBatch = SqsBatchManager.builder()
53                                          .client(client)
54                                          .overrideConfiguration(newConfig)
55                                          .build();
56
57// Async Batch Manager
58SqsAsyncBatchManager sqsBatch = SqsAsyncBatchManager.builder()
59                                          .client(asyncClient)
60                                          .overrideConfiguration(newConfig)
61                                          .build();
62```
63
64### General Usage Examples:
65
66Note: Focusing on automatic batching and manual flushing for the scope of the internship.
67
68```
69// 1. Automatic Batching
70SendMessageRequest request1 = SendMessageRequest.builder()
71                                                .messageBody("1")
72                                                .build();
73SendMessageRequest request2 = SendMessageRequest.builder()
74                                                .messageBody("2")
75                                                .build();
76
77// Sync
78SqsClient sqs = SqsClient.create();
79SqsBatchManager sqsBatch = sqs.batchManager();
80CompletableFuture<SendMessageResponse> response1 = sqsBatch.sendMessage(request1);
81CompletableFuture<SendMessageResponse> response2 = sqsBatch.sendMessage(request2);
82
83// Async
84CompletableFuture<SendMessageResponse> response1 = sqsBatch.sendMessage(request1);
85CompletableFuture<SendMessageResponse> response2 = sqsBatch.sendMessage(request2);
86
87// 2. Manual Flushing
88sqsBatch.flush();
89```
90
91
92
93### `{Service}BatchManager` and `{Service}AsyncBatchManager`
94
95For each service that can leverage batch features, two classes will be created: A {Service}BatchManager and {Service}AsyncBatchManager (ex. SqsBatchManager and SqsAsyncBatchManager for SQS). This follows the naming convention established in v2 like with {Service}Client and {Service}Manager.
96
97**Sync:**
98
99```
100/**
101 * Batch Manager class that implements batching features for a sync client.
102 */
103 @SdkPublicApi
104 @Generated("software.amazon.awssdk:codegen")
105 public interface SqsBatchManager {
106
107    /**
108     * Buffers outgoing requests on the client and sends them as batch requests to the service.
109     * Requests are batched together according to a batchKey and are sent periodically to the
110     * service as determined by {@link #maxBatchOpenInMs}. If the number of requests for a
111     * batchKey reaches or exceeds {@link #maxBatchItems}, then the requests are immediately
112     * flushed and the timeout on the periodic flush is reset.
113     * By default, messages are batched according to a service's maximum size for a batch request.
114     * These settings can be customized via the configuration.
115     *
116     * @param request the outgoing request.
117     * @return a CompletableFuture of the corresponding response.
118     */
119    CompletableFuture<SendMessageResponse> sendMessage(SendMessageRequest message);
120
121    /**
122     * Manually flush the buffer for sendMessage requests. Completes when requests
123     * are sent. An exception is returned otherwise.
124     */
125    CompletableFuture<Void> flush();
126
127    // Other Batch Manager methods omitted
128    // ...
129
130    interface Builder {
131
132        Builder client (SqsClient client);
133
134        /**
135         * Method to override the default Batch Manager configuration.
136         *
137         * @param overrideConfig The provided overriding configuration.
138         * @return a reference to this object so that method calls can be chained.
139         */
140        Builder overrideConfiguration(BatchOverrideConfiguration overrideConfig);
141
142        /**
143         * Convenient method to override the default Batch Manager configuration
144         * without needing to create an instance manually.
145         *
146         * @param overrideConfig The consumer that provides the
147                                 overriding configuration.
148         * @return a reference to this object so that method calls can be chained.
149         */
150        default Builder overrideConfiguration(
151                        Consumer<BatchOverrideConfiguration> overrideConfig);
152
153        SqsBatchManager build();
154
155    }
156 }
157```
158
159**Async:**
160
161```
162/**
163 * Batch Manager class that implements batching features for an async client.
164 */
165 @SdkPublicApi
166 @Generated("software.amazon.awssdk:codegen")
167 public interface SqsAsyncBatchManager {
168
169    /**
170     * Buffers outgoing requests on the client and sends them as batch requests to the service.
171     * Requests are batched together according to a batchKey and are sent periodically to the
172     * service as determined by {@link #maxBatchOpenInMs}. If the number of requests for a
173     * batchKey reaches or exceeds {@link #maxBatchItems}, then the requests are immediately
174     * flushed and the timeout on the periodic flush is reset.
175     * By default, messages are batched according to a service's maximum size for a batch request.
176     * These settings can be customized via the configuration.
177     *
178     * @param request the outgoing request.
179     * @return a CompletableFuture of the corresponding response.
180     */
181    CompletableFuture<SendMessageResponse> sendMessage(SendMessageRequest message);
182
183    /**
184     * Manually flush the buffer for sendMessage requests. Completes when requests
185     * are sent. An exception is returned otherwise.
186     */
187    CompletableFuture<Void> flush();
188
189    // Other Batch Manager methods omitted
190    // ...
191
192    interface Builder {
193
194        Builder client (SqsAsyncClient client);
195
196        /**
197         * Method to override the default Batch Manager configuration.
198         *
199         * @param overrideConfig The provided overriding configuration.
200         * @return a reference to this object so that method calls can be chained.
201         */
202        Builder overrideConfiguration(BatchOverrideConfiguration overrideConfig);
203
204        /**
205         * Convenient method to override the default Batch Manager configuration
206         * without needing to create an instance manually.
207         *
208         * @param overrideConfig The consumer that provides the
209                                 overriding configuration.
210         * @return a reference to this object so that method calls can be chained.
211         */
212        default Builder overrideConfiguration(
213                        Consumer<BatchOverrideConfiguration> overrideConfig);
214
215        SqsAsyncBatchManager build();
216
217    }
218 }
219
220```
221
222
223
224### `BatchOverrideConfiguration`
225
226```
227/**
228 * Configuration class to specify how the Batch Manager will implement its
229 * batching features.
230 */
231public final class BatchOverrideConfiguration {
232
233    private final int maxBatchItems;
234
235    private final long maxBatchSizeInBytes;
236
237    private final Duration maxBatchOpenInMs;
238
239    // More fields and methods omitted
240    // Focus on including configurable fields from v1
241}
242```
243
244* * *
245
246## FAQ
247
248### **Which Services will we generate a Batch Manager?**
249
250Services that already support batch requests (ex. SQS with sendMessageBatch, Kinesis with putRecords) in order to reduce cost for customers should be supported with a batch manager.
251
252Note: In this document, we focus on implementing a batch manager for SQS to ensure the functionality of v1’s `AmazonSQSBufferedAsyncClient` is carried over to v2. Therefore the code snippets used mainly focus on methods and types  supported by the SQS client.
253
254### **Why don’t we just implement batching features directly on the low level client?**
255
256There are three options we discussed in implementing batching features:
257
2581. Create batching features directly on the low level client
2592. Create a separate high level library
2603. Create a separate batch manager class
261
262Using these three options would look like:
263
264```
265SqsAsyncClient sqsAsync = SqsAsyncClient.builder().build();
266
267// Option 1
268sqsAsync.automaticSendMessageBatch(message1);
269sqsAsync.automaticSendMessageBatch(message1);
270
271// Option 2
272 SqsAsyncBatchManager batchManager = SqsAsyncBatchManager
273                                        .builder()
274                                        .client(sqsAsync)
275                                        .build()
276batchManager.sendMessage(message1);
277batchManager.sendMessage(message2);
278
279// Option 3
280SqsAsyncBatchManager batchManager = SqsAsyncBatchManager.batchManager()
281batchManager.sendMessage(message1);
282batchManager.sendMessage(message2);
283```
284
285
286**Option 1 Pros:**
287
2881. Automatic batching features are slightly more discoverable.
289
290**Option 2 Pros:**
291
2921. Hand written library can be more user friendly than generated utility methods.
2932. Works very similarly to the v1 `AmazonSQSBufferedAsyncClient`, so migration from v1 to v2 should require minimal changes.
294
295**Option 3 Pros:**
296
2971. All batch related features for a service would be self-contained in the client’s respective utility class.
2982. Works very similarly to the v1 `AmazonSQSBufferedAsyncClient`, so migration from v1 to v2 should require minimal changes.
2993. Consistent with existing utilities such as the Waiters utility class.
3004. Easily configurable and scalable to incorporate many services.
301
302**Decision:** Option 3 will be used since it closely follows the style used throughout v2 (especially similar to how the waiters abstraction is used). Furthermore, it provides the most flexibility to scale across multiple services without becoming too complicated to use.
303
304Look at [decision log](./DecisionLog.md) for reasoning made on 6/29/2021.
305
306### Why do we only support sending one message at a time instead of a list of messages or streams?
307
308Supporting a singular sendMessage method makes it easier and simpler for customers to correlate request messages with the respective responses than an implementation that receives streams or lists of messages (ex. sending a SendMessageRequest in SQS returns a SendMessageResponse as opposed to a batch response wrapper class).
309
310Sending multiple messages or a stream of messages can be as simple as looping through each of the messages and calling the sendMessage method. Therefore, if needed, adding support for sending streams or a list of messages can easily be done as long as the sendMessage method is supported.
311
312### **Why support Sync and Async?**
313
314Supporting sync and async clients not only ensures that the APIs of both clients do not diverge, but would also have parity with the buffered client in v1. Furthermore, this support is just a matter of using the respective sync and async clients’ methods to make the requests and should both be simple for customers to understand, and for the SDK team to implement.
315
316
317### **Why does `sendMessage` return a CompletableFuture for both the sync and async client?**
318
319The sendMessage method automatically buffers each sendMessage request until the buffer is full or a timeout occurs. Therefore, the sync client’s sendMessage would block until the entire batchRequest is sent and received, which could take as long as the timeout specified. To reduce blocking for this extended period of time, the sendMessage returns a CompletableFuture in both the sync and async client which completes when the underlying batchRequest is sent and a response is received.
320
321Therefore, as mentioned above, the distinction between the sync and async client lies in the use of the respective clients’ methods (ex. the sync batch manager leverages the sync client’s sendMessageBatch under the hood while the async batch manager uses the async client’s sendMessageBatch).
322
323
324## References
325
326* * *
327Github feature requests for specific services:
328
329* [SQS](https://github.com/aws/aws-sdk-java-v2/issues/165)
330* [Kinesis](https://github.com/aws/aws-sdk-java/issues/1162)
331* [Kinesis Firehose](https://github.com/aws/aws-sdk-java/issues/1343)
332* [CloudWatch](https://github.com/aws/aws-sdk-java/issues/1109)
333* [S3 batch style deletions](https://github.com/aws/aws-sdk-java/issues/1307)
334
335