Blog
Ereignisgesteuerte Datenübertragung. Wenn eine einfache Kombination aus SQS und Lambda die EventBridge-Pipes übertrifft

Als Befürworter reaktiver, ereignisgesteuerter Systeme nutze ich häufig Amazon EventBridge, um Teile einer verteilten Architektur miteinander zu verknüpfen. Es handelt sich um einen hervorragenden Dienst, der Ihnen einen Großteil der Arbeit abnimmt, wenn Sie solche Systeme entwerfen und betreiben:
- einfach einzurichtendes Ereignis-Routing,
- sofort einsatzbereite Integration mit mehr als 100 AWS-Diensten,
- mühelose Umgestaltung von Veranstaltungen,
- integrierte Wiederholungsversuche,
- Veranstaltungsarchiv,
- sowie rasante Wiederholungen.
Zusätzlich zu all dem bieten EventBridge Pipes eine hochverfügbare Pipeline zum Filtern und Anreichern von Ereignismeldungen. Sie werden schnell davon überzeugt sein, dass sie die „richtige“ verwaltete Schnittstelle zwischen einer Quelle und einem Ziel darstellen.
Was die Kosten betrifft, zahlen Sie 0,40 US-Dollar pro Million Anfragen nach der Filterung (wie großzügig, nicht wahr?), wobei jeder 64-KB-Block der Nutzdaten als eine Anfrage abgerechnet wird. Vergessen Sie nicht, dass zusätzlich die üblichen Gebühren für andere Dienste anfallen.
Und genau das ist der springende Punkt: Ein Pipe mit einer Lambda-basierten Anreicherung überzeugt mich nicht wirklich. Denn warum sollten Sie – sowohl finanziell als auch durch den Verlust an Fehlergranularität – für etwas bezahlen, das Ihre bestehenden serverlosen Primitive ohnehin bereits besser leisten?
Eine Anmerkung zu diesem Schritt möchte ich jedoch machen. Wenn Sie mit einem Ereignisstrom arbeiten und eine HTTP-API zur Anreicherung nutzen, ist EventBridge Pipe die naheliegende Wahl für eine Low-Code-Integration. Bei einer Pipe vom Typ „SQS – Lambda – SQS“ ist der Aufwand jedoch den Nutzen nicht wert. Ich wollte Pipes gerne mögen und sie für diesen Zweck einsetzen, das habe ich auch versucht …
Was Pipes verspricht und die eine Aufgabe, die wir gerade testen
Eine EventBridge-Pipe ist, kurz gesagt, ein Baustein für die Punkt-zu-Punkt-Integration:
- Es überträgt Nachrichten aus einer Quelle, in der Regel einer SQS-Warteschlange.
- Es kann den Datenstrom anhand des EventBridge -Ereignismusters filtern.
- Bei Bedarf kann jede Nachricht transformiert und einer Anreicherungsphase unterzogen werden.
- Und schließlich werden die Nachrichten an das Ziel der Pipe gesendet.
Darüber hinaus bieten sie eine integrierte Fehlerbehandlung – Wiederholungsversuche mit exponentiellem Backoff. Und im Falle nahezu kontinuierlicher Fehler bei der Anreicherung oder beim Ziel deaktiviert sich die Pipe automatisch. Selbstverständlich können Sie die Dead-Letter-Queue für jede Stufe der Pipe konfigurieren.
Als Bonus stellt Ihnen AWS zahlreiche integrierte Kennzahlen zur Verfügung, die für jede Phase in praktischen Dashboards zusammengefasst sind. Das klingt wie ein Traum, fast schon wie ein Wunschtraum.
Vor diesem Hintergrund habe ich mich daher für die EventBridge-Pipes entschieden, da diese für meine Aufgabe wie geschaffen sind:
- mehrere Millionen Dokumente in S3 hochladen,
- sie durch ein Lambda-Skript zur Anreicherung leiten, um die Nachricht mit den erforderlichen Metadaten zu versehen,
- Ergebnisse in die Datenbank einfügen.
Was könnte schiefgehen?
Wo Sie die Anreicherungs-Batch-Verarbeitung im Stich lässt
EventBridge-Pipes unterstützen das Batching von Nachrichten: Sie nehmen N Nachrichten entgegen und senden diese an die Anreicherung oder das Ziel. Das ist großartig, genau wie beim SQS-Batching, oder? Nun, nein.
Wenn Sie mit SQS-Ereignisquellen für Ihre Lambda-Funktionen vertraut sind, kennen Sie sicherlich die Fehlermeldung bei Teil-Batches – eine spezielle Form der Lambda-Funktionsantwort, mit der Sie melden können, welche Nachrichten fehlgeschlagen sind. Im Endeffekt werden dadurch nur die fehlgeschlagenen Nachrichten zur Wiederholung gesendet und nicht der gesamte Batch.
Das Problem bei „Pipes Enrichment“ besteht also darin, dass es keinen Fehlermodus für Teil-Batches unterstützt: Wenn Ihre Anreicherungsfunktion fehlschlägt, wird der gesamte Batch erneut versucht. Die Zielstufe verfügt zwar über eine umfassende Batch-Unterstützung, allerdings mit einer Besonderheit: Fehlgeschlagene Nachrichten werden über die gesamte Pipe erneut versucht, wodurch Ihre Anreicherung erneut aufgerufen wird:

Die „Batch-of-1“-Ausweichmöglichkeit und was sie kostet
Die „naheliegende“ Lösung besteht darin, die Batchgröße für die Anreicherung auf 1 zu reduzieren, sodass jeder Fehler eindeutig einer einzelnen Nachricht zugeordnet werden kann:
-
Die Pipe erkennt eindeutig, welche Nachricht nicht angereichert werden konnte, und sendet sie an die Quellwarteschlange zurück.
So, als ob es den teilweisen Ausfall einer Charge unterstützen würde.
-
Die übliche „
maxReceiveCount“-Funktion der Warteschlange dient als automatische DLQ-Entlastung.
Bitte beachten Sie, dass es sich hierbei um den DLQ in der Quellwarteschlange handelt, nicht um die Pipe.
- Und „DLQ Redrive-to-Source“ funktioniert, da die Nachrichten die Metadaten der Quelle enthalten.
Auch hier gilt: Genau wie bei der regulären SQS.
Die Einstellung „batch=1“ eliminiert den Kostenhebel der Pipe vollständig – bei 64 KB pro Anfrage könnten Sie mehrere Nachrichten in einen Batch packen, der als eine einzige Anreicherungsanfrage abgerechnet wird. Bei „batch=1“ kosten dieselben fünf Nachrichten fünf Anfragen, und im Gegenzug erhalten Sie keinerlei zusätzlichen Nutzen. Sie erhalten damit genau dieselbe Fehlersemantik, die Ihnen eine einfache Kombination aus SQS und Lambda kostenlos bieten würde...
Ja, in absoluten Zahlen ist der Unterschied verschwindend gering. Dieses Geld fließt jedoch nicht in etwas, das Sie nutzen. Stattdessen zahlen Sie dafür, eine Funktion des Dienstes zu deaktivieren, die Sie nicht aktiviert lassen können.
Wenn Sie in den sauren Apfel beißen und die gesamte N-Charge erneut versuchen lassen, öffnet dies eine weitere Büchse der Pandora – Sie zahlen für die erneute Übermittlung von Nachrichten, die bereits beim ersten Mal erfolgreich waren. Außerdem muss Ihr Lambda-Funktionsblock zur Anreicherung idempotent sein – der Systemzustand oder die Nutzlast muss unverändert bleiben, falls die Nachricht mehr als einmal empfangen wird.
Der clevere Trick, der keiner war
Manchmal bin ich ziemlich stur – ich lasse eine Idee erst los, wenn ich alles ausprobiert habe und zu dem Schluss komme, dass sie sich nicht lohnt. Nun, das ist auch der Grund, warum es diesen Beitrag überhaupt gibt – um Ihnen Zeit und Mühe zu ersparen, da ich die Dinge bereits ausprobiert habe. Gern geschehen 😉.
Der Angriffsplan lautete also wie folgt:
- Bewahren Sie eine große Menge davon auf (maximal 10).
- Leiten Sie Fehler direkt aus dem Anreicherungs-Lambda an den DLQ weiter.
- Filtern Sie diese aus der Anreicherungsreaktion heraus.
- Lassen Sie den Rest durch das Rohr fließen.
Aha, wie clever! Schnallen Sie sich an, wir heben ab, um … auf die Wand zu treffen – „ CouldNotDetermineMessageSource “-Fehler.
Wenn Sie die Nachricht direkt von Ihrer Workload an die DLQ senden, enthält sie nicht die erforderlichen Ursprungsmetadaten, damit ein „Redrive-to-Source“ erfolgreich durchgeführt werden kann. Sie können dies mit einem „Redrive to a custom destination“ und der Übergabe der ARN der Quellwarteschlange bewerkstelligen, wodurch Sie die manuelle Verknüpfung, die durch Pipes eigentlich entfernt werden sollte, im Grunde wieder hinzufügen.
Was Sie eigentlich möchten, befindet sich bereits in der Verpackung
Kehren wir zu den Klassikern der serverlosen Architektur zurück – eine einfache Zuordnung der Ereignisquelle von SQS zu Lambda unterstützt „ ReportBatchItemFailures “ von Haus aus:
- Ihre Verbraucherfunktion erhält Chargen zu je 10.
- Lambda unterstützt bei der Rückmeldung an SQS eine Granularität der Wiederholungsversuche auf Nachrichtenebene.
- Sie müssen keinen eigenen Code erstellen – nutzen Sie einfach das Dienstprogramm„ “ zur Stapelverarbeitung ( ) aus den AWS Lambda Powertools.
Damit erhalten Sie genau das, was Ihnen die Pipes-Erweiterung nicht bieten kann – Sie haben folgende Möglichkeiten:
- Alles stapelweise ausführen und erneut versuchen
- oder „batch=1“ festzulegen und die Stapelverarbeitung gänzlich aufzugeben.
Eine langweilige, altbekannte, einfache Lösung, die besser funktioniert als ein neuer, glänzender Managed Service...
Wenn eine Pfeife sich ihren Platz noch immer verdient
Ich habe dies bereits zu Beginn erwähnt, aber lassen Sie uns etwas näher darauf eingehen.
EventBridge Pipes zeichnen sich besonders aus durch:
- eine No-Code-/Low-Code-Verbindungslösung – Eingabetransformatoren;
- Anreicherung ohne Lambda — API-Ziele, StepFunctions Express;
- Heterogene Verdrahtung zwischen Quelle und Ziel.
Ohne Pipes müssten Sie die Integration manuell durchführen, was wiederum dazu führt, dass Ihr Team für die Arbeitslast auf der Verbraucherseite, Wiederholungsversuche und all das verantwortlich ist.
Meine Kritik richtet sich hier gegen die Verwendung von Pipes für die lokale, deterministische und Lambda-basierte Anreicherung einer SQS-Quelle; sie richtet sich nicht gegen Pipes insgesamt.
Die Zusammenfassung
Falls Sie beabsichtigen, eine Pipe für die Lambda-Anreicherung zu verwenden, prüfen Sie bitte, ob die klassische Kombination aus SQS und Lambda dies bereits kostengünstiger und mit einer besseren Fehlerbehandlung ermöglicht.
Denken Sie außerdem einmal darüber nach: Wenn die Anreicherung fehlschlägt, müssen Sie dann eine Fehlermeldung ausgeben und es erneut versuchen? Wird es beim zweiten Mal gelingen? Vielleicht ist das Ganze einfach sinnlos…
Oder ist es aus Sicht der Geschäftslogik unbedenklich, Nachrichten an das Ziel weiterzuleiten, selbst wenn die Anreicherung fehlgeschlagen ist?
Die Sache ist die, dass hohe Fehlerquoten Ihren Durchsatz beeinträchtigen:


Die Pipe drosselt ihre Ausführung aufgrund einer hohen Fehlerquote des Anreicherungs-Lambda-Ausdrucks und reduziert die Anzahl der Aufrufe auf lediglich etwa 240 pro Minute.
Also habe ich meine Herangehensweise an den Fehler bei der Anreicherung geändert – anstatt bei Nachrichten, die ich in keiner Weise verarbeiten konnte, einen Fehler auszugeben, habe ich einfach ein benutzerdefiniertes Feld „ skipped: boolean “ zur Nutzlast hinzugefügt.
Der Endnutzer wandte denselben Filter an – nämlich, keine Einträge zu berücksichtigen, die mit „ skipped: true “ gekennzeichnet sind. Das Ergebnis waren zehnmal weniger Fehler, ein deutlich höherer Durchsatz und aussagekräftige Fehlermeldungen – so konnte ich meine Anreicherungslogik verfeinern, die Verarbeitung von Randfällen verbessern usw.


Durch die Neugestaltung des Fehlerbehandlungsmechanismus konnte der Durchsatz innerhalb von Pipes stabilisiert werden; die Argumente hinsichtlich der Kosten und der Anzahl der Wiederholungsversuche pro Nachricht, die gegen eine Anreicherung sprechen, bleiben jedoch weiterhin bestehen. Die nächste Frage lautet, ob eine einfache Kombination aus SQS und Lambda sogar diese optimierte Konfiguration übertrifft.
Bleiben Sie dran, Fortsetzung folgt.
Foto: Benjamin White.
Verfasst von
Yev Dytyniuk
AWS Cloud Consultant/Engineer
A software/cloud engineer who has seen enough to go and fix things before the damage is done. I've built various web applications, integrated enterprise systems, run migrations, etc. I like simple, effective, and robust architectures and am always on the lookout for another interesting challenge: optimise a wonky database, turn an hour-long batch process into a blazingly fast event-based pipeline, or just talk about the quirks of another AWS service.
Contact



