Zum Inhalt springen
serdjukow.eu
Zur Startseite

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Gebaut mit Next.js 16, Tailwind CSS und CanvasGehostet auf VercelGitHub (öffnet in einem neuen Tab)Stand