Engineering
Wie ich Systeme baue, die weiterlaufen, wenn etwas schiefgeht: worauf ich mich konzentriere, wie eine zuverlässige Pipeline aufgebaut ist und welche Prinzipien dahinterstehen.
Fokus
Schwerpunkte
Asynchrone Verarbeitung
Queues, Scheduler und Webhooks, die lang laufende Jobs zuverlässig durchlaufen lassen – mit Retries, Dead-Letter-Queues und Reconcilern.
AWS Serverless
Amplify Gen 2, AppSync, Lambda, DynamoDB, SQS und EventBridge Scheduler im Produktivbetrieb.
KI im Produkt
LLM-Funktionen in echten Workflows: KI-bewertete Websites, generierte E-Mails und LLM-gestützte Terminplanung, geprüft durch deterministische Regeln.
Anspruchsvolle Frontends
Oberflächen mit React und Next.js für datenintensive Workflows, mit Live-Status über Subscriptions.
Wie Systeme in Produktion bestehen
Anatomie eines zuverlässigen Systems
Sechs Muster, die ein eventgetriebenes System korrekt halten, wenn Aufrufe scheitern, Nachrichten doppelt ankommen und die Last sprunghaft steigt.
Systemdiagramm: Die API bestätigt Anfragen sofort, die Queue puffert sie; zwei Worker arbeiten sie im eigenen Tempo ab. 01 / 06
Schnell annehmen, asynchron verarbeiten
Die API validiert die Anfrage, legt sie in eine persistente Queue und antwortet innerhalb von Millisekunden mit 202 Accepted. Langsame oder unzuverlässige Arbeit erledigen später Worker, die Nachrichten in ihrem eigenen Tempo abholen. Die Latenz für Aufrufer hängt nicht mehr von der langsamsten nachgelagerten Abhängigkeit ab.
In der Praxis: Mit dem 202 eine Job- oder Event-ID zurückgeben, damit Clients das Ergebnis abfragen oder abonnieren können.
Systemdiagramm: Eine Nachricht scheitert zweimal, wartet mit wachsendem Backoff und ist im dritten Versuch erfolgreich. 02 / 06
Retries mit Backoff
Die meisten Fehler sind vorübergehend: ein gedrosselter Dienst, ein Timeout, eine abgebrochene Verbindung. Eine fehlgeschlagene Nachricht wird nicht gelöscht, sondern nach Ablauf des Visibility Timeouts wieder sichtbar und erneut verarbeitet. Exponentielles Backoff mit Jitter verteilt die Versuche, damit Retries eine angeschlagene Abhängigkeit nicht als synchrone Welle treffen.
In der Praxis: Visibility Timeout deutlich über dem Worker-Timeout ansetzen (bei Lambda etwa das Sechsfache), sonst wird mitten in der Verarbeitung erneut zugestellt.
Systemdiagramm: Eine Nachricht scheitert dreimal und landet in der Dead-Letter-Queue; der Alarm schlägt an, nach dem Fix folgt der Redrive. 03 / 06
Dead-Letter-Queue
Manche Nachrichten werden nie erfolgreich sein: ungültige Daten, ein Bug, ein fehlender Datensatz. Nach maxReceiveCount Versuchen verschiebt die Queue sie in eine Dead-Letter-Queue, damit eine einzelne Poison Message weder den Rest blockiert noch Kapazität verbrennt. Der Fehler ist geparkt, nicht verloren: Ein Alarm schlägt an, die Ursache wird behoben, die Nachricht per Redrive erneut eingespielt.
In der Praxis: Alarm auf DLQ-Tiefe > 0. Eine Dead-Letter-Queue, die niemand beobachtet, ist nur ein langsamerer Weg, Daten zu verlieren.
Systemdiagramm: Dieselbe Nachricht wird zweimal zugestellt; der zweite Write findet den vorhandenen Key und bleibt ohne Wirkung. 04 / 06
Idempotenz
Queues stellen mindestens einmal zu, und auch Clients wiederholen Anfragen; dieselbe Nachricht kommt also irgendwann doppelt an. Jede Nachricht trägt einen Idempotency Key, den der Worker per Conditional Write speichert; der Seiteneffekt passiert nur, wenn der Key neu ist. Das Duplikat wird geschluckt, das Ergebnis ist dasselbe wie bei einmaliger Zustellung.
In der Praxis: In DynamoDB attribute_not_exists(pk) auf den Key: ein Conditional Write, keine Race Condition zwischen Lesen und Schreiben.
Systemdiagramm: Eine Lastspitze füllt die Queue, während nur drei Worker-Slots laufen; der Rückstau baut sich gleichmäßig ab. 05 / 06
Backpressure und Concurrency-Limits
Eine Lastspitze fängt die Queue ab, statt sie durchzureichen. Reserved Concurrency begrenzt, wie viele Worker gleichzeitig laufen, sodass die Datenbank oder eine Partner-API die gleichmäßige Last sieht, für die sie ausgelegt ist. Der Rückstau wächst kurz und baut sich dann ab: Nichts geht verloren, es dauert nur etwas länger.
In der Praxis: Das Limit danach bemessen, was das nachgelagerte System verkraftet, nicht danach, was die Spitze verlangt.
Systemdiagramm: Jede Komponente liefert Logs und Metriken; Alarme überwachen DLQ-Tiefe und das Alter der ältesten Nachricht. 06 / 06
Observability
Asynchrone Systeme fallen leise aus: Die API antwortet weiter mit 202, während sich dahinter Nachrichten stauen. Strukturierte Logs mit Correlation ID machen den Weg einer Nachricht über Dienste hinweg nachvollziehbar; Metriken zeigen Durchsatz, Fehler und Queue-Tiefe. Alarme gehören auf die Symptome, die zählen: DLQ-Tiefe und das Alter der ältesten Nachricht.
In der Praxis: Das Alter der ältesten Nachricht verrät einen hängenden Consumer auch dann, wenn die Queue-Tiefe bei wenig Traffic unauffällig bleibt.
Arbeitsweise
Prinzipien, nach denen ich baue
Schlussfolgerungen aus meinen eigenen Artikeln. Sie betreffen den Teil, den viele Demos auslassen – und der ein verteiltes System im Produktivbetrieb erst beherrschbar macht.
- 01
Mit dem Retry rechnen
Lambda wiederholt fehlgeschlagene Aufrufe, und meine Architektur muss darauf vorbereitet sein. Jeder Handler entscheidet bewusst, welche Fehler er wirft und welche er behandelt – und ein zweiter Lauf darf keinen Schaden anrichten.
- 02
Abgleichen statt nur wiederholen
Retries ersetzen keine Reconciliation. Ein regelmäßiger Abgleich vergleicht, was passieren sollte, mit dem, was tatsächlich passiert ist – damit eine verlorene Nachricht oder ein verpasster Schedule nicht verloren bleibt.
- 03
Mutations koordinieren Workflows
CRUD bildet Zustand ab, Mutations koordinieren Workflows. Der Browser bleibt für die Absicht zuständig, das Backend übernimmt die Orchestrierung.
- 04
Echtzeit, mit Fallback
Echtzeit, wenn es funktioniert, robust, wenn nicht. Für Live-Status setze ich zuerst auf Subscriptions und halte Polling als Fallback bereit, damit der Fortschritt auch bei abgebrochener Verbindung ankommt.