-
v0.3.0 — Phase 1.5: admission control
Pre-ReleaseCI / test (push) Successful in 48sreleased this
2026-09-16 01:53:38 +02:00 | 843 commits to main since this releaseFlow control. A batch workload no longer starves interactive traffic.
Added
- Admission control (
internal/router/admission), inserted between quota
checks and scheduling. It answers when a request may proceed; the scheduler
still answers where it goes. Keeping them apart is the point: an engine
queue is FIFO and knows nothing about tenants, so once a batch job is in it,
no amount of endpoint selection rescues the request behind it. - Saturation detector. One number per pool, the worse of queue depth and KV
occupancy. Either signal alone misleads — a pool can have an empty queue and a
nearly full cache, or the reverse. - Priority bands. Each band has a saturation ceiling; above it, that band
waits. The cluster then degrades from the bottom up. A ceiling of 1.0 means
"flow up to the healthy limit"; above 1 means never wait. - Tenant fairness. Within a band, dispatch turns rotate between tenants, so
one caller sending a thousand requests occupies one slot in the rotation
rather than a thousand. - Bounded queues. Capacity per band (immediate 503 when full) and a TTL
(504), because sending the GPU work the client has already abandoned helps
nobody. Cancelled requests leave the queue instead of holding capacity. - Metrics:
llmgw_admission_wait_seconds,llmgw_admission_rejected_total,
llmgw_admission_queue_depth,llmgw_pool_saturation. Responses carry
x-llmgw-queue-mswhen a request waited.
Phase 1.5 exit criterion, measured in
test/e2eWhile 500 concurrent bronze requests run continuously, interactive p99 in the
platinum band stays at 1.04–1.24x its idle value, against a 1.5x budget.
With admission switched off the same load pushes it to 55–60x. Both
directions are asserted, so the test fails if admission regresses and if the
load generator ever stops being a real load.Two lags, two different fixes
Getting this right took longer than the queueing itself:
- Engine metrics are scraped, so they lag by an interval. The router's inflight
count does not — but it includes running requests, and treating those as
queue depth makes a healthy engine look overloaded every interval. What is
genuinely queued isinflight − batch capacity, and batch capacity is not in
the metric contract, so it is learned as the high-water mark of observed
running. - Between admission granting a request and the router dispatching it, the
request is invisible to every signal. A simultaneous burst therefore passed
the check en masse — exactly the batch arrival pattern this layer exists to
survive. The controller now counts its own outstanding leases against
dispatched inflight.
Known limitations
- The band comes from a trusted header until authentication arrives in v1.0.0.
Anyone who can reach the port can promote themselves to platinum. - The tenant key is the session id, for the same reason.
- Displacement (dropping queued low-band work to make room) is not implemented;
the plan defers it, and bounded queues plus TTL cover most of the need.
Full notes: CHANGELOG.md
Downloads
- Admission control (