Comprendre le Visibility Timeout SQS : Pourquoi vos messages sont traités deux fois
Vous observez des doublons dans votre base de données, des emails envoyés deux fois, ou des transactions dupliquées — et en remontant la chaîne, vous trouvez SQS. Le Visibility Timeout SQS est le mécanisme central qui empêche plusieurs consommateurs de traiter le même message simultanément, et mal le configurer est l'une des causes les plus fréquentes de traitement en double en production.
TL;DR — Visibility Timeout SQS en un coup d'œil
| Aspect | Détail |
|---|---|
| Définition | Durée pendant laquelle un message est invisible dans la file après avoir été reçu par un consommateur |
| Valeur par défaut | 30 secondes |
| Plage configurée | 0 seconde à 12 heures |
| Cause principale de doublon | Timeout expiré avant la fin du traitement → message redevient visible |
| Recommandation Lambda | Visibility Timeout ≥ 6× le timeout de la fonction Lambda |
| Correction en cours de traitement | ChangeMessageVisibility pour prolonger dynamiquement |
| Suppression correcte | DeleteMessage obligatoire après traitement réussi |
Comment fonctionne le Visibility Timeout SQS
SQS n'est pas un broker de messages classique avec un mécanisme d'acquittement explicite. Quand un consommateur appelle ReceiveMessage, le message n'est pas supprimé — il est simplement masqué pendant une durée déterminée. Si le consommateur ne supprime pas le message avant l'expiration de ce délai, SQS le rend à nouveau visible à tous les consommateurs. C'est le Visibility Timeout.
Ce comportement est intentionnel : il garantit qu'un message n'est pas perdu si un consommateur tombe en panne pendant le traitement. Mais il crée une fenêtre de risque si le traitement dure plus longtemps que prévu.
(Visibility Timeout démarre) alt Traitement terminé dans le délai C1->>SQS: DeleteMessage(receipt_handle) SQS-->>C1: Suppression confirmée Note over SQS: Message supprimé définitivement else Timeout expiré avant suppression Note over SQS: Timeout expiré —
message redevient visible C2->>SQS: ReceiveMessage() SQS-->>C2: même message (doublon) C1->>SQS: DeleteMessage(ancien receipt_handle) C2->>SQS: DeleteMessage(nouveau receipt_handle) Note over SQS: Deux traitements
pour un seul message end
- ReceiveMessage : Le consommateur A récupère le message. Le Visibility Timeout démarre immédiatement.
- Message invisible : Pendant la durée du timeout, aucun autre consommateur ne voit le message dans la file.
- Chemin nominal : Le consommateur A termine le traitement et appelle
DeleteMessage. Le message est supprimé définitivement. - Chemin d'échec : Le timeout expire avant la suppression. Le message redevient visible. Le consommateur B le récupère — doublon.
Pensez au Visibility Timeout comme à un verrou optimiste sur un enregistrement de base de données : il ne bloque pas les autres, il leur dit juste 'd'attendre'. Si vous ne confirmez pas à temps, le verrou saute et quelqu'un d'autre prend la main.
Diagnostic : Pourquoi vos messages SQS sont traités deux fois
Avant de toucher à la configuration, il faut identifier la cause réelle. Il y a trois scénarios distincts qui produisent des doublons, et chacun a une correction différente.
Event Source Mapping ?"} Q3{"Durée traitement variable ?"} FIX1["Augmenter Visibility Timeout
= P99 durée traitement × 1.5"] FIX2["Visibility Timeout
= 6 × timeout Lambda"] FIX3["Implémenter ChangeMessageVisibility
toutes les N secondes"] FIX4["Vérifier DeleteMessage appelé
après chaque traitement réussi"] DLQ["Configurer Dead Letter Queue
maxReceiveCount adapté"] START --> Q1 Q1 -- Oui --> Q2 Q1 -- Non --> FIX4 Q2 -- Oui --> FIX2 Q2 -- Non --> Q3 Q3 -- Oui --> FIX3 Q3 -- Non --> FIX1 FIX1 --> DLQ FIX2 --> DLQ FIX3 --> DLQ
Étape 1 — Mesurer la durée réelle de traitement
La première question est simple : combien de temps votre consommateur met-il réellement à traiter un message ? Si vous ne le savez pas avec précision, vous ne pouvez pas configurer le timeout correctement. Commencez par inspecter les métriques CloudWatch de votre file.
aws cloudwatch get-metric-statistics \
--namespace AWS/SQS \
--metric-name ApproximateAgeOfOldestMessage \
--dimensions Name=QueueName,Value=ma-file-production \
--start-time 2024-01-15T00:00:00Z \
--end-time 2024-01-15T23:59:59Z \
--period 300 \
--statistics Maximum \
--region us-east-1
Cette métrique vous indique l'âge du message le plus ancien dans la file. Une valeur élevée suggère que des messages restent bloqués — souvent parce que le traitement dépasse le Visibility Timeout et que les messages sont re-livrés en boucle. Vérifiez également la configuration actuelle de votre file :
aws sqs get-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-production \
--attribute-names VisibilityTimeout,MessageRetentionPeriod,RedrivePolicy \
--region us-east-1
Notez la valeur de VisibilityTimeout retournée. Comparez-la avec la durée moyenne de traitement observée dans vos logs applicatifs.
Étape 2 — Vérifier le nombre de réceptions par message
Chaque message SQS expose un attribut ApproximateReceiveCount. Si ce compteur dépasse 1 pour des messages qui devraient être traités une seule fois, le Visibility Timeout est expiré au moins une fois pendant le traitement. C'est la preuve directe du problème.
aws sqs receive-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-production \
--attribute-names ApproximateReceiveCount \
--max-number-of-messages 1 \
--region us-east-1
Un ApproximateReceiveCount supérieur à 1 sur des messages en cours de traitement normal confirme que le timeout expire avant la suppression. Si vous voyez des valeurs élevées (5, 10, 20), le problème est systémique — probablement un timeout configuré bien en dessous de la durée réelle de traitement.
Étape 3 — Corriger le Visibility Timeout de la file
Une fois la durée réelle de traitement établie, ajustez le Visibility Timeout avec une marge suffisante. La règle pratique : prenez le percentile 99 de votre durée de traitement et multipliez par 1,5 minimum. Cela absorbe les variations de charge sans laisser des messages bloqués trop longtemps en cas de panne consommateur.
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-production \
--attributes VisibilityTimeout=300 \
--region us-east-1
Ce changement prend effet immédiatement pour les nouveaux appels ReceiveMessage. Les messages déjà en cours de traitement conservent leur timeout initial.
Étape 4 — Prolonger dynamiquement le timeout pour les traitements longs
Configurer un Visibility Timeout statique élevé a un coût : si un consommateur tombe en panne, le message reste invisible pendant toute la durée configurée avant d'être re-livré. Pour les traitements dont la durée est variable, la bonne approche est de prolonger le timeout dynamiquement pendant le traitement avec ChangeMessageVisibility.
Le principe : démarrez avec un timeout raisonnable (60 secondes), et toutes les 45 secondes, si le traitement est toujours en cours, appelez ChangeMessageVisibility pour le prolonger. Si le consommateur tombe en panne, il ne renouvelle plus le timeout, et le message redevient visible après l'expiration naturelle.
aws sqs change-message-visibility \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-production \
--receipt-handle AQEBwJnKyrHigUMZj6reyNurzbVsnYVFKJQybn5zDkzkXG7... \
--visibility-timeout 60 \
--region us-east-1
Le receipt-handle est retourné par ReceiveMessage et est unique par tentative de réception. Vous ne pouvez prolonger le timeout que d'un message que vous avez vous-même reçu — un autre consommateur ne peut pas modifier le timeout d'un message qu'il n'a pas récupéré.
Étape 5 — Vérifier le comportement avec Lambda et les Event Source Mappings
Si votre consommateur est une fonction Lambda déclenchée via un Event Source Mapping SQS, la configuration du Visibility Timeout suit une règle spécifique documentée par AWS : le Visibility Timeout de la file doit être d'au moins 6 fois le timeout configuré pour la fonction Lambda.
Cette marge de 6× n'est pas arbitraire. Le service Lambda peut rencontrer du throttling interne et doit pouvoir effectuer plusieurs tentatives de traitement du lot de messages sans que le Visibility Timeout expire entre-temps. Si la marge est insuffisante, Lambda peut tenter de retraiter un lot dont les messages sont déjà redevenus visibles — produisant exactement les doublons que vous observez. Ce comportement est indépendant du nombre de tentatives configuré dans l'Event Source Mapping lui-même.
aws sqs get-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-lambda \
--attribute-names VisibilityTimeout \
--region us-east-1
aws lambda get-function-configuration \
--function-name ma-fonction-traitement \
--region us-east-1 \
--query 'Timeout'
Si votre fonction Lambda a un timeout de 30 secondes, le Visibility Timeout de la file doit être d'au moins 180 secondes (6 × 30). Vérifiez et corrigez si nécessaire :
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-lambda \
--attributes VisibilityTimeout=180 \
--region us-east-1
Étape 6 — Configurer une Dead Letter Queue pour isoler les messages problématiques
Même avec un Visibility Timeout correctement dimensionné, certains messages échoueront systématiquement — payload malformé, dépendance externe indisponible, bug applicatif. Sans Dead Letter Queue, ces messages tournent indéfiniment dans la file principale, consomment des ressources, et génèrent des doublons à chaque cycle. Configurez un maxReceiveCount adapté à votre tolérance aux erreurs transitoires.
aws sqs create-queue \
--queue-name ma-file-production-dlq \
--region us-east-1
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/ma-file-production \
--attributes '{"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:123456789012:ma-file-production-dlq\",\"maxReceiveCount\":\"5\"}" }' \
--region us-east-1
Avec maxReceiveCount=5, un message qui échoue 5 fois est déplacé vers la DLQ au lieu de continuer à polluer la file principale. Cela réduit le bruit et vous permet d'inspecter les messages problématiques séparément.
Le piège classique : symptôme de doublon, mauvais diagnostic
En production, voici ce qui se passe réellement. L'alerte arrive : des commandes sont créées deux fois. L'équipe regarde les logs applicatifs — pas d'erreur visible, pas d'exception. Le premier réflexe est de chercher un bug dans le code de traitement, peut-être un appel API dupliqué.
Après deux heures d'investigation, on finit par regarder le ApproximateReceiveCount des messages en cours. Valeur : 3. Le Visibility Timeout est à 30 secondes. La durée réelle de traitement — qui inclut un appel à une API tierce avec un timeout de 45 secondes — est de 50 à 70 secondes en charge normale.
Le message expire à 30 secondes, redevient visible, un second consommateur le récupère et commence le traitement pendant que le premier termine le sien. Les deux appellent DeleteMessage avec leur propre receipt-handle — la suppression réussit pour les deux, mais la commande a déjà été créée deux fois.
La correction : Visibility Timeout porté à 120 secondes, et ajout d'une clé d'idempotence côté base de données. Le timeout corrige le symptôme immédiat ; l'idempotence protège contre les cas résiduels que vous ne contrôlez pas côté SQS.
Le Visibility Timeout ne garantit pas l'exactly-once delivery. SQS est un système at-least-once par conception. La protection définitive contre les doublons reste l'idempotence applicative.
IAM — Permissions nécessaires pour gérer le Visibility Timeout
Les opérations de diagnostic et de correction décrites dans ce post nécessitent les permissions suivantes. Appliquez le principe du moindre privilège : les consommateurs n'ont pas besoin de sqs:SetQueueAttributes en production.
🔽 Politique IAM minimale — cliquer pour développer
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SQSConsumerOperations",
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:ChangeMessageVisibility",
"sqs:GetQueueAttributes"
],
"Resource": "arn:aws:sqs:us-east-1:123456789012:ma-file-production"
},
{
"Sid": "SQSAdminOperations",
"Effect": "Allow",
"Action": [
"sqs:SetQueueAttributes",
"sqs:CreateQueue"
],
"Resource": "arn:aws:sqs:us-east-1:123456789012:ma-file-production"
},
{
"Sid": "CloudWatchReadMetrics",
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricStatistics"
],
"Resource": "*"
}
]
}
Conclusion et prochaines étapes — Maîtriser le Visibility Timeout SQS
Le Visibility Timeout SQS est un paramètre simple en apparence, mais son interaction avec la durée réelle de traitement, les pannes consommateurs, et les intégrations Lambda en fait l'une des sources de bugs les plus difficiles à diagnostiquer en production. Les points clés à retenir :
- Mesurez la durée réelle de traitement au percentile 99 avant de configurer le timeout.
- Pour Lambda avec Event Source Mapping, appliquez la règle des 6× le timeout de la fonction.
- Utilisez
ChangeMessageVisibilitypour les traitements à durée variable. - Configurez une Dead Letter Queue pour isoler les messages en échec systématique.
- Implémentez l'idempotence applicative — SQS garantit at-least-once, pas exactly-once.
Consultez la documentation officielle AWS sur le Visibility Timeout et la configuration des Dead Letter Queues pour approfondir.
Glossaire
| Terme | Définition |
|---|---|
| Visibility Timeout | Durée pendant laquelle un message SQS est invisible après avoir été reçu par un consommateur. Si le message n'est pas supprimé avant l'expiration, il redevient visible. |
| Receipt Handle | Identifiant unique retourné par ReceiveMessage, nécessaire pour DeleteMessage et ChangeMessageVisibility. Propre à chaque tentative de réception. |
| ApproximateReceiveCount | Attribut de message indiquant combien de fois il a été reçu. Une valeur supérieure à 1 indique des re-livraisons. |
| Dead Letter Queue (DLQ) | File SQS de destination pour les messages qui ont dépassé le nombre maximal de tentatives de traitement (maxReceiveCount). |
| Event Source Mapping | Configuration Lambda qui connecte une file SQS à une fonction Lambda, gérant automatiquement la réception et la suppression des messages. |
Commentaires
Enregistrer un commentaire