generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 176
feat(batch): Async Processing of Records for for SQS Fifo #3160
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
Merged
am29d
merged 22 commits into
aws-powertools:main
from
arnabrahman:3140-async-fifo-processor
Nov 8, 2024
Merged
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
e0c1a1f
feat: `SqsFifo` mixin class
arnabrahman c4c7acf
refactor: use `SqsFifo` mixin inside `SqsFifoPartialProcessor`
arnabrahman c3b2bb4
fix: put back `SqsFifoPartialProcessor` constructor
arnabrahman 85b7b00
feat: `SqsFifoPartialProcessorAsync` for asynchronous FIFO record pro…
arnabrahman 632b8f9
refactor: `BatchProcessingOptions` & `processPartialResponse` for `Sq…
arnabrahman c298f8b
tests: tests for `SqsFifoPartialProcessorAsync`, similar to `SqsFifoP…
arnabrahman 9cf9cd3
refactor: extract `shouldShortCircuit` & `shouldSkipCurrentGroup` cal…
arnabrahman 1453b0d
style: spacing in the doc
arnabrahman 8c407bc
Merge branch 'main' into 3140-async-fifo-processor
arnabrahman 97a0682
feat: revert to original implementation for `SqsFifoPartialProcessor`
arnabrahman bf95cd2
refactor: remove `Mixin` from `SqsFifoPartialProcessorAsync`
arnabrahman a425e81
fix: sonarlint issue for `failedGroupIds`
arnabrahman f561c1c
doc: async sqs fifo message processing
arnabrahman ae5c3ce
test: refactor `SqsFifoPartialProcessor` & `SqsFifoPartialProcessorAs…
arnabrahman f9beef9
refactor: use `SqsFifoProcessingUtils` inside `SqsFifoPartialProcesso…
arnabrahman b27f651
doc: update doc comments for `failureHandler`
arnabrahman d23b178
Merge branch 'main' into 3140-async-fifo-processor
am29d 5d9d203
Update packages/batch/src/processPartialResponse.ts
arnabrahman fe25477
refactor: `SqsFifoProcessor` for functionalities used in fifo process…
arnabrahman dc345c5
Merge branch '3140-async-fifo-processor' of github.com:arnabrahman/aw…
arnabrahman 8c7a8f1
Merge branch 'main' into 3140-async-fifo-processor
am29d 42e74a2
Merge branch 'main' into 3140-async-fifo-processor
am29d File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { | ||
SqsFifoPartialProcessorAsync, | ||
processPartialResponse, | ||
} from '@aws-lambda-powertools/batch'; | ||
import { Logger } from '@aws-lambda-powertools/logger'; | ||
import type { SQSHandler, SQSRecord } from 'aws-lambda'; | ||
|
||
const processor = new SqsFifoPartialProcessorAsync(); | ||
const logger = new Logger(); | ||
|
||
const recordHandler = async (record: SQSRecord): Promise<void> => { | ||
const payload = record.body; | ||
if (payload) { | ||
const item = JSON.parse(payload); | ||
logger.info('Processed item', { item }); | ||
} | ||
}; | ||
|
||
export const handler: SQSHandler = async (event, context) => | ||
processPartialResponse(event, recordHandler, processor, { | ||
context, | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import type { SQSRecord } from 'aws-lambda'; | ||
import { BatchProcessor } from './BatchProcessor.js'; | ||
import { SqsFifoProcessingUtils } from './SqsFifoProcessingUtils.js'; | ||
import { EventType } from './constants.js'; | ||
import { | ||
type BatchProcessingError, | ||
SqsFifoMessageGroupShortCircuitError, | ||
SqsFifoShortCircuitError, | ||
} from './errors.js'; | ||
import type { | ||
BaseRecord, | ||
EventSourceDataClassTypes, | ||
FailureResponse, | ||
SuccessResponse, | ||
} from './types.js'; | ||
|
||
/** | ||
* Batch processor for SQS FIFO queues | ||
* | ||
* This class extends the {@link BatchProcessor} class and provides | ||
* a mechanism to process records from SQS FIFO queues asynchronously. | ||
* | ||
* By default, we will stop processing at the first failure and mark unprocessed messages as failed to preserve ordering. | ||
* | ||
* However, this behavior may not be optimal for customers who wish to proceed with processing messages from a different group ID. | ||
* | ||
* @example | ||
* ```typescript | ||
* import { | ||
* BatchProcessor, | ||
* SqsFifoPartialProcessorAsync, | ||
* processPartialResponse, | ||
* } from '@aws-lambda-powertools/batch'; | ||
* import type { SQSRecord, SQSHandler } from 'aws-lambda'; | ||
* | ||
* const processor = new SqsFifoPartialProcessorAsync(); | ||
* | ||
* const recordHandler = async (record: SQSRecord): Promise<void> => { | ||
* const payload = JSON.parse(record.body); | ||
* }; | ||
* | ||
* export const handler: SQSHandler = async (event, context) => | ||
* processPartialResponse(event, recordHandler, processor, { | ||
* context, | ||
* }); | ||
* ``` | ||
*/ | ||
class SqsFifoPartialProcessorAsync extends BatchProcessor { | ||
/** | ||
* Utility class for processing SQS FIFO queues | ||
*/ | ||
readonly #utils: SqsFifoProcessingUtils; | ||
|
||
public constructor() { | ||
super(EventType.SQS); | ||
this.#utils = new SqsFifoProcessingUtils(); | ||
} | ||
|
||
/** | ||
* Handles a failure for a given record. | ||
* | ||
* @param record - The record that failed. | ||
* @param exception - The error that occurred. | ||
*/ | ||
public failureHandler( | ||
record: EventSourceDataClassTypes, | ||
exception: Error | ||
): FailureResponse { | ||
this.#utils.processFailureForCurrentGroup(this.options); | ||
|
||
return super.failureHandler(record, exception); | ||
} | ||
|
||
/** | ||
* Process a record with a asynchronous handler | ||
* | ||
* This method orchestrates the processing of a batch of records asynchronously | ||
* for SQS FIFO queues. | ||
* | ||
* The method calls the prepare hook to initialize the processor and then | ||
* iterates over each record in the batch, processing them one by one. | ||
* | ||
* If one of them fails and `skipGroupOnError` is not true, the method short circuits | ||
* the processing and fails the remaining records in the batch. | ||
* | ||
* If one of them fails and `skipGroupOnError` is true, then the method fails the current record | ||
* if the message group has any previous failure, otherwise keeps processing. | ||
* | ||
* Then, it calls the clean hook to clean up the processor and returns the | ||
* processed records. | ||
*/ | ||
public async process(): Promise<(SuccessResponse | FailureResponse)[]> { | ||
this.prepare(); | ||
|
||
const processedRecords: (SuccessResponse | FailureResponse)[] = []; | ||
let currentIndex = 0; | ||
for (const record of this.records) { | ||
this.#utils.setCurrentGroup( | ||
(record as SQSRecord).attributes?.MessageGroupId | ||
); | ||
|
||
if (this.#utils.shouldShortCircuit(this.failureMessages, this.options)) { | ||
return this.shortCircuitProcessing(currentIndex, processedRecords); | ||
} | ||
|
||
const result = this.#utils.shouldSkipCurrentGroup(this.options) | ||
? this.#processFailRecord( | ||
record, | ||
new SqsFifoMessageGroupShortCircuitError() | ||
) | ||
: await this.processRecord(record); | ||
|
||
processedRecords.push(result); | ||
currentIndex++; | ||
} | ||
|
||
this.clean(); | ||
|
||
return processedRecords; | ||
} | ||
|
||
/** | ||
* Starting from the first failure index, fail all remaining messages regardless | ||
* of their group ID. | ||
* | ||
* This short circuit mechanism is used when we detect a failed message in the batch. | ||
* | ||
* Since messages in a FIFO queue are processed in order, we must stop processing any | ||
* remaining messages in the batch to prevent out-of-order processing. | ||
* | ||
* @param firstFailureIndex Index of first message that failed | ||
* @param processedRecords Array of response items that have been processed both successfully and unsuccessfully | ||
*/ | ||
protected shortCircuitProcessing( | ||
firstFailureIndex: number, | ||
processedRecords: (SuccessResponse | FailureResponse)[] | ||
): (SuccessResponse | FailureResponse)[] { | ||
const remainingRecords = this.records.slice(firstFailureIndex); | ||
|
||
for (const record of remainingRecords) { | ||
this.#processFailRecord(record, new SqsFifoShortCircuitError()); | ||
} | ||
|
||
this.clean(); | ||
|
||
return processedRecords; | ||
} | ||
|
||
/** | ||
* Processes a fail record. | ||
* | ||
* @param record - The record that failed. | ||
* @param exception - The error that occurred. | ||
*/ | ||
#processFailRecord( | ||
record: BaseRecord, | ||
exception: BatchProcessingError | ||
): FailureResponse { | ||
const data = this.toBatchType(record, this.eventType); | ||
|
||
return this.failureHandler(data, exception); | ||
} | ||
} | ||
|
||
export { SqsFifoPartialProcessorAsync }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import type { | ||
BatchProcessingOptions, | ||
EventSourceDataClassTypes, | ||
} from './types.js'; | ||
|
||
/** | ||
* Utility class to handle processing of SQS FIFO messages. | ||
*/ | ||
class SqsFifoProcessingUtils { | ||
/** | ||
* The ID of the current message group being processed. | ||
*/ | ||
#currentGroupId?: string; | ||
|
||
/** | ||
* A set of group IDs that have already encountered failures. | ||
*/ | ||
readonly #failedGroupIds: Set<string>; | ||
|
||
public constructor() { | ||
this.#failedGroupIds = new Set<string>(); | ||
} | ||
|
||
/** | ||
* Adds the specified group ID to the set of failed group IDs. | ||
* | ||
* @param group - The group ID to be added to the set of failed group IDs. | ||
*/ | ||
public addToFailedGroup(group: string): void { | ||
this.#failedGroupIds.add(group); | ||
} | ||
|
||
/** | ||
* Sets the current group ID for the message being processed. | ||
* | ||
* @param group - The group ID of the current message being processed. | ||
*/ | ||
public setCurrentGroup(group?: string): void { | ||
this.#currentGroupId = group; | ||
} | ||
|
||
/** | ||
* Determines whether the current group should be short-circuited. | ||
* | ||
* If we have any failed messages, we should then short circuit the process and | ||
* fail remaining messages unless `skipGroupOnError` is true | ||
* | ||
* @param failureMessages - The list of failure messages. | ||
* @param options - The options for the batch processing. | ||
*/ | ||
public shouldShortCircuit( | ||
failureMessages: EventSourceDataClassTypes[], | ||
options?: BatchProcessingOptions | ||
): boolean { | ||
return !options?.skipGroupOnError && failureMessages.length !== 0; | ||
} | ||
|
||
/** | ||
* Determines whether the current group should be skipped. | ||
* | ||
* If `skipGroupOnError` is true and the current group has previously failed, | ||
* then we should skip processing the current group. | ||
* | ||
* @param options - The options for the batch processing. | ||
*/ | ||
public shouldSkipCurrentGroup(options?: BatchProcessingOptions): boolean { | ||
return ( | ||
(options?.skipGroupOnError ?? false) && | ||
this.#currentGroupId && | ||
this.#failedGroupIds.has(this.#currentGroupId) | ||
); | ||
} | ||
|
||
/** | ||
* Handles failure for current group | ||
* Adds the current group ID to the set of failed group IDs if `skipGroupOnError` is true. | ||
* | ||
* @param options - The options for the batch processing. | ||
*/ | ||
public processFailureForCurrentGroup(options?: BatchProcessingOptions) { | ||
if (options?.skipGroupOnError && this.#currentGroupId) { | ||
this.addToFailedGroup(this.#currentGroupId); | ||
} | ||
} | ||
} | ||
|
||
export { SqsFifoProcessingUtils }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.