Zeitgesteuerte Software klingt einfach: Speichere einen Zeitpunkt und führe dann eine Aktion aus. In realen Systemen starten Prozesse neu, Jobs laufen doppelt, Uhren werden korrigiert und Wartungsfenster überdecken den geplanten Moment. Ein zuverlässiger Scheduler garantiert deshalb selten millisekundengenaue Einmaligkeit. Er kombiniert persistente Planung, wiederholbare Zustellung und idempotente Geschäftsoperationen zu einem belastbaren Ablauf.
„Wann“ und „was“ sollten getrennt gespeichert werden
Ein geplanter Eintrag braucht einen auslösenden Zeitpunkt oder eine Wiederholungsregel sowie eine stabile Beschreibung der beabsichtigten Aktion. Liegt die Planung nur in Prozessspeicher oder einem Betriebssystem-Cron, kann ein Deployment den Kontext verlieren.
Persistente Datensätze ermöglichen Audit, Neuplanung und Wiederaufnahme. Sie sollten Status, Versuche und einen Idempotenzschlüssel enthalten, ohne sensible Nutzdaten unnötig zu duplizieren.
At-least-once ist die realistische Zustellgarantie
Ein Worker kann die Aktion ausführen und abstürzen, bevor er den Job als abgeschlossen markiert. Das System wird ihn erneut zustellen. Verhindert man Wiederholung zu aggressiv, kann ein Absturz vor der Ausführung den Job dagegen dauerhaft verlieren.
Die Geschäftsoperation muss deshalb idempotent sein oder eine eindeutige Ausführungs-ID transaktional verwenden. „Exactly once“ entsteht nicht allein durch die Queue, sondern durch die gemeinsame Gestaltung von Zustellung und Seiteneffekt.
Leases verhindern parallele Arbeit, nicht jede Wiederholung
Mehrere Worker können fällige Jobs suchen. Ein atomarer Claim oder eine Lease weist einen Datensatz vorübergehend einem Worker zu. Läuft die Lease ab, darf ein anderer übernehmen. Das schützt vor dauerhaften Sperren nach einem Crash.
Ein langsamer erster Worker kann nach Ablauf trotzdem weiterarbeiten. Fencing Tokens oder transaktionale Statusprüfungen verhindern, dass ein veralteter Besitzer später ein neueres Ergebnis überschreibt.
Wall Clock plant, monotone Zeit misst
„Sende am 10. Juni um 9 Uhr“ bezieht sich auf Kalenderzeit. Ein 30-Sekunden-Timeout sollte dagegen eine monotone Uhr verwenden. Springt die Systemzeit durch Synchronisierung, darf ein laufender Request nicht plötzlich negative oder stark verlängerte Dauer erhalten.
Scheduler vergleichen persistente Instants mit Wall Clock, müssen aber Clock-Skew tolerieren. Laufzeit, Backoff und Lease-Dauer gehören soweit möglich auf eine monotone Basis.
Verspätete Jobs brauchen eine fachliche Policy
Nach zwei Stunden Ausfall sind viele Jobs überfällig. Nicht jede Aktion sollte sofort nachgeholt werden. Eine Erinnerung kann noch sinnvoll sein, ein kurzfristiger Preiswechsel vielleicht nicht. Manche wiederkehrenden Jobs benötigen genau einen Catch-up, andere jeden verpassten Lauf.
Der Datensatz sollte Deadline oder maximal zulässige Verspätung ausdrücken. So entscheidet Geschäftslogik und nicht ein zufälliges Scheduler-Default über das Verhalten.
Wiederholungen speichern die Kalenderabsicht
Für „täglich um 9 Uhr Europe/Berlin“ muss die Zone erhalten bleiben. Wird nur jeweils 24 Stunden addiert, verschiebt sich die lokale Uhrzeit an DST-Grenzen. Nach jedem Lauf sollte der nächste Termin aus Regel und Kalender neu berechnet werden.
Die Regel benötigt außerdem eine Entscheidung für nicht existente und doppelte lokale Zeiten. Diese Policy ist Teil des Produkts und gehört in Tests und Benutzeroberfläche.
Cron-Ausdrücke sind kompakt, aber nicht vollständig
Cron beschreibt wiederkehrende Kalendermuster, sagt aber wenig über Zeitzone, Catch-up, Gleichzeitigkeit oder Fehlerbehandlung. Verschiedene Implementierungen unterscheiden sich bei Sekundenfeldern und Sonderzeichen.
Eine API sollte nicht nur einen Cron-String speichern, sondern dessen Dialekt, Zone und Ausführungspolitik. Für einfache Nutzeraufgaben ist ein strukturiertes Modell wie „werktags um 08:30“ verständlicher.
Retries brauchen Backoff und Grenzen
Ein externer Dienst kann vorübergehend ausfallen. Sofortige endlose Wiederholungen erhöhen die Last und blockieren andere Jobs. Exponentieller Backoff mit Jitter verteilt Versuche; eine Maximalzahl oder Deadline führt anschließend in einen sichtbaren Fehlerzustand.
Nicht jeder Fehler ist retryfähig. Validierungsfehler und dauerhaft fehlende Berechtigungen sollten direkt beendet werden. Der Worker braucht eine Klassifikation statt eines pauschalen Catch-and-retry.
Transaktionen schützen Zustandsübergänge
Wenn eine Aktion Daten ändert und ein Folgeevent veröffentlicht, kann zwischen beiden Schritten ein Crash auftreten. Transactional Outbox oder vergleichbare Muster speichern Geschäftsänderung und auszuliefernde Nachricht atomar. Ein separater Publisher übernimmt die wiederholbare Zustellung.
Damit wird Zeitplanung Teil einer nachvollziehbaren Zustandsmaschine. Statusübergänge und Events lassen sich rekonstruieren, statt auf eine flüchtige Reihenfolge von Netzwerkaufrufen zu vertrauen.
Clock-Skew darf keine Autorisierung öffnen
Bei Ablaufzeiten für Tokens oder signierte Links wird oft eine kleine Toleranz erlaubt. Diese darf nicht so groß sein, dass kurze Sicherheitsgrenzen bedeutungslos werden. Alle Hosts brauchen Zeitsynchronisierung und Alarme bei ungewöhnlicher Abweichung.
Für verteilte Reihenfolge sind Datenbanksequenzen oder logische Versionen geeigneter als lokale Timestamps. Zeit ist ein Signal, keine universelle Konsensquelle.
Beobachtbarkeit misst mehr als Fehlerzahlen
Wichtige Metriken sind Planungsverzug, tatsächlicher Start gegenüber Sollzeit, Laufzeit, Retryanzahl, Queuealter und Zahl überfälliger Jobs. Ein System kann formal fehlerfrei sein und trotzdem Erinnerungen Stunden zu spät senden.
Logs sollten Job-ID, geplanten Zeitpunkt, Versuch und Ergebnis korrelieren. Payloads können sensibel sein und müssen nicht vollständig gespeichert werden. Ein Audit zeigt, ob eine Aktion geplant, beansprucht, ausgeführt oder verworfen wurde.
Tests brauchen eine kontrollierbare Uhr
Code, der überall direkt „jetzt“ liest, ist schwer deterministisch zu testen. Eine injizierbare Clock erlaubt Sprünge über Ablaufgrenzen, DST und Retryfenster. Tests laufen schnell, ohne real zu warten.
Integrationstests sollten Crashs an kritischen Stellen simulieren: vor und nach dem Seiteneffekt, vor dem Acknowledge und während einer Lease. Gerade diese Zwischenzustände bestimmen die Zuverlässigkeit.
Zuverlässige Zeitlogik ist explizite Zustandslogik
Ein belastbares System speichert Absicht, akzeptiert mögliche Wiederholung, macht Operationen idempotent und entscheidet bewusst über verspätete Ausführung. Kalenderregeln, Dauer und Wall Clock werden nicht vermischt.
Damit verschwindet die Illusion, ein Timer allein könne Geschäftsprozesse garantieren. Zuverlässigkeit entsteht aus persistenter Planung, atomaren Übergängen, kontrollierten Retries und einer Beobachtung, die Sollzeit und tatsächliche Wirkung miteinander vergleicht.