Files
netris-nestri/packages/functions/src/queues/retry.ts
Wanjohi e67a8d2b32 feat: Upgrade to asynchronous event bus with retry queue and backoff strategy (#290)
## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a retry and dead-letter queue system for more robust event
processing.
- Added a retry handler for processing failed Lambda invocations with
exponential backoff.
- Enhanced event handling to support retry logic and improved error
management.

- **Refactor**
- Replaced SQS-based library event processing with an event bus-based
approach.
- Updated event names and structure for improved clarity and
consistency.
  - Removed legacy library queue and related infrastructure.

- **Chores**
  - Updated dependencies to include the AWS Lambda client.
  - Cleaned up unused code and removed deprecated event handling logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-04 07:53:30 +03:00

93 lines
2.6 KiB
TypeScript

import { Resource } from "sst";
import type { SQSHandler } from "aws-lambda";
import {
SQSClient,
SendMessageCommand
} from "@aws-sdk/client-sqs";
import {
LambdaClient,
InvokeCommand,
GetFunctionCommand,
ResourceNotFoundException,
} from "@aws-sdk/client-lambda";
const lambda = new LambdaClient({});
lambda.middlewareStack.remove("recursionDetectionMiddleware");
const sqs = new SQSClient({});
sqs.middlewareStack.remove("recursionDetectionMiddleware");
export const handler: SQSHandler = async (evt) => {
for (const record of evt.Records) {
const parsed = JSON.parse(record.body);
console.log("body", parsed);
const functionName = parsed.requestContext.functionArn
.replace(":$LATEST", "")
.split(":")
.pop();
if (parsed.responsePayload) {
const attempt = (parsed.requestPayload.attempts || 0) + 1;
const info = await lambda.send(
new GetFunctionCommand({
FunctionName: functionName,
}),
);
const max =
Number.parseInt(
info.Configuration?.Environment?.Variables?.RETRIES || "",
) || 0;
console.log("max retries", max);
if (attempt > max) {
console.log(`giving up after ${attempt} retries`);
// send to dlq
await sqs.send(
new SendMessageCommand({
QueueUrl: Resource.Dlq.url,
MessageBody: JSON.stringify({
requestPayload: parsed.requestPayload,
requestContext: parsed.requestContext,
responsePayload: parsed.responsePayload,
}),
}),
);
return;
}
const seconds = Math.min(Math.pow(2, attempt), 900);
console.log(
"delaying retry by ",
seconds,
"seconds for attempt",
attempt,
);
parsed.requestPayload.attempts = attempt;
await sqs.send(
new SendMessageCommand({
QueueUrl: Resource.RetryQueue.url,
DelaySeconds: seconds,
MessageBody: JSON.stringify({
requestPayload: parsed.requestPayload,
requestContext: parsed.requestContext,
}),
}),
);
}
if (!parsed.responsePayload) {
console.log("triggering function");
try {
await lambda.send(
new InvokeCommand({
InvocationType: "Event",
Payload: Buffer.from(JSON.stringify(parsed.requestPayload)),
FunctionName: functionName,
}),
);
} catch (e) {
if (e instanceof ResourceNotFoundException) {
return;
}
throw e;
}
}
}
}