Pre-alpha documentation
Schedule tenants from one durable order.
Compile immutable weights, reserve replica-global slots, expose an exact starvation bound, and retain lost-ack evidence safely.
Compile one immutable weighted policy
StateKnot sorts exact tenant IDs, compiles a deterministic smooth weighted-round-robin cycle, verifies every configured count, and derives each tenant’s largest circular selection gap. The hard limits are 1,024 tenants, weight 1–1,024, and 4,096 total slots per shard.
let tenant_a = TenantId::try_from("tenant-a")?;
let tenant_b = TenantId::try_from("tenant-b")?;
let policy = WeightedFairnessPolicy::new(
SchedulerShardId::try_from("primary-v1")?,
[
TenantFairnessWeight::new(tenant_a, 3)?,
TenantFairnessWeight::new(tenant_b.clone(), 1)?,
],
)?;
let bound = policy
.starvation_bound(&tenant_b)
.expect("tenant-b was registered in this policy");
assert!(bound.maximum_reservations_until_selection() <= 4); The ratio applies to scheduling opportunities—not token spend, CPU time, money, or completed runs. Changing tenants, weights, ordering, or the algorithm requires a new shard identity and an explicit rollout.
Reserve globally before scanning locally
-
Register
DurableFairScheduler::registerbinds byte-identical policy content to one shard before any run is claimed. -
Reserve
Every replica allocates one stable UUIDv7 reservation and advances the short PostgreSQL cursor transaction once.
-
Select
The durable slot maps through the immutable local cycle to exactly one tenant queue.
-
Tick
The existing tenant-isolated scheduler scans, claims, and executes at most one run for that tenant.
The reservation is consumed when the queue is idle or loses contention; it is never reassigned to a busy tenant. That preserves global order and prevents work-conserving shortcuts from stealing another tenant’s share.
Measure the actual circular bound
TenantStarvationBound is the maximum number of global reservations
between selections, including the selected slot. It is derived from the compiled
cycle rather than estimated from a ratio. Monitor both this selection gap and
successful-claim/service lag because queue contention can still prevent work
after a fair selection.
Retain reservation evidence beyond every retry
Migration 14 stores immutable reservation rows so a lost database
acknowledgement returns the original sequence and slot. Retention uses the
database clock, indexed oldest-first candidates, and
FOR UPDATE SKIP LOCKED. It never modifies policy or cursor
state.
let policy = SchedulerFairnessRetentionPolicy::new(
Duration::from_secs(24 * 60 * 60),
1_000,
)?;
let report = store
.prune_scheduler_fairness_reservations(policy)
.await?; The supported window is one hour through 366 days, and a transaction deletes at most 10,000 rows. Never retry a reservation identity after its retention window; choose a window longer than every retry, incident replay, and audit requirement.
Roll policy changes as new shards
A shard ID permanently binds one canonical policy. Drain the old
deployment before activating a new shard such as primary-v2.
Running two shard identities against the same logical worker pool creates
two independent schedules by design and invalidates any combined bound.
Qualification evidence
Property tests verify arbitrary bounded tenant order/weights, exact cycle counts, order independence, and every circular bound. PostgreSQL 16/17 tests cover immutable registration, same-ID and unique-ID concurrency, contiguous reservations, lost acknowledgements, bounded retention, migration integrity, and a 3:1 share across four distributed ticks. Read the complete deployment and operations contract before adopting this pre-alpha surface.