SQS Visibility Timeout verstehen: Warum Nachrichten doppelt verarbeitet werden
Eine Nachricht landet bei zwei verschiedenen Consumern gleichzeitig — das ist kein Bug im Anwendungscode, sondern meistens ein falsch kalibriertes Visibility Timeout in Amazon SQS. Wer diesen Mechanismus nicht genau versteht, baut sich eine Race Condition direkt in die Verarbeitungspipeline ein.
TL;DR: SQS Visibility Timeout auf einen Blick
| Aspekt | Detail |
|---|---|
| Was es ist | Zeitfenster, in dem eine abgerufene Nachricht für andere Consumer unsichtbar ist |
| Standardwert | 30 Sekunden |
| Konfigurierbarer Bereich | 0 Sekunden bis 12 Stunden |
| Typische Ursache für Doppelverarbeitung | Verarbeitungszeit überschreitet das Visibility Timeout |
| Lösung | Timeout an reale Verarbeitungsdauer anpassen oder ChangeMessageVisibility nutzen |
| Löschen der Nachricht | Muss explizit per DeleteMessage erfolgen — passiert nicht automatisch |
Wie SQS Visibility Timeout funktioniert
SQS ist kein klassischer Message Broker mit exklusivem Lock. Wenn ein Consumer eine Nachricht per ReceiveMessage abruft, wird sie nicht aus der Queue entfernt — sie wird nur temporär versteckt. Dieses Versteck-Fenster ist das Visibility Timeout. Läuft es ab, bevor der Consumer die Nachricht explizit löscht, taucht sie wieder auf und ist für jeden anderen Consumer sichtbar.
Das ist kein Fehler im Design — es ist eine bewusste Entscheidung für Resilienz. Wenn ein Consumer crasht, soll die Nachricht nicht verloren gehen. Aber genau dieser Mechanismus produziert Doppelverarbeitung, sobald die Verarbeitungszeit das Timeout überschreitet.
- ReceiveMessage: Consumer A ruft die Nachricht ab. Das Visibility Timeout startet sofort.
- Unsichtbar: Während des Timeouts ist die Nachricht für alle anderen Consumer verborgen.
- Timeout abgelaufen: Wenn Consumer A nicht rechtzeitig löscht, wird die Nachricht wieder sichtbar.
- Doppelverarbeitung: Consumer B ruft dieselbe Nachricht ab — parallele Verarbeitung beginnt.
- DeleteMessage: Nur ein expliziter Delete-Aufruf entfernt die Nachricht dauerhaft.
Warum das Visibility Timeout so oft falsch konfiguriert ist
Der Standardwert von 30 Sekunden klingt großzügig — bis die Verarbeitungslogik eine externe API aufruft, eine Datenbankabfrage ausführt oder ein großes S3-Objekt verarbeitet. In Produktionssystemen mit variablen Latenzen reichen 30 Sekunden oft nicht aus.
Das tückische daran: Der Fehler zeigt sich nicht sofort. Unter normaler Last verarbeitet Consumer A schnell genug. Erst bei erhöhter Last, langsamen Downstream-Services oder GC-Pausen überschreitet die Verarbeitungszeit das Timeout — und plötzlich verarbeiten zwei Consumer dieselbe Nachricht.
Das Visibility Timeout ist wie ein Parkticket: Solange du zurück bist, bevor es abläuft, passiert nichts. Aber wenn du zu spät kommst, steht jemand anderes auf deinem Parkplatz.
SQS Visibility Timeout konfigurieren und diagnostizieren
Aktuellen Timeout-Wert einer Queue prüfen
Bevor etwas geändert wird, den Ist-Zustand ermitteln. Das Attribut heißt VisibilityTimeout.
aws sqs get-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/meine-queue \
--attribute-names VisibilityTimeout
Visibility Timeout auf Queue-Ebene anpassen
Den Wert auf einen realistischen Puffer über der maximalen erwarteten Verarbeitungszeit setzen — nicht nur den Durchschnitt, sondern das 99. Perzentil der Verarbeitungsdauer als Orientierung nehmen.
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/meine-queue \
--attributes VisibilityTimeout=300
Visibility Timeout pro Nachricht verlängern (ChangeMessageVisibility)
Wenn die Verarbeitungsdauer nicht vorhersehbar ist — etwa bei abhängigen externen Calls — kann der Consumer das Timeout aktiv verlängern, während er noch arbeitet. Das ist die robustere Lösung für variable Workloads.
aws sqs change-message-visibility \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/meine-queue \
--receipt-handle "AQEB...receiptHandle..." \
--visibility-timeout 120
Der receipt-handle wird bei jedem ReceiveMessage-Aufruf zurückgegeben und ist pro Empfang eindeutig. Wichtig: Dieser Aufruf muss erfolgen, bevor das aktuelle Timeout abläuft — nicht danach.
Nachricht nach erfolgreicher Verarbeitung löschen
Das Löschen passiert nicht automatisch. Wer das vergisst, sieht jede Nachricht nach Ablauf des Timeouts erneut — auch wenn die Verarbeitung erfolgreich war.
aws sqs delete-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/meine-queue \
--receipt-handle "AQEB...receiptHandle..."
Diagnosepfad: Doppelverarbeitung systematisch eingrenzen
Das Symptom ist klar — zwei Consumer verarbeiten dieselbe Nachricht. Die Ursache ist meistens eine von drei: Timeout zu kurz, Delete fehlt, oder der Consumer gibt den Receipt Handle nicht korrekt weiter.
ChangeMessageVisibility nutzen"] C -- Nein --> E["DeleteMessage-Aufruf pruefen"] E --> F{"Delete wird aufgerufen?"} F -- Nein --> G["Delete nach Verarbeitung implementieren"] F -- Ja --> H["Receipt Handle korrekt?"] H -- Nein --> I["Handle aus aktuellem ReceiveMessage verwenden"] H -- Ja --> J["CloudWatch Metriken pruefen"] D --> K["Problem geloest"] G --> K I --> K
- ApproximateNumberOfMessagesNotVisible prüfen: Dieser CloudWatch-Metrikwert zeigt, wie viele Nachrichten aktuell 'in flight' sind. Ein dauerhaft hoher Wert deutet auf Nachrichten hin, die nicht rechtzeitig gelöscht werden.
- Verarbeitungszeit messen: Eigene Anwendungsmetriken oder X-Ray-Traces zeigen, ob die Verarbeitungsdauer das konfigurierte Timeout überschreitet.
- DeleteMessage-Aufrufe verifizieren: Sicherstellen, dass der Delete-Aufruf im Erfolgsfall tatsächlich ausgeführt wird — nicht nur im Happy Path, sondern auch nach Retries.
CloudWatch-Metrik abfragen
aws cloudwatch get-metric-statistics \
--namespace AWS/SQS \
--metric-name ApproximateNumberOfMessagesNotVisible \
--dimensions Name=QueueName,Value=meine-queue \
--start-time 2024-01-15T10:00:00Z \
--end-time 2024-01-15T11:00:00Z \
--period 300 \
--statistics Average
Erfahrung aus der Praxis: Die falsche Diagnose
Ein Team beobachtete Doppelverarbeitung nur unter Last — nie in der Staging-Umgebung. Die erste Vermutung war ein Bug im Consumer-Code: irgendwo wird ReceiveMessage zweimal aufgerufen. Stunden wurden damit verbracht, den Anwendungscode zu durchsuchen.
Die eigentliche Ursache: Das Visibility Timeout war auf 30 Sekunden gesetzt. Die Verarbeitung dauerte unter normaler Last 8-10 Sekunden — kein Problem. Unter Last, wenn ein nachgelagerter Datenbankcluster unter Druck geriet, stieg die Verarbeitungszeit auf 35-45 Sekunden. Das Timeout lief ab, die Nachricht wurde wieder sichtbar, ein zweiter Consumer griff zu.
Der entscheidende Hinweis kam nicht aus dem Anwendungslog, sondern aus der CloudWatch-Metrik ApproximateNumberOfMessagesNotVisible kombiniert mit der Datenbanklatenz. Sobald die DB-Latenz stieg, stieg auch die Doppelverarbeitungsrate — eine direkte Korrelation, die den Timeout-Zusammenhang sofort offenbarte.
Fix: Timeout auf 120 Sekunden erhöht, zusätzlich ChangeMessageVisibility als Heartbeat alle 60 Sekunden implementiert. Doppelverarbeitung seitdem null.
IAM-Berechtigungen für Visibility-Timeout-Operationen
Consumer benötigen explizite Berechtigungen für alle drei relevanten Aktionen. Fehlende ChangeMessageVisibility-Berechtigung ist ein häufiger Grund, warum der Heartbeat-Ansatz in Produktion lautlos scheitert.
🔽 IAM-Policy für SQS Consumer (klicken zum Aufklappen)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:ChangeMessageVisibility",
"sqs:GetQueueAttributes"
],
"Resource": "arn:aws:sqs:us-east-1:123456789012:meine-queue"
}
]
}
Dead Letter Queue als Sicherheitsnetz
Selbst mit korrektem Timeout-Management können Nachrichten in eine Endlosschleife geraten, wenn die Verarbeitung dauerhaft fehlschlägt. Eine Dead Letter Queue (DLQ) fängt Nachrichten auf, die eine konfigurierte Anzahl von Empfangsversuchen (maxReceiveCount) überschreiten.
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/meine-queue \
--attributes '{
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:123456789012:meine-queue-dlq\",\"maxReceiveCount\":\"5\"}"
}'
Der maxReceiveCount-Wert sollte höher sein als die erwartete Anzahl legitimer Retries — sonst landen valide Nachrichten in der DLQ, bevor sie erfolgreich verarbeitet werden konnten.
SQS Visibility Timeout: Zusammenfassung und nächste Schritte
Doppelverarbeitung durch SQS ist fast immer auf ein zu kurz konfiguriertes Visibility Timeout zurückzuführen. Die Lösung ist nicht kompliziert, aber sie erfordert ein genaues Verständnis der eigenen Verarbeitungsdauer — inklusive Ausreißer unter Last.
- Visibility Timeout immer auf Basis des 99. Perzentils der Verarbeitungszeit konfigurieren, nicht des Durchschnitts
- Für variable Workloads
ChangeMessageVisibilityals Heartbeat implementieren DeleteMessageexplizit nach erfolgreicher Verarbeitung aufrufen — nie davon ausgehen, dass es automatisch passiert- DLQ einrichten, um dauerhaft fehlschlagende Nachrichten zu isolieren
- CloudWatch-Metrik
ApproximateNumberOfMessagesNotVisiblemonitoren
Weiterführende Dokumentation: AWS SQS Visibility Timeout — offiziell
Glossar
| Begriff | Bedeutung |
|---|---|
| Visibility Timeout | Zeitfenster nach dem Abruf einer Nachricht, in dem sie für andere Consumer unsichtbar ist |
| Receipt Handle | Eindeutiger Bezeichner für einen spezifischen Empfangsvorgang einer Nachricht — wird für Delete und ChangeMessageVisibility benötigt |
| In-Flight Message | Nachricht, die abgerufen wurde, aber noch nicht gelöscht ist — zählt gegen das In-Flight-Limit der Queue |
| Dead Letter Queue (DLQ) | Separate Queue, in die Nachrichten verschoben werden, die zu oft erfolglos verarbeitet wurden |
| ChangeMessageVisibility | API-Aufruf zum Verlängern des Visibility Timeouts einer bereits abgerufenen Nachricht |
Kommentare
Kommentar veröffentlichen