Der Worker ist ausgefallen. Der Sync nicht.

Beende den Worker mitten im Batch und verfolge einen einzelnen Kontakt: welcher Cursor gespeichert wurde, warum der wiederholte Schreibvorgang nicht doppelt ankommt, wem der Sync während des Neustarts gehört. Danach die zwei Vorfälle, die zeigten, dass Wiederherstellung nur die halbe Arbeit war.

Sync Fehler Wiederherstellung

Was passiert mit einem laufenden Sync, wenn der Worker mitten in einem Batch ausfällt?

Der Cursor bewegt sich erst am Ende eines sauberen Batches. Ein Worker, der mitten im Batch beendet wird, lässt ihn also am Anfang dieses Batches stehen, und der nächste Lauf liest dieselbe Seite. Der Zustellschlüssel jedes Datensatzes wird aus dem Seiten-Cursor und seiner Position abgeleitet, und ein Sieben-Tage-Ledger abgeschlossener Schreibvorgänge verhindert, dass die Wiederholung doppelt ankommt. Ein Lease pro Sync verhindert, dass zwei Läufe gleichzeitig arbeiten. Das war die leichte Hälfte. Ein Sync scheiterte Lauf für Lauf, bevor ihn irgendetwas pausierte, und ein Workspace blieb still, mit Fehlern und ohne Erfolg, bevor es irgendetwas bemerkte. Deshalb gibt es jetzt einen Breaker, der nach fünf aufeinanderfolgenden Fehlern pausiert, und einen stündlichen Alarm bei vierundzwanzig Stunden ohne Erfolg.

Mehr erfahren
Auf dieser Seite

In einem Vortrag aus dem Developer-Relations-Team von Crusoe, den AI Engineer am 3. Oktober 2026 veröffentlicht hat, wurde ein PyTorch-Trainingsjob auf zwei A100-Knoten gestartet. Dann bekam das eigene Monitoring-System der Demo die Meldung, dass eine GPU einen XID-79-Fehler ausgelöst habe. Der Knoten wurde als ausgefallen markiert und der Job abgebrochen. Der Prozess bekam ein SIGTERM und „bis zu zwei Minuten, um auf dieses SIGTERM zu reagieren und entweder die Checkpoints zu speichern oder die Logs zu schreiben“. Ein Ersatzknoten sprang ein, und „der slurm-Job startet neu. Er lädt den Checkpoint und setzt das Training dort fort, wo es aufgehört hat“. Von Anfang bis Ende knapp unter fünfzehn Minuten. Niemand wurde alarmiert.

Ein Sync ist ein viel kleineres Tier als ein Trainingslauf. Die Frage ist dieselbe. Was braucht ein Lauf, damit der Ausfall des Workers Minuten kostet und keine Duplikate erzeugt?

Hier ist die Antwort für Tajos eigenen Sync-Runner, gelesen aus dem Code und nicht aus einer Broschüre. Reverse ETL beschreibt die Form des Jobs. Hier geht es darum, was passiert, wenn der Job stirbt.

Folge einem Kontakt

Ein Lauf liest die Quelle Seite für Seite, ausgehend von einem gespeicherten Cursor. Nennen wir den Seiten-Cursor c3 und unseren Kontakt den 212. Datensatz auf dieser Seite. Drei Dinge gelten für diesen Kontakt, bevor irgendetwas geschrieben wird.

Erstens hat er einen Zustellschlüssel. Der Schlüssel ist ein Hash aus der ID des Syncs, der Quelle, dem Seiten-Cursor, der Position auf der Seite und dem Datensatz selbst. Cursor und Datensatz gehen in den Hash ein und werden nie in den Schlüssel eingebettet, sodass der Schlüssel keine E-Mail-Adresse enthält.

Zweitens läuft der Schreibvorgang über ein Ledger und nicht direkt zum Ziel. Bevor das Ziel aufgerufen wird, wird der Schlüssel als ausstehend beansprucht. Gelingt der Schreibvorgang, wird die Zeile als abgeschlossen markiert und sieben Tage lang aufbewahrt.

Drittens hat sich der Cursor nicht bewegt. Er bewegt sich nur am Ende eines Batches, nur wenn jeder Datensatz in diesem Batch angekommen ist, und nur nachdem der Lauf bewiesen hat, dass ihm der Sync noch gehört.

Beende jetzt den Worker zwischen dem Schreibvorgang und dem Checkpoint.

Bei einem SIGTERM leert sich jeder Queue-Worker innerhalb einer gemeinsamen Frist von fünfzehn Sekunden, und der Prozess beendet sich nach fünfundzwanzig Sekunden, egal was noch läuft; systemd gibt ihm fünfundvierzig, bevor es ihn hart beendet. Ein Lauf, der in diesem Fenster nicht fertig wurde, ist weiterhin als laufend markiert, und der nächste Worker übernimmt ihn über die Wiederherstellung hängengebliebener Jobs der Queue. Bei einem harten Abbruch wird gar nichts geleert. So oder so gelten dieselben zwei Tatsachen. Das Ziel hat unseren Kontakt. Unser Cursor steht immer noch auf c3.

Die Wiederholung

Der nächste Lauf beginnt bei c3, liest dieselbe Seite, leitet für den 212. Datensatz denselben Schlüssel ab und fragt das Ledger. Das Ledger sagt: abgeschlossen. Das Ziel wird nicht aufgerufen; das gespeicherte Ergebnis wird wiedergegeben, und der Lauf macht weiter. Auf dem Bulk-Pfad, auf dem der Import-Endpunkt von Brevo eine ganze Seite in einem Aufruf annimmt, läuft dieselbe Prüfung pro Schlüssel, bevor die Anfrage gebaut wird. Eine Seite, deren Schlüssel alle bereits abgeschlossen sind, sendet nichts.

Das ist der ganze Trick, und es lohnt sich zu sagen, warum er funktioniert. Der Schlüssel wird aus dem Seiten-Cursor am Anfang des Batches abgeleitet, nicht aus dem Lauf, der gerade zufällig ausgeführt wurde. Ein Absturz, ein Wiederholungsversuch sechzig Sekunden später und ein geplanter Lauf morgen leiten alle für denselben Datensatz denselben Schlüssel ab. Dem Ledger ist egal, wer von ihnen fragt.

Jetzt die Kosten. Das Ledger vergisst nach sieben Tagen. Der Kommentar im Code sagt selbst, dass sieben Tage eine verzögerte Wiederholung und einen kurzen Ausfall abdecken, und mehr behauptet er nicht. Eine Wiederholung ab einem Cursor, der älter ist, schreibt erneut. Und ein Lauf, der hart unterbrochen wird, kann warten. Der Lease des toten Laufs hält bis zu fünfzehn Minuten, und bei einem geplanten Sync wird ein Lauf, dessen Lease frei ist, erst aufgeräumt und neu eingeplant, wenn er vor über einer Stunde gestartet ist. Die Demo von Crusoe ist in unter fünfzehn Minuten wieder da. Unsere nicht immer.

Wem der Sync während des Neustarts gehört

Ein Sync gehört immer nur einem Lauf. Der Lauf nimmt einen Lease in Redis mit eigenem Token, einer Lebensdauer von fünfzehn Minuten und einer Verlängerung alle fünf Minuten. Vor jedem Checkpoint stellt er sicher, dass er diesen Lease noch hält. Wenn nicht, hört er auf. Der Lauf wird mit dem schlichten Satz erfasst, den die zuständige Person lesen wird: „The run lost its slot part-way through and stopped to avoid writing twice.“ (Der Lauf hat seinen Slot unterwegs verloren und angehalten, um nicht doppelt zu schreiben.) Ein zweiter Lauf, der mit einem aktiven Eigentümer kollidiert, wird nicht wiederholt. Er endet mit „Another run of this sync was already in progress“ (Ein anderer Lauf dieses Syncs war bereits im Gange), und der nächste geplante Lauf startet normal.

Auch die Zuständigkeit für die Behebung ist festgehalten. Jeder Fehlercode verweist auf einen Satz dazu, was passiert ist, einen dazu, was als Nächstes zu tun ist, und auf eine zuständige Partei: du oder Tajo. Ein Ziel, das jeden Datensatz abgelehnt hat, weil das Mapping falsch ist, liegt bei dir. Ein verlorener Lease liegt bei uns. Die Seite des Laufs zeigt den nächsten Schritt; der Leitfaden zur Sync-Wiederherstellung führt auf, was zutrifft.

Bis hierhin ist das die leichte Hälfte. Ein Absturz kostet eine Wiederholung, und nichts kommt doppelt an. Zwei Vorfälle haben uns die andere Hälfte gelehrt.

Fehler auf Fehler

Ein Sync scheiterte immer wieder. Nicht in einer engen Schleife, sondern einmal pro Scheduler-Intervall, Lauf für Lauf, jedes Mal mit derselben Meldung: Eine benutzerdefinierte ID darf keinen Doppelpunkt enthalten. Der Lauf erreichte das Ziel nie. Er starb schon beim Einreihen in die Queue, bevor ein Runner ihn sah, sodass ein Alarm, der am Runner ansetzte, nie etwas hatte, woran er ansetzen konnte.

Niemand beobachtete die Serie. Das ist die ganze Erkenntnis.

Die Lösung ist ein Breaker. Nach jedem erfassten Fehler liest er die letzten fünf abgeschlossenen Läufe des Syncs seit dessen letzter Änderung. Sind alle fünf Fehler, pausiert er den Sync, schreibt eine Audit-Zeile mit dem Grund circuit_breaker_tripped und löst einen kritischen Alarm aus. Die Serie wird aus den gespeicherten Läufen abgeleitet, nicht aus einem Zähler im Worker. Ein Fehler zählt, egal auf welchem Weg er erfasst wurde, auch der Fehler beim Einreihen, der den Vorfall verursacht hat. Laut Kommentar im Code hätte er diesen Vorfall bei Lauf fünf gestoppt. Ein Erfolg setzt die Serie zurück. Das Aufheben der Pause schaltet den Breaker wieder scharf, statt ihn anhand der alten Zeilen erneut auszulösen. Der Leitfaden zum Shopify-Connector sagt dasselbe in einer Zeile: Nach fünf aufeinanderfolgenden fehlgeschlagenen Läufen pausiert sich der Sync selbst, und seine Seite zeigt den letzten Fehler.

Was der Breaker nicht tut: irgendetwas reparieren. Crusoe ersetzt den Knoten und startet den Job neu, ohne zu fragen. Tajo hält an und fragt: Ein pausierter Sync bleibt pausiert, bis eine Person die Ursache behebt und ihn erneut veröffentlicht. Die vorsichtige Voreinstellung ist Absicht: Ein automatischer Neuversuch eines Syncs, der in Kundendatensätze schreibt, ist selbst ein Schreibvorgang.

Die Stille, die niemand sah

Der Einwand ist berechtigt und lautet so: „Ein Breaker, der nach fünf Fehlern pausiert, hätte auch den zweiten Vorfall erkannt. Warum einen zweiten Wächter bauen?“ Weil der zweite Vorfall nicht nach Fehler aussah. Über einen langen Zeitraum hatte ein Workspace einen aktiven Sync, einen wachsenden Stapel von Fehlerzeilen und nicht einen einzigen Erfolg, niemals, und nichts schlug Alarm. Jedes vorhandene Signal hing an Aktivität: Ein Lauf schlug fehl, ein Webhook kam an, ein Budget wurde überschritten. Null Erfolge sind kein Ereignis. Sie sind das Fehlen eines Ereignisses, und ein Breaker läuft nur, wenn ein Fehler erfasst wird. Ein toter Scheduler, eine festgefahrene Queue, ein Worker, der nie hochfährt: Sie alle erfassen nichts, und ein Breaker wacht nie auf.

Die Totmanneinrichtung einer Lokomotive funktioniert andersherum. Die Person am Führerstand muss sie ständig drücken, und Stille hält den Zug an. Die Sync-Version läuft einmal pro Stunde, jeweils dreizehn Minuten nach der vollen Stunde. Für jeden Workspace mit einem aktiven Sync stellt sie eine einzige Frage: Ist in den letzten vierundzwanzig Stunden irgendein Lauf erfolgreich gewesen? Ein Workspace, dessen ältester aktiver Sync vor weniger als einem Tag aktiviert wurde, bleibt unberührt; er hatte noch keinen Tag Zeit für einen Erfolg. Einer, der älter und still ist, bekommt einen kritischen Alarm. Der Alarm sagt, welche Stille es ist: Läufe erfasst, aber keiner erfolgreich, oder gar keine Läufe erfasst. Er löst einmal pro Vorfall aus. Eine Markierung in Redis hält ihn geschlossen bis zum ersten erfolgreichen Lauf, dann wird sie gelöscht, sodass der nächste Vorfall wieder alarmiert.

Beide Wächter kamen in einem Commit, im Nachhinein. Der Kommentar des Breakers sagt, er hätte den ersten Vorfall bei Lauf fünf gestoppt. Der Kommentar des Dead-Man-Alarms sagt, ein Sync, der vor langer Zeit aktiviert wurde und null Läufe hat, muss alarmieren. Keiner von beiden existierte, als er gebraucht wurde. Wir haben sie danach geschrieben, mit beiden Vorfällen im Dateikopf festgehalten, damit niemand vergisst, warum.

Was gebaut ist und was nicht

Gebaut, Stand heute:

  • Der Cursor wird am Ende jedes sauberen Batches gespeichert, und noch einmal bei einem Absturz.
  • Der Zustellschlüssel jedes Datensatzes wird aus dem Seiten-Cursor und seiner Position abgeleitet und vor jedem Aufruf des Ziels gegen ein Sieben-Tage-Ledger geprüft.
  • Ein Lease pro Sync, mit einer Lebensdauer von fünfzehn Minuten.
  • Fünf Versuche pro Lauf, mit Backoff ab sechzig Sekunden.
  • Ein Breaker, der den Sync nach fünf aufeinanderfolgenden Fehlern pausiert und festhält, warum.
  • Ein stündlicher Alarm bei vierundzwanzig Stunden ohne Erfolg.

Nicht gebaut, und gut zu wissen, bevor du dich darauf verlässt:

  • Der Dead-Man-Alarm beobachtet den Workspace, nicht den Sync. Ein gesunder Sync verdeckt einen stillen Nachbarn im selben Workspace.
  • Sein Alarm erreicht die Betreiber:innen von Tajo: immer eine Logzeile, und Sentry, ein Webhook oder eine E-Mail, wo konfiguriert. Er erscheint nicht auf der Seite des Syncs. Die Pause des Breakers schon.
  • Nichts startet einen pausierten Sync neu. Das tut eine Person.
  • Die Wiederherstellung nach einem harten Abbruch ist nicht schnell. Der Lease kann fünfzehn Minuten halten, und das Aufräumen wartet eine Stunde.
  • Beide Schwellenwerte, fünf Fehler und vierundzwanzig Stunden, sind Einstellungen des Deployments. Du kannst sie nicht pro Sync festlegen.

Eine lange Kette fast perfekter Schritte scheitert trotzdem; der Ausweg ist, den Endzustand zu definieren und ihn zu prüfen. Dieser Artikel ist die operative Hälfte davon. Definiere, was ein toter Lauf hinterlässt, und prüfe auf das Nichts, das Stille hinterlässt.

Crusoes Schlusssatz war, dass „Ausfälle unvermeidlich sind“. Unserer geht einen Schritt weiter. Der Absturz war die leichte Hälfte, weil er Zeilen hinterließ. Die Stille war die Hälfte, nach der wir suchen mussten.

Häufig gestellte Fragen

Was passiert mit einem laufenden Sync, wenn der Worker mitten in einem Batch ausfällt?

Der Cursor zeigt weiterhin auf den Anfang des unterbrochenen Batches, denn Tajo bewegt ihn erst, wenn ein Batch sauber angekommen ist. Der nächste Lauf liest dieselbe Seite noch einmal. Jeder Datensatz trägt einen Zustellschlüssel, der aus dem Seiten-Cursor und seiner Position abgeleitet wird, und ein Ledger hält jeden abgeschlossenen Schreibvorgang sieben Tage lang fest. Bereits geschriebene Datensätze werden deshalb aus dem Ledger wiedergegeben, statt ein zweites Mal an das Ziel gesendet zu werden.

Warum schreibt ein wiederholter Sync denselben Kontakt nicht zweimal?

Vor jedem Aufruf des Ziels prüft der Lauf den Zustellschlüssel des Datensatzes gegen ein Ledger. Der Schlüssel ist ein Hash aus dem Sync, der Quelle, dem Seiten-Cursor, der Position auf der Seite und dem Datensatz selbst. Ein Absturz, ein Wiederholungsversuch eine Minute später und ein geplanter Lauf am nächsten Tag leiten für denselben Datensatz also denselben Schlüssel ab. Ein Schlüssel, der innerhalb des Sieben-Tage-Fensters bereits als abgeschlossen markiert ist, wird nicht erneut gesendet.

Was ist ein Dead-Man-Alarm für einen Sync?

Eine Prüfung, die bei Abwesenheit auslöst statt bei einem Fehler. Einmal pro Stunde sieht sich Tajo jeden Workspace mit einem aktiven Sync an und alarmiert, wenn in den letzten vierundzwanzig Stunden kein Lauf erfolgreich war, auch dann, wenn gar kein Lauf erfasst wurde. Der Alarm löst einmal pro Vorfall aus und wird nach dem ersten erfolgreichen Lauf wieder scharf geschaltet. Es gibt ihn, weil ein Fehleralarm nur Fehler sehen kann, die auch erfasst wurden.

Beantrage frühzeitigen Zugang

Gib deinen Vornamen und deine E-Mail-Adresse ein. Wir senden dir einen Link, mit dem du deine Anfrage für den Tajo-Zugang bestätigst.

Brevo erhalten

Diese Seite als Markdown: worker-died-sync-didnt.md

Markdown wird geladen …
In Claude öffnen In ChatGPT öffnen