No leader election
Nothing is elected. One atomic call to the store decides who runs each time, and a random delay spreads the work across instances.
import { RedisBackend, Scheduler } from 'anarchon';
const scheduler = new Scheduler({ backend: new RedisBackend({ redis: 'redis://localhost:6379' }) });
scheduler .every('sync', '10s', async (ctx) => { /* ... */ }) .cron('digest', '0 8 * * 1-5', async (ctx) => { /* ... */ }, { timezone: 'Europe/Athens' });
await scheduler.start();Run that code in three instances, and one instance runs “sync” every 10 seconds, not all three.
flowchart LR
subgraph service["Your service, 3 instances"]
A["Instance A"]
B["Instance B"]
C["Instance C"]
end
S[("Redis, Valkey,<br/>PostgreSQL or SQLite")]
A -- "claim the 12:00:10 run" --> S
B -- "claim the 12:00:10 run" --> S
C -- "claim the 12:00:10 run" --> S
S -- "won" --> B
S -. "taken" .-> A
S -. "taken" .-> C
No leader election
Nothing is elected. One atomic call to the store decides who runs each time, and a random delay spreads the work across instances.
Recovery after a runner fails
On Redis, Valkey, PostgreSQL and SQLite the winner renews a lease while the job runs. If it dies or freezes, another instance starts the run again as a new attempt, so handlers must tolerate retries. RabbitMQ and NATS only select the winner.
Long jobs don't pile up
On lease backends, the next run of a job is skipped while the current one holds a live lease. A frozen runner can briefly overlap the instance that replaced it.
Catch-up after downtime
On lease backends, the latest run that was due while every instance was down runs when the first
one comes back, if it was due within catchUpMs (5 minutes by default). Older runs are not replayed.
anarchon does not guarantee exactly-once side effects. Guarantees and limits states what is promised on each backend.