Surveillance18 octobre 2026 11 min de lecture

Comment surveiller un pipeline SQS → Lambda → DynamoDB sans écrire de code

Une file, une fonction et une table peuvent tomber en panne de cinq façons différentes, et certaines ne laissent aucune trace dans les métriques. Nous voyons quel signal trahit chaque panne, comment surveiller les trois ressources avec AAA Sentinel sans toucher à votre code, et comment un flux de test avec un traceId vous indique l'étape exacte où la chaîne se rompt.

Cet article est aussi disponible en:esenpt

Une commande arrive dans une file SQS, une fonction Lambda la traite et le résultat est enregistré dans DynamoDB. C'est l'un des schémas les plus courants sur AWS, et l'un de ceux qui tombent en panne le plus discrètement : la file accepte des messages même quand personne ne les lit, la fonction réessaie jusqu'à ce que quelque chose passe, et la table ne se plaint jamais de ce qu'elle n'a pas reçu. Dans cet article, nous passons en revue la façon dont cette chaîne tombe en panne, le signal qui trahit chaque panne et la manière de la surveiller avec AAA Sentinel, étape par étape, avec des noms d'exemple à remplacer par les vôtres.

Le pipeline de l'exemple

Nous prenons une boutique fictive avec ce pipeline dans le compte 123456789012, région eu-west-1 :

Pipeline d'exemple

File d'entrée
orders-queue, avec redrive vers la DLQ après 5 réceptions
File de messages en échec (DLQ)
orders-dlq
Fonction
process-order, reliée à la file par un event source mapping
Table
orders, avec la clé de partition orderId (texte)

Comment un tel pipeline tombe en panne

Presque tous les incidents de cette chaîne entrent dans l'un de ces cinq cas :

  • Les messages s'accumulent : la fonction est arrêtée, l'event source mapping est désactivé ou la concurrence réservée est restée à zéro. La file continue d'accepter des commandes et personne ne les traite.
  • Messages empoisonnés : un message que la fonction ne sait pas traiter échoue à chaque tentative. Une fois le délai de visibilité écoulé, il revient dans la file, et quand il atteint le nombre maximal de réceptions de la redrive policy, il finit dans la DLQ. Si la fonction traite des lots sans signaler les échecs message par message (ReportBatchItemFailures), un seul mauvais message fait réessayer tout le lot.
  • Erreurs et limitations de la fonction : exceptions, délais dépassés ou throttling faute de concurrence. Un pic d'erreurs se remarque en général ; 3 % en continu, non.
  • Des tentatives qui masquent la panne : le message échoue deux fois et passe à la troisième. Il n'arrive jamais dans la DLQ et le taux d'erreurs ne se remarque pas, mais la commande est enregistrée avec plusieurs minutes de retard.
  • Des éléments jamais écrits : la fonction se termine bien, mais n'écrit rien. Une variable d'environnement qui pointe vers une autre table, une écriture conditionnelle abandonnée, une exception interceptée qui ne laisse qu'une ligne de log. Aucune métrique ne bouge.

Le signal qui trahit chaque panne

CloudWatch publie déjà presque tout ce qu'il faut. Voici le signal qui détecte le mieux chaque cas :

Panne → signal

Messages accumulés
SQS ApproximateAgeOfOldestMessage sur orders-queue. Mieux que compter les messages : le nombre monte avec la charge, l'ancienneté monte quand personne ne consomme
Messages empoisonnés
ApproximateNumberOfMessagesVisible sur orders-dlq supérieur à zéro. Un message à cet endroit a déjà épuisé toutes ses tentatives
Erreurs de la fonction
Lambda Errors rapporté à Invocations pour process-order, en pourcentage
Limitations de la fonction
Lambda Throttles pour process-order
Fonction lente
Lambda Duration p95 proche du timeout configuré : quand il l'atteindra, chaque tentative deviendra une erreur
Table saturée
DynamoDB ReadThrottleEvents et WriteThrottleEvents sur orders
Éléments jamais écrits et tentatives qui retardent
Aucune métrique : on ne le voit qu'en envoyant un test et en vérifiant qu'il arrive au bout

Les métriques disent que chaque pièce est allumée, pas que la chaîne fonctionne. Une file vide, une fonction sans erreurs et une table sans throttling sont compatibles avec zéro commande enregistrée.

Étape 1 : surveiller la file, la DLQ, la fonction et la table

Cette partie ne demande aucun code. Dans Monitoring → Nouveau → Moniteur, choisissez le type Ressource cloud, le compte déjà connecté, la région et la ressource dans la liste (ou collez son ARN). Si le compte n'accorde pas encore l'accès en lecture de Monitoring, le formulaire l'indique et propose « Accorder l'autorisation ». AAA Sentinel lit les métriques avec ce rôle en lecture seule, sans agent. Trois moniteurs couvrent les quatre ressources :

  • orders-queue (File SQS) : considérée en panne s'il y a un message dans sa file de messages en échec ou si le plus ancien message attend depuis plus que l'ancienneté maximale (60 minutes si vous laissez le champ vide). La DLQ n'a pas besoin de son propre moniteur : celui de la file la trouve dans la redrive policy et lit son nombre de messages directement dans SQS, sans attendre CloudWatch.
  • process-order (Fonction Lambda) : considérée en panne si 10 % ou plus de ses exécutions échouent ou sont limitées (avec au moins 10), ou si toutes échouent alors qu'il y en a entre 3 et 9. Le pourcentage se règle dans « Taux d'échec (%) ».
  • orders (Table DynamoDB) : considérée en panne si elle rejette par capacité plus de 10 requêtes sur la période, lectures et écritures confondues. Le seuil se règle dans « Rejets par capacité tolérés » ; sur une table à la demande peu sollicitée, vous pouvez le descendre à 0.

Le moniteur de la fonction regarde les erreurs, les limitations et, pour les sources de type stream, l'ancienneté de l'itérateur ; il ne regarde pas la durée. Si la p95 de Duration compte pour vous en soi, une alarme CloudWatch est le bon outil. Pour ce que ressent le client, le temps de bout en bout, il y a le flux de l'étape suivante.

Étape 2 : ce que fait un flux

Un flux vérifie la chaîne entière avec une transaction de test. Toutes les 15 minutes au plus, AAA Sentinel envoie un message dans orders-queue avec son propre traceId (32 caractères hexadécimaux) et vérifie qu'il atteint chaque étape dans son délai. Le message porte le traceId dans l'attribut de message x-aaa-trace et dans le corps sous aaaTraceId, avec un traceparent W3C ; sur une file FIFO, le test part dans son propre groupe de messages. Les étapes possibles sont une ligne dans CloudWatch Logs, un élément DynamoDB, un objet S3 ou un callback que votre code fait vers une URL à nous, jusqu'à cinq. Pour ce pipeline, nous en utilisons deux : la ligne dans le log de process-order et l'élément dans orders.

Étape 3 : préparer la fonction (ici, quelques lignes)

Soyons honnêtes avec le titre : surveiller les ressources ne demande pas de code, mais le flux a besoin que la fonction reconnaisse le test. Le test parcourt votre vraie chaîne : votre code doit donc le traiter sans effet réel (ni paiement, ni e-mail, ni commande) et laisser deux traces que nous pouvons retrouver :

Node.js avec @aaa-sentinel/healthjavascript
// npm install @aaa-sentinel/health
import { propagateTrace } from '@aaa-sentinel/health'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'

const db = DynamoDBDocumentClient.from(new DynamoDBClient({}))

export const handler = async (event) => {
  for (const record of event.Records) {
    const trace = propagateTrace(record)
    if (trace) {
      trace.log('order received')
      // Test AAA Sentinel : ni paiement ni e-mail, et le traceId dans la clé pour que l'étape DynamoDB le trouve.
      await db.send(new PutCommand({
        TableName: process.env.ORDERS_TABLE,
        Item: {
          orderId: `aaa-test-${trace.traceId}`,
          ...trace.fields,
          // Attribut TTL de la table : DynamoDB supprime seul les éléments de test.
          expiresAt: Math.floor(Date.now() / 1000) + 86400,
        },
      }))
      continue
    }
    // Ici continue votre vraie logique de commande.
  }
}
Python, sans dépendancepython
import json, os, time
import boto3

table = boto3.resource("dynamodb").Table(os.environ["ORDERS_TABLE"])

def handler(event, context):
    for record in event["Records"]:
        attribute = record.get("messageAttributes", {}).get("x-aaa-trace", {})
        trace_id = attribute.get("stringValue")
        if trace_id:
            print(json.dumps({"msg": "order received", "traceId": trace_id}))
            # Test AAA Sentinel : aucun effet réel, et le traceId dans la clé de l'élément.
            table.put_item(Item={"orderId": f"aaa-test-{trace_id}", "aaaTraceId": trace_id, "expiresAt": int(time.time()) + 86400})
            continue
        # Ici continue votre vraie logique de commande.
  • Une ligne dans son log avec le traceId. Si la fonction écrit déjà dans le log l'événement ou le corps du message, le traceId y apparaît et il n'y a rien à changer pour cette étape.
  • Un élément dont la clé primaire contient le traceId. L'étape DynamoDB ne cherche pas par attribut : elle fait un GetItem en lecture cohérente sur la clé que vous définissez, avec {traceId} remplacé par celui de chaque test, et ne demande que les attributs de la clé, pas le contenu de l'élément. La clé doit donc être un texte qui inclut le traceId (ici, orderId = aaa-test-{traceId}) ; le stocker seulement dans un attribut ne suffit pas, et les index secondaires ne sont pas consultés.

propagateTrace lit le traceId de n'importe quel événement (SQS, SNS, EventBridge, DynamoDB Streams, HTTP) et renvoie les en-têtes, les attributs de message et les champs pour le transmettre à l'étape suivante ; trace.log écrit une ligne JSON qui se termine par le traceId. Activez le TTL de la table sur expiresAt pour que les éléments de test ne s'accumulent pas.

Étape 4 : créer le flux dans l'assistant

Dans Monitoring → Nouveau → Flux, l'assistant compte trois étapes :

Flux d'exemple · valeurs

Ce qui lance le flux
File SQS · arn:aws:sqs:eu-west-1:123456789012:orders-queue
Contenu du test
{"type": "order", "test": true}
Étape 1 · Ligne de log
arn:aws:logs:eu-west-1:123456789012:log-group:/aws/lambda/process-order:* · texte {traceId} · délai 60 s
Étape 2 · Élément DynamoDB
Table orders · orderId = aaa-test-{traceId} · délai 120 s
Fréquence
Toutes les 15 minutes
  • Où commence la chaîne : choisissez le compte, la région et, dans « Ce qui lance le flux », File SQS ; collez l'ARN d'orders-queue et cliquez sur « Découvrir la chaîne ». Seule la configuration est lue ; aucun test n'est encore envoyé. AAA Sentinel suit l'event source mapping jusqu'à process-order et propose une étape de logs par fonction, avec le texte {traceId} et un délai de 60 secondes.
  • Ce qui est vérifié : relisez l'étape de logs et ajoutez la dernière dans « Ajouter la dernière étape » → Élément DynamoDB. La liste place en tête les tables citées dans la configuration de la fonction (« apparaît dans votre fonction ») : si ORDERS_TABLE pointe vers orders, vous la verrez en haut. Dans « Clé de l'élément », saisissez orderId et aaa-test-{traceId}. Dans « Contenu du test », vous pouvez ajouter ce dont votre code a besoin pour ne pas écarter le message.
  • Nom et alertes : le nom, la sévérité et la fréquence des tests, au minimum toutes les 15 minutes, car chaque test écrit dans votre chaîne et fait une vingtaine de lectures dans votre compte.

Chaque délai se compte depuis le déclencheur, pas depuis l'étape précédente, va de 10 à 900 secondes et ne peut pas être inférieur à celui de l'étape précédente. Avec 60 et 120 secondes, la commande a deux minutes pour être dans la table.

Étape 5 : le rôle de flux

Envoyer le message et lire la table ne se fait pas avec le rôle de lecture, mais avec un rôle à part, FinOpsSentinelMonitoringFlow, créé à partir d'un modèle CloudFormation depuis l'accès du compte. Ce rôle ne touche que les ressources que vous mettez dans ses paramètres, et l'assistant vous indique celles dont ce flux a besoin dans « Permissions dont votre rôle de flux a besoin » :

Paramètres du stack FinOpsSentinelMonitoringFlow

TriggerQueueArns
arn:aws:sqs:eu-west-1:123456789012:orders-queue
ObservedTableArns
arn:aws:dynamodb:eu-west-1:123456789012:table/orders
  • Pour l'étendre : ouvrez le stack dans CloudFormation, cliquez sur « Mettre à jour », choisissez « Utiliser le modèle actuel », ajoutez les ARN à ceux que chaque paramètre contient déjà, séparés par des virgules, et continuez jusqu'à « Envoyer ». Ne remplacez pas ceux qui y sont, sinon d'autres flux cesseront de fonctionner.
  • Si vous chiffrez la file avec une clé KMS gérée par vous, ajoutez aussi son ARN dans KmsKeyArns.
  • À chaque exécution, la session est en plus restreinte à ce que fait ce flux : sqs:SendMessage sur orders-queue et dynamodb:GetItem sur orders, même si le rôle permet davantage.
  • Les logs se lisent avec l'accès en lecture de Monitoring, là où le compte accorde logs:FilterLogEvents.

Comment lire le résultat

La fiche du flux montre le parcours du dernier test, avec son traceId, et une rangée de points par exécution récente. Chaque étape s'affiche comme « Arrivé à temps », « Arrivé en retard », « Pas arrivé » ou « Impossible à vérifier », avec le temps écoulé depuis la précédente. Si une étape échoue, elle est marquée comme le point de rupture et les suivantes indiquent « Non vérifié : la chaîne s'est rompue avant ». Trois résultats typiques pour ce pipeline :

  • Étape 1 à temps (+3 s) et étape 2 à temps (+1 s) : la chaîne fonctionne de bout en bout.
  • Étape 1 à temps et étape 2 « Pas arrivé » : la fonction a reçu la commande et ne l'a pas écrite. C'est le cas de l'élément jamais écrit, celui qu'aucune métrique ne voit. Vérifiez la variable d'environnement de la table et les exceptions interceptées.
  • Étape 1 « Pas arrivé » : le message n'a pas atteint la fonction, ou la fonction n'a pas écrit la ligne. Regardez le moniteur d'orders-queue (messages accumulés, DLQ) et celui de process-order (erreurs, throttling).
  • « Impossible à vérifier » n'est pas « Pas arrivé » : cela veut dire que nous n'avons pas pu regarder, par exemple parce que le rôle n'a pas l'autorisation sur la table. La fiche indique alors quel ARN manque dans chaque paramètre.

Le temps d'une ligne de log est son propre horodatage : il est exact, et si la ligne apparaît après son délai, l'étape s'affiche « Arrivé en retard ». Un élément DynamoDB n'a pas d'horodatage : son temps est celui du regard qui l'a trouvé, et les regards sont répartis sur le délai, environ six par étape. L'étape de logs attend en plus une minute après son délai, le temps que CloudWatch indexe une ligne.

Quand il alerte : apprentissage et confirmation

Un nouveau moniteur passe ses 24 premières heures à apprendre : il observe, affiche les résultats et n'alerte pas. Ensuite, l'état passe par suspect, en confirmation et en panne, et seul « en panne » ouvre un incident et alerte par e-mail, Slack, Microsoft Teams ou webhook, les mêmes canaux que les alertes de coûts. L'incident se ferme tout seul quand la ressource ou le flux se rétablit.

  • Moniteurs de ressource : deux lectures mauvaises d'affilée déclarent la panne et une bonne la clôt. Ils lisent toutes les 10 minutes par défaut, toutes les 5 au plus souvent.
  • Flux : trois tests en échec d'affilée déclarent la panne et deux réussis la clôturent. Avec un test toutes les 15 minutes, l'alerte arrive 30 à 45 minutes après la rupture de la chaîne ; un test qui échoue une fois ne réveille personne.
  • La confirmation depuis une deuxième région (les sondes partent d'eu-west-1 et confirment depuis eu-central-1) concerne les moniteurs HTTP, où elle écarte un problème réseau. Pour une ressource ou un flux, recommencer depuis une autre région lirait les mêmes métriques du même compte : ce sont donc les résultats consécutifs qui confirment. Si votre pipeline a une API devant qui écrit dans orders-queue, un moniteur HTTP sur celle-ci bénéficie bien de la confirmation depuis la deuxième région.

Combien ça coûte

Les moniteurs de ressource et les flux font partie du pack Monitoring : 49 €/mois sur les plans Pro et Business (39 € à partir du quatrième pack), avec 25 moniteurs, 3 flux, une page d'état et 90 jours d'historique. Ce pipeline utilise trois moniteurs et un flux d'un seul pack. Les sondes tournent dans notre compte ; voici ce qui apparaît sur votre facture AWS :

  • La lecture des métriques avec GetMetricData : environ 0,20 USD au plus par ressource et par mois en lisant toutes les 10 minutes, soit environ 0,60 USD au plus pour les trois. Lire la DLQ, ce sont des appels SQS qui tiennent largement dans son offre gratuite d'un million de requêtes par mois.
  • Les tests du flux : toutes les 15 minutes, cela fait environ 2 900 par mois (si le flux épuise son plafond de coût du mois, par une chaîne cassée ou très longue et lente, on passe à un toutes les 6 heures jusqu'au 1er, et à ce rythme un incident met plus de temps à se clore), et chacun représente un message SQS, une invocation de process-order, une écriture dans orders et une vingtaine de lectures (GetItem et FilterLogEvents). C'est une très petite fraction de ce que génère le trafic réel, mais comptez-la si vos marges sont serrées.

Ce qu'il ne fait pas

Pour que vous sachiez où sont les limites :

  • Les flux sont réservés à AWS. Les moniteurs de ressource couvrent aussi Azure et Google Cloud.
  • Ce n'est pas un APM : il ne collecte pas vos logs et ne fait ni traçage distribué ni métriques personnalisées. Il cherche le traceId du test dans vos logs et votre table, et lit les métriques qu'AWS publie déjà.
  • L'étape DynamoDB a besoin du traceId dans la clé primaire, en texte, sur une ou deux clés. Si vous ne pouvez pas donner cette forme à l'élément de test, une étape callback ou un objet S3 sont les alternatives.
  • La découverte suit les files, les fonctions et leurs destinations ; si la chaîne passe par SNS, un bus d'événements ou Step Functions, ce qui se trouve derrière s'ajoute à la main.

Checklist pour démarrer

Avec ces éléments en place, la prochaine commande perdue vous est signalée par AAA Sentinel, pas par un client.

  • Le compte AWS connecté avec l'accès en lecture de Monitoring.
  • Un moniteur de ressource sur orders-queue (il couvre orders-dlq), un autre sur process-order et un autre sur orders.
  • La fonction reconnaît le test à son traceId, écrit la ligne et enregistre l'élément avec orderId = aaa-test-{traceId}, sans effet réel.
  • Le TTL activé sur la table pour les éléments de test.
  • Le rôle de flux avec orders-queue dans TriggerQueueArns et orders dans ObservedTableArns.
  • Le flux créé avec deux étapes et des délais de 60 et 120 secondes.
  • Au moins un canal d'alerte testé avant la fin des 24 heures d'apprentissage.