ergon_job_notifier (ergon v0.5.0)

View Source

Turns ergon.jobs into a reactive queue: one LISTEN connection that wakes the right workers the instant a runnable job lands, instead of making them wait out their poll interval.

One instance runs per node, subscribing to the fixed channel ergon_job_available on the node's shared ergon_listener connection. The emitting half is the pg_cron tick installed by priv/migrations/cron: ergon.notify_pending_jobs() runs every second and fires pg_notify('ergon_job_available', queue) once per queue holding immediately runnable work. Each notification's payload is the queue name only, never job data or tenant, since NOTIFY bypasses row-level security, and the notifier hands it to ergon_worker_registry:wake/1.

Writers never call pg_notify

This is the architectural point, and it is easy to undo by accident. A transaction with a pending NOTIFY takes the global notification-queue lock at commit, which serialises every notifying commit. A trigger firing pg_notify per insert therefore caps enqueue throughput no matter how much hardware is available. The 1 s cron tick is the batching mechanism that avoids it: at most one notification per queue per second regardless of how many jobs arrive, so a batch enqueue of a thousand jobs produces a single wake.

The poll is still the durable path

ergon.jobs is the durable fact and checkout's FOR UPDATE SKIP LOCKED is the reliable puller, so a notification is a hint that cannot be depended on. Workers keep their periodic fallback poll, which alone drains everything correctly. That fallback covers the gap before the listener connects, every reconnect window, and every deployment without pg_cron, where the tick never runs and no notification ever fires. A dropped notification costs latency, never a stuck job.

Because the tick is level-triggered on "runnable work exists" rather than edge- triggered on an insert, it keeps re-waking workers until a queue is drained, and a future-scheduled retry wakes its workers on the first tick after scheduled_at passes, precisely when it is due.

Configuration

Optional. Disabled, workers simply poll: still fully correct, only slower.

{ergon, [{ergon_job_notifier, [{enabled, false}]}]}

A note on connection poolers

LISTEN needs a stable, dedicated backend, which is why ergon_listener's connection sits outside the pool. But if the PG* environment points at a transaction-mode pooler such as PgBouncer, that dedicated connection still goes through the pooler and LISTEN silently never receives anything. Point Ergon at a session-mode route when a transaction-mode pooler sits in front of PostgreSQL.

Summary

Functions

The single LISTEN/NOTIFY channel job wake-ups travel on.

Whether the host has left the reactive fast path enabled. Defaults to true.

Types

opts()

-type opts() :: #{config => pgo:pool_config()}.

Functions

channel()

-spec channel() -> binary().

The single LISTEN/NOTIFY channel job wake-ups travel on.

enabled()

-spec enabled() -> boolean().

Whether the host has left the reactive fast path enabled. Defaults to true.

handle_call(Request, From, State)

handle_cast(Msg, State)

handle_info/2

init(Opts)

-spec init(opts()) -> {ok, map()}.

start_link()

-spec start_link() -> {ok, pid()} | {error, term()}.

start_link(Opts)

-spec start_link(opts()) -> {ok, pid()} | {error, term()}.