Was wir wollten, war ganz einfach.
Einen neuen Artikel schreiben und so schnell wie möglich veröffentlichen.
Mehr nicht.
Doch irgendwann lief vor dem Veröffentlichen schon eine Besprechung.
Wer ist diesmal zuständig? Workers oder Actions? Ist noch Budget da? Ist die letzte Sperre noch aktiv? Wem gehört die Markierung? Wie oft haben wir es schon erneut versucht?
Der Artikel wartet.
Das System wartet auch.
Alle denken: „Das ist nicht meine Aufgabe.“
In einer Firma kann man alle in den Besprechungsraum rufen. Ein verteiltes System bleibt einfach still stehen.
Das ist die Live-Schedule-Hölle.
1. Wir wollten sofort veröffentlichen und haben stattdessen eine Wahl für den Zuständigen veranstaltet
Wenn man den Ablauf zum Veröffentlichen robuster machen will, baut man ganz natürlich mehr Sicherungen ein.
Bleibt Workers stehen, übernimmt Actions. Ist das Budget von Actions knapp, übernimmt Workers. Man setzt eine Sperre, damit niemand dieselbe Arbeit zweimal macht. Man setzt eine Markierung, damit zwei Prozesse nicht dieselbe Aufgabe anfassen. Man zählt die erneuten Versuche. Man stellt einen Aufpasser dazu.
Einzeln betrachtet ist jede dieser Ideen vernünftig.
Das Problem entsteht, wenn man sie alle hintereinander schaltet.
Vor der Frage „Kann dieser Artikel veröffentlicht werden?“ steht dann eine viel größere Frage: „Wer darf ihn veröffentlichen?“
Am gefährlichsten ist es, wenn nur das Zuständigkeitsschild überlebt.
Die Arbeit auf der Workers-Seite ist stehen geblieben. Im Eintrag steht aber noch „Zuständig: Workers“. Wenn Actions später aufwacht, liest es „Zuständig: Workers“ und überspringt die Aufgabe.
Nichts ist kaputt. Niemand arbeitet.
Das ist die Version von „Die zuständige Person ist gerade nicht am Platz“ für verteilte Systeme.
2. Das Problem war die Abstimmung, nicht die Fähigkeit zur Ausführung
Bei einem Lauf hatte Actions noch Kapazität und Budget.
Die eigentliche Arbeit lief trotzdem nicht.
Der Grund ist einfach: Die Prüfung kam zu dem Ergebnis, „diese Aufgabe gehört Workers“.
Das ist der entscheidende Punkt.
Es fehlte nicht an CPU. Es waren nicht alle APIs ausgefallen. Actions konnte ganz normal starten.
Das Problem ist die Abstimmung, nicht die Ausführung.
Was blockiert, ist die Frage, wer es macht, nicht die Kraft, es zu tun.
So hört man mit dem Beheben einzelner Fehler nicht auf.
Man korrigiert die Menge der geholten Daten. Man korrigiert das Scheitern beim Speichern in main. Man korrigiert die erneuten Versuche. Man korrigiert den Wechsel der Zuständigkeit. Dann bleibt eine alte Markierung zurück. Dann bleibt eine alte Sperre zurück. Der Aufpasser bemerkt das Problem, darf aber nicht veröffentlichen.
Es sieht aus wie Maulwurf-Klopfen, aber alle Maulwürfe kommen aus demselben Loch.
Die Steuerungsebene ist zu schlau.
3. Ein Schedule ist eine Uhr, keine Aufgabenliste
Der Schedule von GitHub Actions ist praktisch. Laut der offiziellen Dokumentation kann er aber bei hoher Last verspätet laufen, und bei sehr hoher Last können einige wartende Aufträge sogar verloren gehen.
Ein Schedule eignet sich also für „Weck mich ungefähr um diese Zeit“.
Er eignet sich schlecht für „Jemand muss diesen Text unbedingt abholen und festhalten, bis es klappt“.
Eine Uhr klingelt.
Eine Uhr hält deinen Text nicht fest.
Eine Nachrichtenwarteschlange dagegen speichert die Arbeit selbst. Sie kann auch mit Fehlern umgehen: erneute Versuche, Verzögerungen und eine „Warteschlange für gescheiterte Nachrichten“ (Dead-Letter-Queue), ein eigener Ort für alles, was immer wieder scheitert.
Hier geht es nicht darum, unbedingt einen neuen Dienst einzubauen.
Es geht darum, die Rollen klar zu trennen:
- Schedule = weckt in regelmäßigen Abständen
- Liste offener Aufgaben = das maßgebliche Verzeichnis der unfertigen Arbeit
- Ausführender = macht die Arbeit wirklich
- kurze Sperre = der Schlüssel, der verhindert, dass zwei gleichzeitig arbeiten
- erneuter Versuch und Fehlerwarteschlange = wohin gescheiterte Arbeit kommt
Wenn die Uhr zugleich Personalabteilung, Lager und Fundbüro sein soll, hat die Uhr einen schlechten Tag.
4. Maßgeblich ist „Was ist noch nicht fertig?“, nicht „Wem gehört es?“
Bei einer komplizierten Zuteilung rückt leicht „der Zuständige“ in den Mittelpunkt des Verzeichnisses.
Beim Veröffentlichen will man aber eigentlich nur wissen:
Ist dieser Artikel veröffentlicht oder nicht?
Fast nur das.
Deshalb reicht als maßgebliches Verzeichnis die Liste der offenen Aufgaben.
Ein neuer Text kommt an: Er kommt auf die Liste. Beim Start der Veröffentlichung: Eine kurze Sperre wird gesetzt. Bei Erfolg: Er wird als veröffentlicht markiert. Bei einem Fehler: Er geht zurück auf die Liste. Nach der festgelegten Zahl von Fehlern: Er wandert in die Fehlerwarteschlange, und es gibt eine Warnung.
„Zuständig: Workers“ oder „Zuständig: Actions“ ist nur eine Zusatzinformation. Sie darf nie ein dauerhaftes Vetorecht werden.
Auch wenn „Workers“ dasteht: Bleibt eine bestimmte Zeit lang alles stehen, kann ein anderer Ausführender die Arbeit übernehmen.
So kann ein totes Zuständigkeitsschild die Arbeit nicht als Geisel halten.
5. Der Hauptweg startet mit dem Ereignis; der Schedule sammelt nur auf, was durchgerutscht ist
Wer sofort veröffentlichen will, widerspricht sich selbst, wenn er auf den nächsten Zeitpunkt wartet.
Man weckt den Ausführenden in dem Moment, in dem ein neuer Text ankommt.
Das ist der Hauptweg.
Und der Schedule sammelt alte offene Aufgaben ein, die nach einigen Minuten oder einigen Dutzend Minuten noch da sind.
Also:
Das Ereignis ist der Hauptweg. Der Schedule ist die Versicherung.
So ist es im Normalfall schnell.
Geht eine Benachrichtigung verloren, holt der Schedule sie später nach.
Macht man den Schedule zum Hauptweg, verzögert jede Verspätung die Veröffentlichung selbst. Kommt dann noch die Prüfung der Zuständigkeit dazu, passiert „aufgewacht, aber nichts getan“.
Der Wecker hat geklingelt.
Die Prüfung war bestanden.
Und dann: „Heute bin ich nicht zuständig“, und ab nach Hause.
Wozu war er dann überhaupt da?
6. Der Ersatzweg heißt „Altes Unfertiges einsammeln“, nicht „Zuständigkeit ändern“
Eine häufige Falle beim Ersatzweg ist, die Arbeit offiziell von A an B zu übergeben.
Eine Übergabe braucht ein Update im Verzeichnis.
Schlägt dieses Update fehl, wird die Übergabe selbst zur Störung.
Es geht viel einfacher.
Normalerweise erledigt Workers die Arbeit. Bei Erfolg ist sie fertig. Bei einem Fehler bleibt der Eintrag auf der Liste.
Ein Ersatz-Ausführender wie Actions prüft regelmäßig. Wenn
- ein offener Eintrag da ist
- er älter ist als eine festgelegte Zeit
- keine gültige Sperre besteht
- er selbst ihn ausführen kann
dann übernimmt er die Arbeit.
Er braucht keine „Übergabebescheinigung“ von Workers.
Entscheidend ist die Tatsache, dass die Arbeit nicht fertig ist.
Bei diesem Vorgehen zählt, ob das Ergebnis da ist, mehr als eine saubere Zuständigkeitstabelle.
Für ein System, das Artikel veröffentlicht, ist das am natürlichsten.
7. Live Schedule muss man nicht wegwerfen, aber er darf nicht zum Beatmungsgerät werden
Live Schedule selbst ist nicht das Problem.
Er ist sehr stark, wenn ein Mensch zuschaut:
Prioritäten ändern. Auffälligkeiten untersuchen. Verbesserungsvorschläge entwerfen. Reparaturen beschleunigen. Neue Pläne einbringen.
Das Problem ist, dass mit ihm auch die Artikelfabrik stehen bleibt, sobald er ausfällt.
Live soll ein Beschleuniger sein, kein einzelner Punkt, dessen Ausfall alles lahmlegt.
Auch ohne Live
neuer Text → Liste offener Aufgaben → Ausführender → Prüfung → Veröffentlichung
muss von selbst weiterlaufen.
Mit Live geht es schneller.
Ohne Live stirbt nichts.
So wie man das Herz eines Menschen nicht daran koppeln darf, ob jemand in Slack geantwortet hat.
8. Das Ziel ändern: von „das schlaue System ist fertig“ zu „einer rein, einer raus“
Ein solches System lässt sich ewig verbessern.
Faire Zuteilung. Das Gratis-Kontingent optimal nutzen. Doppelte Läufe verhindern. Budget vorhersagen. Prioritäten. Überwachung. Automatische Reparatur. Auswertungen.
Alles macht Spaß.
Und wenn man alles macht, erscheint kein Artikel.
Darum darf das erste Ziel viel gröber sein.
Ein neuer Text kommt rein, und ohne besonderen Eingriff wird ein Artikel veröffentlicht.
Schlägt es fehl, bleibt er auf der Liste. Ein anderer Ausführender holt ihn. Nur was immer wieder scheitert, geht an einen Menschen.
Wenn diese einfache gerade Linie stabil läuft, kann man die Optimierungen zurückholen.
Wir wollten nur neue Artikel schnell veröffentlichen und haben, ohne es zu merken, etwas gebaut, das einem selbstgebastelten verteilten Konsens ähnelt.
Solche Tage gibt es.
Aber es gibt einen Ausweg.
Die Komplexität kappen.
Der Wert eines Veröffentlichungssystems liegt nicht in der Schönheit der Wahl des Zuständigen.
Er liegt darin, dass Artikel nach draußen kommen.
Fazit
Der Kern der Live-Schedule-Hölle ist nicht der Schedule selbst.
Es liegt daran, dass der Zustand „Wer ist zuständig?“ wichtiger wurde als der Zustand „Welche Arbeit ist noch offen?“.
Wenn man zu einem einfacheren Entwurf zurückgeht, reicht Folgendes:
- Die Liste offener Aufgaben ist das maßgebliche Verzeichnis der unfertigen Arbeit
- Die Arbeit startet sofort, wenn das Ereignis eintrifft
- Sperren bleiben kurz
- Der Ersatz-Ausführende holt alte offene Einträge
- Der Schedule dient als Versicherung
- Was zu oft scheitert, kommt in die Fehlerwarteschlange
- Live ist ein optionaler Beschleuniger
Eine Fabrik, die nicht stehen bleibt, ist besser als eine schlaue Fabrik.
Fangen wir damit an.
Quellen (4)
- GitHub Docs: Events that trigger workflows — schedule https://docs.github.com/actions/reference/workflows-and-actions/events-that-trigger-workflows
- Cloudflare Workers Docs: Cron Triggers https://developers.cloudflare.com/workers/configuration/cron-triggers/
- Cloudflare Queues Docs: Overview https://developers.cloudflare.com/queues/
- Cloudflare Queues Docs: Dead Letter Queues https://developers.cloudflare.com/queues/configuration/dead-letter-queues/

