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