Systems Engineering
Queue- & Background-Job-Engineering
Queues, um die Sie sich nicht kümmern müssen - idempotente Jobs, Wiederholungen mit Backoff, ein Fehlerpfad den jemand ansieht, und Worker die ein Deployment ohne Arbeitsverlust überstehen.
Eine Queue ist der Teil einer Laravel-Anwendung, der am leichtesten
hinzuzufügen und am schwersten zu betreiben ist. dispatch() ist eine Zeile,
und alles, was den Unterschied zwischen einer Queue und einer verlässlichen
Queue ausmacht, passiert nach dieser Zeile.
Die vier Fragen, die eine Queue beantworten muss
Was passiert, wenn der Job scheitert? Nicht falls. Eine fremde API läuft in einen Timeout, ein Datensatz wird zwischen Dispatch und Ausführung gelöscht, ein Deployment startet den Worker mitten im Job neu. Die Antwort muss als Wiederholungsrichtlinie aufgeschrieben sein, mit Backoff, einer maximalen Versuchszahl und einem Ziel für alles, was am Ende immer noch scheitert.
Was passiert, wenn er zweimal läuft? At-least-once-Zustellung ist die übliche Zusicherung, das heißt: Jeder Job muss entweder gefahrlos wiederholbar sein oder durch etwas abgesichert werden, das die Wiederholung harmlos macht. Eine zweimal gesendete E-Mail ist peinlich. Eine zweimal eingezogene Zahlung ist eine Rückbuchung und ein Support-Vorgang.
Wer erfährt davon? Eine failed_jobs-Tabelle, die sich still füllt, ist
der häufigste Produktionsfehler, zu dem wir gerufen werden, und er ist nie
wirklich ein Queue-Fehler. Der Job ist gescheitert, das Framework hat es genau
wie vorgesehen aufgezeichnet, und an der Aufzeichnung hing nichts.
Was passiert bei einem Deployment? Ein Worker, der gerade einen Job hält, während der Code unter ihm ausgetauscht wird, wird entweder sauber neu gestartet oder abgeschossen. Was von beidem, hängt von einer Konfiguration ab, die die meisten Teams erben statt sie zu wählen.
Was wir tun
Jobs idempotent machen. Eine Kennung, die der Job mitführt, durchgesetzt dort, wo die Wirkung tatsächlich landet – eine Unique-Constraint, ein Idempotenzschlüssel beim Zahlungsdienstleister, ein Statuswechsel, der nur einmal möglich ist. Kein Prüfen-dann-Handeln in PHP, das zwei Worker an dem Tag, an dem es zählt, gleichzeitig passieren.
Wiederholungsrichtlinien pro Job setzen, nicht pro Anwendung. Ein vorübergehender HTTP-Fehler will mehrere Versuche mit wachsender Verzögerung. Ein Validierungsfehler will null – ihn zu wiederholen verbrennt nur die Queue und verzögert alles dahinter. Die beiden zu unterscheiden ist eine Zwei-Zeilen-Änderung, und sie wird fast nie gemacht.
Fehlern einen Ort geben. Fehlgeschlagene Jobs gemeldet an das, was Ihr Team ohnehin beobachtet, mit genug Kontext zum Handeln. Ein Dead-Letter-Pfad für das, was nicht wiederholt werden kann. Ein Alarm auf die Rate statt auf das einzelne Ereignis, damit es Signal ist und kein Rauschen.
Die Topologie richtig schneiden. Queues nach Latenzanforderung trennen statt nach Funktion, damit ein nächtlicher Export kein Passwort-Zurücksetzen verzögern kann. Worker, die zur tatsächlichen Form der Arbeit passen. Horizon mit Supervisors, die dazu passen, und Messwerten, die einen wachsenden Rückstau sichtbar machen, bevor er ein Vorfall ist.
Lange Arbeit überlebensfähig machen. Gebündelte Jobs mit Fortschritt, so zerteilt, dass ein Neustart einen Abschnitt kostet statt den ganzen Durchlauf, und mit einer Möglichkeit fortzusetzen statt neu zu beginnen.
Geplante Aufgaben, die dieselben Probleme haben
Der Scheduler bekommt weniger Aufmerksamkeit als die Queue und scheitert auf dieselbe Weise. Eine Aufgabe, die sich selbst überlappt, weil der vorige Lauf noch läuft. Eine Aufgabe, die still aufhört, weil der Cron-Eintrag auf einem Server stand, der ersetzt wurde. Eine Aufgabe, deren Scheitern eine Logzeile ist, die niemand liest.
Wir behandeln geplante Aufgaben als Jobs mit einem Auslöser: Überlappungsschutz, wo er zählt, ein Heartbeat, damit eine Aufgabe, die aufhört zu laufen, auffällt, und derselbe Fehlerpfad wie bei allem anderen.
Wie ein Projekt abläuft
Schicken Sie die Job-Klassen und, falls Sie sie aufbewahren, eine Woche fehlgeschlagener Jobs. Was bereits schiefgegangen ist, sagt mehr über das Design als der Code.
Zurück kommt ein Leistungsumfang: jeder Job mit seinem Fehlerverhalten, die Queue-Topologie, das Monitoring und was in Phase eins gehört. Er hat einen Preis, und der Vertrag bezieht sich darauf statt auf ein Gespräch.
Die Arbeit landet als reviewbare Pull Requests in Ihrem Repository, mit getestetem statt beschriebenem Retry- und Idempotenzverhalten.
Was Sie bekommen
Eine Durchsicht jedes Jobs und jeder geplanten Aufgabe der Anwendung mit dokumentiertem Fehlerverhalten, die Korrekturen als prüfbare Pull Requests, die Queue-Topologie und Worker-Konfiguration als Code, und das Monitoring angebunden an das, was Sie bereits nutzen.
Das Ergebnis, das uns wichtig ist, ist das am schwersten vorführbare: eine Queue, an die einen Monat lang niemand gedacht hat, weil sie niemanden gebraucht hat.
Das ganze Design ruht auf einer Annahme: jeder Job wird zweimal laufen. Die meisten Queue-Bugs sind ein Verstoß dagegen. Der zweithäufigste ist ein Worker, der noch den Code der Vorwoche ausführt, ein Deployment-Fehler, der sich als Queue-Fehler meldet. Sind die Jobs langsam und nicht falsch, beginnt man bei der Performance-Arbeit.
