Job queue with a single claim — Examples
Job queue with a single claim
Section titled “Job queue with a single claim”A worker queue often needs an invariant like “at most one pending job per (name, actor)” — while still allowing many finished rows with the same key. A partial unique constraint expresses exactly that: uniqueness that only applies to rows matching a filter.
enum QueueStatus { Pending, Running, Failed, Processed }
type Job { required name: str; required actor: str; payload: json; status: QueueStatus { default := QueueStatus.Pending }; claimed_at: datetime; processed_at: datetime;
# Uniqueness only among Pending rows — Running/Failed/Processed are unconstrained. constraint exclusive on (.name, .actor) filter .status = QueueStatus.Pending;}Postgres can’t attach a WHERE to a table UNIQUE, so this lowers to a
partial unique index:
CREATE UNIQUE INDEX IF NOT EXISTS "uq_job_name_actor" ON "job" ("name", "actor") WHERE (status = 'Pending');Enqueue is guarded
Section titled “Enqueue is guarded”A second Pending job for the same (name, actor) now raises a unique-violation
at insert time — the database refuses the duplicate:
insert Job { name := $name, actor := $actor, payload := $payload };Catch that error in your worker and treat it as “already queued”.
Claiming a job frees the key
Section titled “Claiming a job frees the key”Moving a job off Pending (to Running) drops it out of the partial index — so
the same key can be enqueued again while the first one is still in flight:
update Job filter .id = $id<uuid> set { status := QueueStatus.Running, claimed_at := now() };From generated code
Section titled “From generated code”await runner.query.enqueueJob({ name, actor, payload }); // no-op if a Pending twin existsawait runner.query.claimJob({ id }); // status → Running_, err := runner.Query.EnqueueJob(ctx, gen.EnqueueJobParams{Name: name, Actor: actor, Payload: payload})_, err = runner.Query.ClaimJob(ctx, gen.ClaimJobParams{ID: id})