5 Cron-Ausdrucksmuster aus echten Produktionssystemen
Cron-Ausdrücke wirken einfach — fünf Felder, ein paar Operatoren — doch Produktionsteams machen immer wieder dieselben Fehler: Jobs, die in bestimmten Monaten stillschweigend übersprungen werden, überlappende Läufe, die einen gemeinsamen Zustand korrumpieren, und Hunderte von Jobs, die alle gleichzeitig aufwachen und eine Datenbank überlasten. Der Ausdruck selbst ist selten das Problem; das Problem ist das fehlende Durchdenken der Grenzfälle.
Dieser Leitfaden behandelt fünf Cron-Muster, die Engineering-Teams tatsächlich in der Produktion einsetzen, mit den Überlegungen dahinter und den Fallen, die es zu vermeiden gilt. Verwenden Sie den Toova Cron Parser, um jeden Ausdruck zu überprüfen und die nächsten zehn Ausführungszeiten vor dem Deployment zu sehen.
Anatomie eines Cron-Ausdrucks
Ein Standard-Cron-Ausdruck besteht aus fünf durch Leerzeichen getrennten Feldern:
*/15 * * * *
| | | | |
| | | | +---- day of week (0-6, 0=Sunday)
| | | +------- month (1-12)
| | +---------- day of month (1-31)
| +------------- hour (0-23)
+----------------- minute: every 15th (0, 15, 30, 45)
Jedes Feld akzeptiert: einen bestimmten Wert (5), einen Platzhalter (*), einen Bereich (1-5), eine Liste (1,3,5) oder einen Schrittweitenoperator (*/15). Einige Implementierungen fügen ein sechstes Feld für Sekunden hinzu; andere (wie GitHub Actions) erwarten genau fünf. Prüfen Sie immer, welchen Dialekt Ihr Scheduler verwendet, bevor Sie komplexe Ausdrücke schreiben.
Muster 1: Alle 15 Minuten — */15 * * * *
*/15 * * * *
| | | | |
| | | | +---- day of week (0-6, 0=Sunday)
| | | +------- month (1-12)
| | +---------- day of month (1-31)
| +------------- hour (0-23)
+----------------- minute: every 15th (0, 15, 30, 45) Der bevorzugte Ausdruck für Queue-Prozessoren, Heartbeat-Checks und Polling-Jobs, die häufig, aber nicht kontinuierlich laufen müssen. Er wird bei :00, :15, :30 und :45 jeder Stunde, jeden Tag ausgelöst.
In einer Crontab-Datei:
# crontab entry — run batch processor every 15 minutes
*/15 * * * * /opt/app/bin/process-queue.sh >> /var/log/queue.log 2>&1 In Node.js:
// Node.js with node-cron
import cron from 'node-cron';
cron.schedule('*/15 * * * *', async () => {
const batch = await db.query(
'SELECT * FROM jobs WHERE status = $1 LIMIT 100',
['pending']
);
await processBatch(batch);
}); Das Thundering-Herd-Problem. Wenn Sie Hunderte von Workern mit demselben */15-Ausdruck betreiben, werden sie alle gleichzeitig bei :00, :15, :30 und :45 ausgelöst. Trifft jeder Worker eine gemeinsame Datenbank, entstehen vier Lastspitzen pro Stunde statt einer gleichmäßigen Last. Die Lösung besteht darin, vor jedem Lauf einen zufälligen Jitter einzufügen:
// Add jitter to avoid thundering herd on distributed systems
cron.schedule('*/15 * * * *', async () => {
const jitterMs = Math.floor(Math.random() * 30_000); // up to 30s
await sleep(jitterMs);
await processBatch();
}); Bis zu 30 Sekunden Jitter verteilen die Spitze über ein 30-Sekunden-Fenster, während der Job innerhalb des erwarteten 15-Minuten-Zyklus bleibt. Für eine ausgefeiltere Staffelung schauen Sie sich Muster 5 (Jenkins H-Syntax) an.
Verwenden Sie den Timestamp Converter, um Ihren nächsten geplanten Lauf von Unix-Epoch in eine lesbare Zeit umzurechnen, wenn Sie Job-Timing debuggen.
Muster 2: Werktags zu Geschäftszeiten — 0 9 * * 1-5
0 9 * * 1-5
| | | | |
| | | | +-- day of week: Monday–Friday (1-5)
| | | +------ month: every month
| | +---------- day of month: every day
| +-------------- hour: 9
+------------------ minute: 0 (exactly on the hour) Dieser Ausdruck wird genau einmal, um 9:00 Uhr, Montag bis Freitag ausgelöst. Er ist das Standardmuster für: das Versenden täglicher Digest-E-Mails, das Ausführen von Berichten während der Geschäftszeiten, das Auslösen von Workflows, die eine menschliche Überprüfung während des Arbeitstags erfordern, und das Deployment auf Staging zu Beginn des Engineering-Tags.
In einem Kubernetes CronJob:
# Kubernetes CronJob — weekday digest email at 9 AM UTC
apiVersion: batch/v1
kind: CronJob
metadata:
name: digest-email
spec:
schedule: "0 9 * * 1-5"
jobTemplate:
spec:
template:
spec:
containers:
- name: mailer
image: myapp/mailer:latest
command: ["/app/send-digest"]
restartPolicy: OnFailure Zeitzonenfalle. Kubernetes CronJob-Zeitpläne laufen in der Cluster-Zeitzone, die standardmäßig UTC ist. "9 Uhr UTC" entspricht 5 Uhr Eastern im Winter und 4 Uhr Pacific. Ihre Nutzer erhalten ihren "Morgenüberblick" mitten in der Nacht. Verwenden Sie eine Cron-Bibliothek, die IANA-Zeitzonenstrings akzeptiert:
// Handle timezone — "9 AM UTC" is rarely what users expect
// Use a library that understands IANA timezones
import { CronJob } from 'cron';
const job = new CronJob(
'0 9 * * 1-5',
() => sendDailyDigest(),
null,
true,
'America/New_York' // runs at 9 AM Eastern, not 9 AM UTC
); Der Timezone Converter hilft Ihnen, das UTC-Äquivalent für eine bestimmte lokale Uhrzeit in jeder IANA-Zeitzone zu finden.
Wochentags-Nummerierung. Die Standard-Cron-Definition verwendet 0 oder 7 für Sonntag. Einige Implementierungen (Quartz, cron4j) verwenden stattdessen 1 für Montag. Prüfen Sie die Dokumentation Ihres Schedulers und testen Sie mit dem Cron Parser vor dem Deployment.
Muster 3: Nächtlicher Batch — 0 22 * * *
0 22 * * *
| | | | |
| | | | +-- day of week: any
| | | +------ month: any
| | +---------- day of month: any
| +-------------- hour: 22 (10 PM)
+------------------ minute: 0 Wird einmal täglich um 22 Uhr ausgeführt. Der nächtliche Batch ist eines der häufigsten Muster in der Datentechnik: Tagesereignisse aggregieren, Suchindizes neu aufbauen, Berichte generieren, Logs archivieren, End-of-Day-Benachrichtigungen versenden.
Warum 22 Uhr statt Mitternacht? Aus zwei Gründen. Erstens gibt 22 Uhr einen Puffer, bevor der Tag wechselt, was die Wahrscheinlichkeit verringert, spät eintreffende Ereignisse aufzunehmen. Zweitens bedeutet ein schwerer Job um Mitternacht, dass er am ersten jeden Monats mit der Monatsendabrechnung (Muster 4) konkurriert.
Die drei häufigsten Möglichkeiten, einen Tagesjob zu schreiben:
# Three ways to write "nightly at 10 PM":
0 22 * * * # standard cron
@daily # midnight — NOT the same as 10 PM
0 22 * * * # always prefer explicit time over @macros for non-midnight Überlappungsverhinderung. Ein nächtlicher Job, der normalerweise 45 Minuten dauert, kann gelegentlich 2 Stunden dauern — und wenn er noch läuft, wenn der nächste nächtliche Trigger auslöst, werden beide Jobs dieselben Daten modifizieren. Verwenden Sie eine verteilte Sperre:
// Idempotency check — prevent double-processing if job overlaps
async function runNightlyBatch() {
const lock = await redis.set(
'nightly-batch-lock',
process.pid,
'EX', 3600, // TTL: 1 hour
'NX' // only set if not exists
);
if (!lock) {
console.log('Batch already running, skipping.');
return;
}
try {
await processNightlyData();
} finally {
await redis.del('nightly-batch-lock');
}
}
Das Redis-Flag NX stellt sicher, dass immer nur ein Prozess die Sperre halten kann. Der TTL verhindert, dass ein abgestürzter Job die Sperre dauerhaft blockiert.
Muster 4: Monatsabrechnung — 0 0 1 * *
0 0 1 * *
| | | | |
| | | | +-- day of week: any
| | | +------ month: any
| | +---------- day of month: 1 (first)
| +-------------- hour: 0 (midnight)
+------------------ minute: 0 Wird um Mitternacht am ersten Tag jedes Monats ausgelöst. Das klassische Muster für: das Erstellen monatlicher Rechnungen, das Zurücksetzen von Nutzungskontingenten, die Durchführung der Monatsschluss-Abstimmung und das Archivieren der Daten des Vormonats.
Cron-Fallen für monatliche Jobs:
# First of month at midnight UTC
0 0 1 * *
# ---- TRAPS TO AVOID ----
# This does NOT run "every month" reliably:
0 0 29-31 * * # months with fewer days silently skip
# Quarter billing (Jan, Apr, Jul, Oct):
0 0 1 1,4,7,10 *
# End of month is tricky — there is no "last day" in standard cron
# Use @reboot + application logic, or a cron that runs daily and
# checks whether today is the last day of the month in code. Monatsendabrechnung. Wenn Sie am letzten Tag jedes Monats abrechnen müssen (anstatt am ersten), gibt es keinen sauberen Cron-Ausdruck dafür. Der Workaround:
// Application-level "last day of month" check
cron.schedule('0 23 28-31 * *', async () => {
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
// If tomorrow is the 1st, today is the last day of the month
if (tomorrow.getDate() === 1) {
await runEndOfMonthBilling();
}
}); Dies wird an den Tagen 28–31 jedes Monats ausgelöst und prüft dann im Code, ob heute tatsächlich der letzte Tag ist. Es läuft bis zu viermal unnötigerweise (in Monaten mit weniger als 31 Tagen), aber die Prüfung ist günstig und die Logik klar.
Für eine quartalsmäßige Abrechnung verwenden Sie den Listenoperator im Monatsfeld: 0 0 1 1,4,7,10 * (Januar, April, Juli, Oktober).
Muster 5: Hashed Schedule (Jenkins H-Syntax) — H/30 * * * *
H/30 * * * *
|
+--- H means Jenkins picks a random minute per job.
H/30 = once every 30 minutes, starting at H.
Different jobs get different H values, staggering load.
Das H-Token ist eine Jenkins-spezifische Erweiterung der Cron-Syntax. Es ersetzt H durch eine stabile pseudozufällige Zahl, die aus dem Job-Namen abgeleitet wird. H/30 im Minutenfeld bedeutet "einmal alle 30 Minuten, ab einer Minute, die durch einen Hash des Job-Namens bestimmt wird."
In einem Jenkinsfile:
// Jenkinsfile — pipeline triggered every 30 minutes, staggered
pipeline {
triggers {
cron('H/30 * * * *')
}
stages {
stage('Deploy') {
steps {
sh './deploy.sh'
}
}
}
} Warum das wichtig ist. Jenkins-Installationen haben oft Hunderte von Pipelines. Wenn jede Pipeline */30 * * * * verwendet, werden alle bei :00 und :30 ausgelöst, was massive Lastspitzen im Agent-Pool und in allen nachgelagerten Systemen erzeugt. Mit H/30 erhält jeder Job einen anderen Versatz — einer bei :03/:33, ein anderer bei :17/:47 — und verteilt die Last gleichmäßig über die Stunde.
GitHub Actions unterstützt H nicht. Um mehrere Actions-Workflows zu staffeln, wählen Sie manuell verschiedene feste Minuten-Versätze:
// GitHub Actions — H is not supported; use schedule with offset manually
// To stagger two jobs: pick different fixed minutes
name: Job A
on:
schedule:
- cron: '5 * * * *' # runs at :05 of every hour
---
name: Job B
on:
schedule:
- cron: '35 * * * *' # runs at :35 — 30 minutes after Job A Dies ist weniger elegant als Jenkins H, erzielt aber denselben Effekt für eine kleine Anzahl von Workflows. Bei einer großen Anzahl von Workflows sollten Sie einen externen Orchestrator in Betracht ziehen, der nativ verteiltes Scheduling unterstützt.
Übergreifende Aspekte
Idempotenz ist nicht verhandelbar
Jeder Cron-Job sollte sicher zweimal ausgeführt werden können. Infrastruktur versagt, Scheduler starten neu, und verteilte Sperren laufen gelegentlich ab. Gestalten Sie Jobs so, dass die zweimalige Ausführung — mit denselben Eingaben, zur selben Zeit — dasselbe Ergebnis liefert. Verwenden Sie Upserts statt Inserts, Check-before-Act-Muster und Idempotenz-Schlüssel für externe API-Aufrufe.
Dead-Letter-Benachrichtigungen
Ein Cron-Job, der lautlos fehlschlägt, ist schlimmer als einer, der gar nicht läuft. Senden Sie eine Benachrichtigung an eine Dead-Letter-Queue oder ein Monitoring-System, wenn ein Job fehlschlägt oder sein erwartetes Zeitfenster nicht einhält. Tools wie Sentry Crons, Healthchecks.io und Cronitor fügen einen Heartbeat-Check hinzu: Der Job pingt eine URL, wenn er startet und wenn er endet. Wenn der Abschluss-Ping nicht innerhalb der Frist eintrifft, wird eine Benachrichtigung ausgelöst.
Zeitplan protokollieren
Geben Sie zu Beginn und am Ende jedes Cron-Laufs eine strukturierte Log-Zeile aus, einschließlich des Ausdrucks, des ausgelösten Zeitstempels (nicht der aktuellen Zeit, die aufgrund von Jitter leicht abweichen kann) und der Dauer. Dies erleichtert es, einen nachgelagerten Vorfall — "die Datenbank verlangsamte sich um 22 Uhr" — mit einem bestimmten Batch-Job-Lauf zu korrelieren.
Kurzreferenz
Parsen und validieren Sie jeden Ausdruck mit dem Cron Parser. Konvertieren Sie geplante Zeiten zwischen Zeitzonen mit dem Timezone Converter. Wenn Ihre Job-Logs Unix-Zeitstempel enthalten, wandelt der Timestamp Converter diese sofort in lesbare Datumsangaben um.
Eine umfassende Referenz der Cron-Syntax und häufige Beispiele bietet crontab.guru als kanonische Kurzreferenz. Für die Jenkins-H-Syntax und ihre vollständige Spezifikation lesen Sie die Jenkins Pipeline Cron-Syntax Dokumentation.