Docket.Postgres.ClaimPolicy.TenantFair.RingFunction (docket v0.1.0-dev)

Copy Markdown View Source

Defines docket_tenant_fair_claim, the database-side TenantFair admission engine.

One call accepts a point in time, expired-claim cutoff, requested outcome count, poison-attempt threshold, optional ready/expired preference, and trace flag. The function returns newly claimed leases, poisoned runs, or a normalized pre-admission error.

Scheduling model

docket_claim_schedule is a stable ring of claim partitions. A schedule row participates while unfinished_count > 0; membership therefore covers ready work, claimed work, future timers, and other nonterminal runs rather than only work that can produce an outcome immediately.

The singleton policy row stores scan_ring_position, the position inspected by the last committed TenantFair call. A call starts at its absolute successor, wraps to the smallest positive position when necessary, and materializes exactly the next S = 32 visits when demand remains. If the positive ring contains fewer than 32 partitions, traversal wraps repeatedly and may visit the same partition more than once. An empty ring produces no visits and leaves the cursor unchanged.

Entering a visit advances the call-local cursor. The visit counts even when partition authority is unavailable, the partition is capped, its work is dormant, its bounded candidate page is empty, every exact run lock is skipped, or every locked row fails authoritative recheck. If demand becomes satisfied, prefetched but unentered visits do not advance the cursor.

Each entered visit targets one partition and produces at most one grant. A grant contains between one and Q = 8 outcomes, limited further by the caller's remaining demand. Only a nonempty grant increments that partition's admission_epoch, and it increments the epoch exactly once regardless of the number of outcomes in the grant.

Policy and partition authority

The function requires a writable Read Committed transaction. It preserves a lower caller lock_timeout; otherwise it limits lock waits throughout the function to 250 ms.

It locks the singleton policy row, verifies that backend startup persisted an initialized TenantFair default, and never changes the cap or active engine. That lock serializes committed cursor movement and remains held until the surrounding transaction ends. A timeout during this narrow readiness/cursor phase returns lock_contention without inspecting a partition or changing policy, schedules, epochs, or runs. Later errors abort the statement.

For every entered visit, the function attempts the exact partition with FOR NO KEY UPDATE SKIP LOCKED. Under that authority it freshly counts healthy running rows carrying the durable tenant_admitted_at marker. The effective cap is the partition override or persisted default; promotion capacity is the nonnegative difference between that cap and the admitted count.

Ready and expired candidates

Ready work is an unclaimed running row whose wake_at is due. An admitted ready row already has tenant_admitted_at and may reacquire a transient claim at or above the cap. A queued ready row has no marker; its FIFO-head promotion atomically installs both the marker and a claim and requires a free logical-run slot. Cap debt blocks promotion, not reacquisition.

Expired work is a claimed running row whose claimed_at precedes the cutoff. Replacing its claim token is a count-neutral steal, so ordinary expired work remains admissible at or above the cap.

A ready or expired row at the attempt threshold becomes a poison outcome. Poisoning clears scheduling, claim, and admission state and returns no claim token. An admitted poison can therefore retire a cohort member at the cap. A queued poison must itself be the FIFO head and requires a free promotion slot; it never bypasses older ordinary queued work.

The function reads a shared page of at most K = 16 structural ready candidates: admitted-ready rows first, then the oldest queued-ready rows that fit the residual page. It separately reads at most 16 expired candidates. From their bounded union it ranks and freezes at most 16 exact run IDs. Demand-one calls put the requested class head first and retain the other class as fallback. Calls with demand of at least two reserve service for both ready and expired work until each class produces a committed outcome. If only one final demand slot remains while a class is still unserved, the other class cannot consume it; the call may therefore intentionally return one fewer outcome.

The frozen attempt set first retains each unserved class head when demand is at least two, so either class cannot disappear at the K truncation, then prefers admitted work and applies demand-one preference. Mutation still orders admitted work before promotion. Queued rows retain exact (wake_at, id) FIFO order even when a later queued row is poison. The function locks only those exact IDs with FOR UPDATE SKIP LOCKED. A skipped or stale ID is not replaced by structural row 17 or any ID outside the frozen set during that visit.

After locking, every row is rechecked against the frozen scope, ready or expired class, poison class, running/poisoned state, claim state, wake or cutoff time, and remaining promotion capacity. Only rows that still satisfy all authoritative predicates are mutated. Queue mutation also requires the next contiguous frozen queue ordinal and an authoritative absence check for an older currently visible unadmitted head. A locked or stale head therefore blocks every later promotion in that visit. TenantFair never rotates queued discovery.

Lock and write order

Authority is acquired in this order:

policy cursor -> partition -> frozen run IDs

The function never locks a schedule row and then seeks another run. Policy, run, epoch, and cursor changes are part of the caller's transaction and roll back together.

Results and trace mode

Outcome and error rows begin with the stable fourteen columns decoded by TenantFair. The raw record appends a per-call token, transaction ID, visit and outcome ordinals, demand, cursor transition, ring identity, disposition, outcome count, and epoch delta.

Production calls the function with trace disabled. TenantFair.SQL removes the internal columns and explicitly orders public outcomes by visit and outcome ordinal. Trace-enabled calls additionally receive one inspection row per entered visit, including skips, denials, stale pages, empty pages, and grants. Trace rows are returned only to the caller and are never stored in a trace table or emitted as identity-bearing metric labels.

Summary

Functions

create_sql(prefix)

drop_sql(prefix)

identity_arguments()

lock_timeout_ms()

name()

prosrc(prefix)

public_projection(alias_name \\ "claimed")

public_result_columns()

result_definition()