Architecture événementielle avec AWS Lambda & EventBridge
Ce tutoriel conçoit un pipeline événementiel sur Lambda et EventBridge qui résiste aux messages empoisonnés, aux tempêtes de retry, aux pics de trafic et à la latence de cold start, plutôt que de ne gérer que le chemin heureux.
EventBridge : router par pattern, pas par fonction
Ne mettez pas de logique métier dans la règle et n'envoyez pas chaque événement vers une seule Lambda généraliste. Définissez des patterns d'événements étroits pour que chaque consommateur ne voie que ce qu'il est censé traiter.
{
"source": ["com.acme.orders"],
"detail-type": ["OrderCompleted"],
"detail": {
"status": ["completed"],
"customerId": [{ "exists": true }]
}
}Cette simple vérification exists empêche un payload à customerId manquant d'atteindre le consommateur : il est soit intercepté plus tôt dans le pipeline, soit routé vers une règle séparée « événement malformé » qui alerte un humain. Le pattern matching est gratuit ; utilisez-le comme couche de validation autant que comme routeur.
Concurrence Lambda : réservée et provisionnée, délibérément
Deux réglages comptent ici : la concurrence réservée plafonne le nombre d'exécutions concurrentes qu'une fonction peut consommer, ce qui empêche une source d'événements bruyante d'affamer toutes les autres fonctions de votre compte sur le pool de concurrence régional partagé ; la concurrence provisionnée maintient un nombre défini d'environnements d'exécution « chauds », éliminant les cold starts pour cette part du trafic.
resource "aws_lambda_function" "order_processor" {
function_name = "order-processor"
runtime = "nodejs20.x"
handler = "index.handler"
memory_size = 512
timeout = 30
reserved_concurrent_executions = 50
}
resource "aws_lambda_provisioned_concurrency_config" "order_processor_warm" {
function_name = aws_lambda_function.order_processor.function_name
qualifier = aws_lambda_function.order_processor.version
provisioned_concurrent_executions = 10
}La concurrence provisionnée est facturée qu'elle soit invoquée ou non. Réservez-la à la part du fan-out d'événements exposée au client, là où les 2-3 secondes de cold start sont inacceptables, et laissez les consommateurs batch/arrière-plan démarrer à froid librement.
SQS comme tampon : de la contre-pression
Câbler EventBridge directement sur Lambda signifie que le scaling de la concurrence Lambda est dicté par le débit de rafale de la source d'événements. Ajoutez une file SQS entre le bus et la fonction, et vous obtenez trois choses que l'invocation directe ne vous donne pas : un tampon qui absorbe les pics, du batching, et un emplacement pour attacher une DLQ.
Lambda consomme désormais la file via un event source mapping, avec ses propres contrôles de concurrence et de batching :
resource "aws_cloudwatch_event_target" "to_queue" {
rule = aws_cloudwatch_event_rule.order_completed.name
arn = aws_sqs_queue.order_buffer.arn
}
resource "aws_lambda_event_source_mapping" "queue_to_lambda" {
event_source_arn = aws_sqs_queue.order_buffer.arn
function_name = aws_lambda_function.order_processor.arn
batch_size = 10
maximum_batching_window_in_seconds = 5
scaling_config {
maximum_concurrency = 20
}
}maximum_concurrency sur l'event source mapping est un second seuil, plus granulaire, que la concurrence réservée sur la fonction elle-même. Utilisez-le quand une fonction est alimentée par plusieurs files et que vous voulez des limites par file.
La DLQ : là où les événements empoisonnés vont mourir tranquillement
Plutôt que de retenter indéfiniment un événement irrécupérable (ou, pire, de le perdre silencieusement une fois la fenêtre de retry d'EventBridge épuisée), routez-le vers une dead-letter queue après un nombre borné de tentatives. Quelqu'un, ou une alarme, regarde plutôt ce qu'il y a dans cette file.
{
"deadLetterTargetArn": "arn:aws:sqs:eu-west-1:111122223333:order-processor-dlq",
"maxReceiveCount": 5
}Attachez cette redrive policy à la file source (order_buffer ci-dessus). Configurez séparément la destination on_failure de la fonction Lambda pour les échecs qui surviennent après que l'événement a quitté SQS, à l'intérieur du propre mécanisme de retry de la fonction, les deux mécanismes couvrent des points différents du pipeline, et vous voulez généralement les deux.
Handlers idempotents : le prix de la livraison « au moins une fois »
EventBridge et SQS garantissent tous deux une livraison « au moins une fois », jamais exactement une fois, combiné aux retries de Lambda, n'importe quel événement peut atteindre votre handler deux, trois fois ou plus. Si « traiter cette commande » signifie « débiter cette carte », une livraison dupliquée non dédupliquée est un débit dupliqué.
La solution est une table de déduplication indexée sur la clé d'idempotence propre à l'événement (j'utilise l'ID d'événement EventBridge ou une clé métier comme ID de commande + statut, celle qui reste stable à travers les retries) :
{
"TableName": "processed-events",
"KeySchema": [{ "AttributeName": "eventId", "KeyType": "HASH" }],
"TimeToLiveSpecification": {
"AttributeName": "expiresAt",
"Enabled": true
}
}Écrivez dans cette table avec un PutItem conditionnel (attribute_not_exists(eventId)) avant de faire quoi que ce soit d'irréversible. Si l'écriture échoue parce que l'élément existe déjà, cet événement a déjà été traité, renvoyez un succès sans réexécuter l'effet de bord. Le TTL empêche la table de croître indéfiniment ; quelques jours suffisent généralement compte tenu des fenêtres de retry d'EventBridge/SQS.
Les clés d'idempotence comptent surtout pour tout ce qui a un effet de bord externe : paiements, emails, décréments de stock. Les opérations de lecture/transformation/écriture purement internes à votre propre base de données sont souvent déjà naturellement idempotentes ; ne construisez pas de table de déduplication pour celles-ci.
Le chemin complet, de bout en bout
EventBridge, SQS, les limites de concurrence Lambda, les écritures conditionnelles, vous avez probablement déjà utilisé chacun individuellement. Ce qui distingue un pipeline qui encaisse une mauvaise semaine de production d'un pipeline qui réveille quelqu'un à 2h du matin, c'est d'avoir les quatre en place avant l'arrivée du premier événement empoisonné.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez