Dispatch modes

Poll vs event dispatch — how the Flux server assigns work to workers, when to switch to event mode, and the LISTEN/NOTIFY mechanics behind it.

The server assigns executions to workers through one of two dispatch strategies, selected by [flux.dispatch] mode. The default, poll, works everywhere including SQLite. event is the scalable mode for large worker fleets on PostgreSQL. Workers are oblivious to the choice — either way, work arrives as SSE events and is claimed the same way.

[flux.dispatch]
mode = "poll"              # "poll" (default) | "event"
batch_size = 64            # max executions claimed per dispatcher wakeup (event mode)
fallback_interval = 15.0   # safety-net tick in seconds (event mode)

Env vars: FLUX_DISPATCH__MODE, FLUX_DISPATCH__BATCH_SIZE, FLUX_DISPATCH__FALLBACK_INTERVAL.

Poll mode (default)

The legacy per-worker query loop: each connected worker’s dispatch path polls the database for claimable work — roughly 5 queries per worker every 0.5 s. That cost is per worker, so idle database load grows linearly with fleet size and degrades superlinearly under contention: at around 500 workers the query volume saturates the server’s database executor and submission latency climbs into minutes.

Poll mode’s virtue is universality. It has no PostgreSQL dependency, so it is the only mode that works on SQLite, and it needs no notification plumbing. For a handful of workers it is perfectly fine.

Event mode

FLUX_DISPATCH__MODE=event replaces the per-worker loops with one dispatcher task per server replica (flux/dispatcher.py). The dispatcher sleeps until woken, then batch-claims up to batch_size executions in a single SELECT ... FOR UPDATE SKIP LOCKED query and routes them to connected workers with free capacity slots.

Three things wake it:

  1. Local in-process signals — work submitted through this replica wakes its dispatcher immediately.
  2. PostgreSQL LISTEN/NOTIFY — cross-replica wakeups. A replica that accepts new work fires NOTIFY flux_work; a checkpoint landing on one replica fires NOTIFY flux_exec to wake sync/stream callers held open on another. Notifications are wakeups only — they carry no payload; the woken dispatcher re-queries the database for the actual work.
  3. The fallback tick — every fallback_interval seconds (default 15) the dispatcher wakes unconditionally, covering any missed notification (a dropped LISTEN connection, a notify racing a restart). Nothing is ever lost to a missed NOTIFY; it is at worst delayed by one tick.

Database load becomes a function of work volume, not fleet size: one dispatcher per replica, one batched claim query per wakeup.

Event mode is also where the two soft placement signals live — poll mode ignores both:

Multi-replica behavior

Event mode is what makes multiple server replicas coordinate cleanly:

Choosing and tuning

pollevent
Database supportSQLite and PostgreSQLPostgreSQL (falls back to tick-only wakeups without LISTEN/NOTIFY)
Idle DB load~5 queries per worker per 0.5 sOne dispatcher per replica, woken on demand
Dispatch latencySub-second (poll cadence)Immediate on wakeup; worst case one fallback_interval
Scales toSmall fleetsHundreds of workers per replica

Tuning rarely matters beyond the mode switch. Raise batch_size if a single wakeup regularly finds more than 64 claimable executions (high-volume bursts); lower fallback_interval only if you cannot tolerate a 15 s worst case on a missed notification, and remember each tick is a query per replica.

What’s next