ADR-0011: single-home Claim eligibility (app.eligible_simulations)#8
Merged
Conversation
The per-User claim-eligibility predicate (state='queued', engine_version match,
per-user running-count < cap) plus the claim ordering (priority DESC,
created_at ASC) was duplicated across three runtimes that must agree: the Python
claim (runner db.py), the Go KEDA eligible-depth query, and the KEDA
scaledobject doc sample. Give it one home as a STABLE set-returning Postgres
function that owns the predicate + ordering but NOT row locking or the UPDATE.
- db/migrations/0006_eligible_simulations.sql: new
app.eligible_simulations(engine_version, user_cap) RETURNS SETOF
app.simulations (idempotent, CREATE OR REPLACE).
- runner/synergy_runner/db.py: rewrite CLAIM_SQL to a membership-gate form —
JOIN the function to the base table and keep FOR UPDATE OF s2 SKIP LOCKED on
app.simulations so the row lock stays on the base table (wrapping the function
in FOR UPDATE silently drops the lock on PG16 -> double-claims). Advisory-lock
H-2 logic untouched.
- internal/controller/runnerpool_controller.go: eligibleQuery becomes
SELECT count(*) FROM app.eligible_simulations('<ver>', <cap>).
- config/keda/scaledobject.yaml: doc sample query + header updated to the
function.
Verified on PG16: all migrations 0001-0006 apply in order; EXPLAIN of the new
claim shows a LockRows node on the base table; a two-session concurrency test
claims two different rows (no block/overwrite); eligible-depth count correct.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019p51SPdbrjmGXJVeZnwTrD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements ADR-0011 — gives the per-User Claim-eligibility predicate a single home.
Change
db/migrations/0006_eligible_simulations.sql—app.eligible_simulations(version, cap)owns the predicate (state='queued' AND engine_version=p AND per-user running-count < cap) + ordering.runner/synergy_runner/db.py—CLAIM_SQLnow uses the function as a membership gate withFOR UPDATE OF s2 SKIP LOCKEDon the base table; advisory-lock H-2 wrapper unchanged.internal/controller/runnerpool_controller.go— KEDAeligibleQuery→SELECT count(*) FROM app.eligible_simulations(...).config/keda/scaledobject.yaml— doc sample updated.Why the membership-gate form (not the obvious wrap)
Wrapping
app.eligible_simulations(...) FOR UPDATE SKIP LOCKEDsilently drops the row lock on Postgres 16 (verified: noLockRowsnode, double-claims under concurrency). Locking + LIMIT stay on the base table.Verification (docker PG16)
EXPLAINshowsLockRowspreserved · two-session concurrency claims different rows ·eligible_simulationsreturns correct depth ·go build/testgreen. Python suite not run (no venv in worktree).Design rationale: ADR-0011. Validated under worktree isolation in #7.
🤖 Generated with Claude Code
https://claude.ai/code/session_019p51SPdbrjmGXJVeZnwTrD