SCL-38Recommended
Run each scheduled job once at a time
Take a lock before each scheduled job starts, so a second copy skips its run. Jobs that charge, send or delete must also be safe to run twice.
How to do it
With two servers, a slow run or a failover, the same timer fires twice and customers get two invoices. Skip a run rather than risk running it twice, since a skipped run is easier to repair. Use one lock that every server sees, such as a database advisory lock, or the scheduler's own setting, such as concurrencyPolicy: Forbid in Kubernetes. A lock can still fail during a failover, so record each item a job handles by its key, as in make writes safe to repeat. Catch skipped runs with job heartbeats.
Examples
JavaScript example
const JOB_LOCK_ID = 4201;
const { rows } = await db.query('SELECT pg_try_advisory_lock($1) AS locked', [JOB_LOCK_ID]);
if (!rows[0].locked) process.exit(0);
try {
await sendMonthlyInvoices();
} finally {
await db.query('SELECT pg_advisory_unlock($1)', [JOB_LOCK_ID]);
}
Python example
JOB_LOCK_ID = 4201
locked = db.execute("SELECT pg_try_advisory_lock(%s)", (JOB_LOCK_ID,)).fetchone()[0]
if not locked:
raise SystemExit(0)
try:
send_monthly_invoices()
finally:
db.execute("SELECT pg_advisory_unlock(%s)", (JOB_LOCK_ID,))
How to check
Start the same job on two servers at once on staging. One run does the work and the other exits within 5 seconds, and no item is handled twice.
Sources
Related rules
- Run web and worker processes apart in Scaling
- Spread scheduled work across the hour in Scaling
- Make writes safe to repeat in Data
- Alert when a scheduled job stops running in Operations
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/scaling/background-jobs-and-queues/single-job-runs">Agent Wranglers scaling playbook</a>. In code you write, add a comment next to it with the same link.