Share concurrent request limits across the fleet - #975
Conversation
Admission goes through a Postgres lease table serialized per organization and model by an advisory lock, so replicas share one count instead of each enforcing the limit locally. Off by default via FLEET_CONCURRENCY_MODE, falling back to the existing per-process counter on any database error. Not finished: leases have no heartbeat, so a request outliving its TTL frees capacity while still in flight, and there are no metrics yet.
A rate limit of zero means unset everywhere else, so reading it as a real limit locked an org out of every model. in_flight went out as a counter when it is a depth reading, and the background loops sent metrics with no environment tag. Sweep deletes in batches now, and an unknown FLEET_CONCURRENCY_MODE warns instead of quietly leaving the feature off.
Main's prompt-cache migration also claimed V0073, so refinery applied one and skipped the other; ours moves to V0075. ConcurrentRequestGuard now holds a ConcurrentSlot so the native Anthropic route admits through fleet leases.
Shadow counts leases fleet-wide but leaves rejection to the per-replica limit, so the numbers can be measured before enforcement turns anyone away. Also covers the guard direct transport routes hold, which had no test.
Review: Share concurrent request limits across the fleetSolid design overall — the advisory-lock admission, Four things I would want fixed before this rolls out, two of which affect 🔴 1.
|
Review · Status🟩 CompletedIronLoop completed the review and posted it to GitHub. ResultRun detailsAutomatic trigger · attempt 1 of 3 · completed in 6m 54s |
There was a problem hiding this comment.
Review · Summary
Found three actionable fleet-concurrency issues.
Findings: 🔴 High 1 · 🟠 Medium 2
Code-specific findings are attached to the diff.
Validation
- ✅ Captured CI — The captured unit, integration, E2E, lint, test-suite, and release-build checks passed.
Review details
- Run:
9cb3366c-2601-44c7-a92c-408735cfe8e8 - Attempts: 1
| ttl: Duration, | ||
| enforcing: bool, | ||
| ) -> Self { | ||
| let (release, mut released) = mpsc::unbounded_channel::<Uuid>(); |
There was a problem hiding this comment.
🔴 High · Bound the asynchronous lease-release queue
Every completed request sends an ID into an unbounded channel, while the single receiver awaits database release calls. During a lease-store stall or outage, fallback traffic can continue completing faster than retries drain, so this shared queue grows without bound and can exhaust process memory. Use a bounded/coalescing nonblocking queue and allow dropped release notifications to expire safely.
| INSERT INTO concurrency_leases | ||
| (id, organization_id, model_id, instance_id, expires_at) | ||
| VALUES ($1, $2, $3, $4, NOW() + make_interval(secs => $5)) | ||
| ON CONFLICT (id) DO NOTHING |
There was a problem hiding this comment.
🟠 Medium · Make acquisition retries idempotent before checking capacity
A connection loss after the server commits can make retry_db! rerun this operation with the same lease ID. If other requests fill the remaining capacity before the retry, the count check returns AtLimit before reaching the conflict-tolerant insert. The request is then rejected and never tracks or releases its already-committed lease, consuming capacity until its TTL. Detect an existing lease_id (or exclude it from the count) before the limit check.
| )); | ||
| ); | ||
|
|
||
| if config.fleet_concurrency.mode != config::FleetConcurrencyMode::Off { |
There was a problem hiding this comment.
🟠 Medium · Do not use a single shadow replica as fleet-wide rollout evidence
Instances left in off mode never create FleetConcurrency, so they continue using only local counters and write no leases. Consequently, the documented one-instance shadow rollout measures only that instance, and enabling enforce one instance at a time still ignores traffic on off replicas. Require all replicas to write shadow leases before relying on the metric or performing a gradual enforce rollout.
| let host = env::var("HOSTNAME").unwrap_or_else(|_| "unknown".to_string()); | ||
| format!("{host}-{}", std::process::id()) |
There was a problem hiding this comment.
In containerized environments (Kubernetes, Docker), std::process::id() is almost always 1, and HOSTNAME is typically the pod/container name. If HOSTNAME is unset in some environments, the fallback produces unknown-1 — identical across all replicas. While this does not cause functional lease corruption (leases are tracked by UUID in the concurrency_leases table, not by instance_id), it makes the instance_id column useless for identifying which replica holds a lease during fleet-wide concurrency incidents. Consider generating a random suffix (e.g., a short UUID) to guarantee uniqueness even when HOSTNAME is missing.
Suggestion:
| let host = env::var("HOSTNAME").unwrap_or_else(|_| "unknown".to_string()); | |
| format!("{host}-{}", std::process::id()) | |
| let host = env::var("HOSTNAME").unwrap_or_else(|_| "unknown".to_string()); | |
| format!("{host}-{}-{}", std::process::id(), uuid::Uuid::new_v4().simple()) |
| lease_ttl_seconds: env::var("FLEET_CONCURRENCY_LEASE_TTL_SECONDS") | ||
| .ok() | ||
| .and_then(|value| value.parse::<u64>().ok()) | ||
| .filter(|seconds| *seconds > 0) | ||
| .unwrap_or(60), |
There was a problem hiding this comment.
Unlike FLEET_CONCURRENCY_MODE which warns on unrecognized values, an invalid FLEET_CONCURRENCY_LEASE_TTL_SECONDS (non-numeric, zero, or negative) silently falls back to 60 with no warning. An operator who sets a typo or invalid value may be unaware the default is being used. Consider adding an eprintln! warning consistent with the mode handling for unrecognized values.
Suggestion:
| lease_ttl_seconds: env::var("FLEET_CONCURRENCY_LEASE_TTL_SECONDS") | |
| .ok() | |
| .and_then(|value| value.parse::<u64>().ok()) | |
| .filter(|seconds| *seconds > 0) | |
| .unwrap_or(60), | |
| lease_ttl_seconds: match env::var("FLEET_CONCURRENCY_LEASE_TTL_SECONDS") { | |
| Ok(value) => match value.parse::<u64>() { | |
| Ok(seconds) if seconds > 0 => seconds, | |
| _ => { | |
| eprintln!( | |
| "WARN: invalid FLEET_CONCURRENCY_LEASE_TTL_SECONDS '{value}', \ | |
| falling back to 60" | |
| ); | |
| 60 | |
| } | |
| }, | |
| Err(_) => 60, | |
| }, |
| CREATE TABLE concurrency_leases ( | ||
| id UUID PRIMARY KEY, | ||
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | ||
| model_id UUID NOT NULL REFERENCES models(id) ON DELETE CASCADE, | ||
| instance_id TEXT NOT NULL, | ||
| acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
| expires_at TIMESTAMPTZ NOT NULL | ||
| ); |
There was a problem hiding this comment.
The migration lacks a descriptive header comment explaining the table's purpose. Other recent migrations (e.g. V0074) include a brief comment. Adding one here would help future maintainers understand that this table tracks fleet-wide concurrency leases for rate-limit enforcement across replicas.
Suggestion:
| CREATE TABLE concurrency_leases ( | |
| id UUID PRIMARY KEY, | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| model_id UUID NOT NULL REFERENCES models(id) ON DELETE CASCADE, | |
| instance_id TEXT NOT NULL, | |
| acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| expires_at TIMESTAMPTZ NOT NULL | |
| ); | |
| -- Fleet-wide concurrency leases: each in-flight request acquires a lease | |
| -- with a TTL; the active count per (organization, model) is checked against | |
| -- the org's rate_limit before admission. Expired leases are swept periodically. | |
| CREATE TABLE concurrency_leases ( | |
| id UUID PRIMARY KEY, | |
| organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, | |
| model_id UUID NOT NULL REFERENCES models(id) ON DELETE CASCADE, | |
| instance_id TEXT NOT NULL, | |
| acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| expires_at TIMESTAMPTZ NOT NULL, | |
| CHECK (expires_at > acquired_at) | |
| ); |
| acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
| expires_at TIMESTAMPTZ NOT NULL | ||
| ); |
There was a problem hiding this comment.
Consider adding a CHECK constraint (expires_at > acquired_at) as defense-in-depth. The application code always sets expires_at = NOW() + positive TTL, so the invariant holds today, but a database-level constraint would prevent accidental insertion of already-expired leases and make the intent explicit.
Suggestion:
| acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| expires_at TIMESTAMPTZ NOT NULL | |
| ); | |
| acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | |
| expires_at TIMESTAMPTZ NOT NULL, | |
| CHECK (expires_at > acquired_at) | |
| ); |
| where | ||
| F: Fn(&mut config::ApiConfig), | ||
| { |
There was a problem hiding this comment.
Using Fn as the closure bound is unnecessarily restrictive for a function that calls the closure multiple times (once per instance). Fn prohibits the closure from mutating captured state, which means a future test author cannot assign unique instance_id values per replica — a realistic fleet scenario — using a simple mutable counter:
let mut i = 0;
setup_test_fleet(INSTANCES, |config| {
config.fleet_concurrency.instance_id = format!("instance-{i}");
i += 1; // requires FnMut, not Fn
}).await;
FnMut is the idiomatic bound for closures that are called multiple times and may need to track state across invocations. All closures that satisfy Fn also satisfy FnMut, so existing callers are unaffected.
Suggestion:
| where | |
| F: Fn(&mut config::ApiConfig), | |
| { | |
| where | |
| F: FnMut(&mut config::ApiConfig), | |
| { |
A retry after a lost commit response counted the lease it had already written, so it could reject the request that was holding it. Releases now queue with a bound, and a dropped release or a failed sweep is counted rather than only logged.
Main took V0075 for the orphaned-org-children migration, so refinery applied one and skipped the other.
PierreLeGuen
left a comment
There was a problem hiding this comment.
In shadow mode a fleet-admitted request returns before touching the per-replica counter, so a replica can hold its full lease allowance plus a full local allowance — up to 2x the organization limit — in the first rollout step that is meant to change no admission decision.
Blocking findings:
crates/services/src/completions/mod.rs:1912— In shadow mode a fleet-admitted request returns early fromtry_acquire_concurrent_slotand never increments theconcurrent_countsatomic. Impact: With an organization limit L, one replica in shadow mode admits up to 2L concurrent backend requests for that… Fix: Do not let a fleet admission bypass the per-replica limiter when!fleet.enforcing.
Checks: cargo +1.92.0 fmt --all -- --check — passed; cargo +1.92.0 build -p services --tests — exit 0; cargo +1.92.0 clippy -p services -p config -p database --all-targets — clean, no new warnings
A fleet-admitted request returned before reaching the local counter, so a replica could run its lease allowance and its local allowance at once. While shadowing the fleet only observes now, including when the lease store is down.
|
Fixed. While shadowing the fleet only observes now, so the per-replica counter decides admission and a lease can't bypass it. The store-outage path had the same shape and defers too.
|
A lost response makes retry_db! re-run the acquire under the same id. If the free capacity went elsewhere in between, the rejection left the first attempt's row holding a slot that nothing renewed or released until its TTL. The reject path deletes it now.
Summary
Rollout
FLEET_CONCURRENCY_MODEdefaults tooff, so merging changes nothing. Replicasleft in
offwrite no leases at all, so move the whole fleet toshadowbeforereading anything from the numbers, then compare
concurrency.would_rejectagainst
concurrency.admittedto size the impact. Move toenforceone replicaat a time after that. Rollback is setting the mode back to
off.Verification
cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warningscargo nextest run --lib --bins(1407 passed)cargo nextest run --test integration_tests(8 passed)cargo nextest run --test e2e_all(698 passed, fresh database)cargo build --release