Skip to content

Centralized job locking into one Locker+JobBuffer per process#122

Draft
stephenbinns wants to merge 3 commits into
masterfrom
centralize-locker-job-buffer
Draft

Centralized job locking into one Locker+JobBuffer per process#122
stephenbinns wants to merge 3 commits into
masterfrom
centralize-locker-job-buffer

Conversation

@stephenbinns

@stephenbinns stephenbinns commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

As an opt in we can replace the per-worker-thread Locker (each thread independently running its own recursive-CTE lock query) with a single Locker per process that batch-locks jobs into a shared JobBuffer, which worker threads then pull from without ever querying Postgres themselves. This collapses lock queries from one-per-worker-thread to one-per-process, addressing lock contention that scales worse as worker count grows.

Also fixes two related correctness issues surfaced while building this: pg_try_advisory_lock is reentrant within a session, so the Locker now tracks and excludes job_ids it already holds from every lock query (otherwise the cursor-reset-on-empty retry could re-lock and double-dispatch an in-flight job); and the poll backoff now always waits a full cycle when the buffer isn't full, rather than busy-looping when nothing is found.

stephenbinns and others added 3 commits July 14, 2026 15:36
Replaces the per-worker-thread Locker (each thread independently running
its own recursive-CTE lock query) with a single Locker per process that
batch-locks jobs into a shared JobBuffer, which worker threads then pull
from without ever querying Postgres themselves. This collapses lock
queries from one-per-worker-thread to one-per-process, addressing lock
contention that scales worse as worker count grows.

Also fixes two related correctness issues surfaced while building this:
pg_try_advisory_lock is reentrant within a session, so the Locker now
tracks and excludes job_ids it already holds from every lock query
(otherwise the cursor-reset-on-empty retry could re-lock and
double-dispatch an in-flight job); and the poll backoff now always waits
a full cycle when the buffer isn't full, rather than busy-looping when
nothing is found.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds an architecture: :legacy|:centralized option to WorkerGroup.start,
a matching -a/--architecture CLI flag (and QUE_ARCHITECTURE env var) on
bin/que, and documents both in the README. Defaults to :legacy (the
existing per-worker-thread locking behaviour) so the new centralized
Locker+JobBuffer path from the previous commit is strictly opt-in until
it's been proven out, per the staged rollout this was originally planned
around.

The pre-centralization Locker/Worker are preserved verbatim as
LegacyLocker/LegacyWorker to back the :legacy path, reusing the existing
Locker/Worker Prometheus metric constants rather than declaring
duplicates.

Also parametrizes the integration suite to run against both
architectures, and fixes a latent test-isolation gap (InterruptibleSleepJob.log
was never reset between examples) that only surfaced once the same
example ran twice in one process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restructures the previous two commits for a cleaner diff: Locker and
Worker are now byte-identical to master again (the pre-centralization,
per-worker-thread implementation), and the new centralized
Locker+JobBuffer architecture lives entirely in new CentralizedLocker/
CentralizedWorker classes instead of overwriting the originals.

WorkerGroup's architecture: toggle now selects between Locker/Worker
(:legacy) and CentralizedLocker/CentralizedWorker (:centralized).
CentralizedLocker/CentralizedWorker reuse Locker/Worker's existing
Prometheus metric constants rather than redeclaring them, so dashboards
don't change based on which architecture is selected.

Also reverts lock_job's cursor predicate back to `>=` (matching master
exactly - it's only used by the untouched legacy path, which never
needed the `>` fix). find_job_to_lock keeps the `>` fix since it's
shared with the new batched adapter path where it's still required for
correctness; added a comment explaining why the two queries now differ.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@stephenbinns stephenbinns changed the title Centralize job locking into one Locker+JobBuffer per process Centralized job locking into one Locker+JobBuffer per process Jul 15, 2026
@stephenbinns
stephenbinns requested a review from BBrannick July 15, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant