Proposal: Alternative architectural approach to Heavyweight Chat Concurrency & Load Shedding (avoiding 503/502 subagent drops) #9608
Replies: 4 comments 2 replies
|
Hey @Minus-Brain! Thank you for the thoughtful write-up -- this is a genuinely useful architectural discussion and you have correctly identified the tension between load-shedding and multi-provider parallelism. You are right about the core contradiction. The admission controller was introduced to prevent V8 heap OOMs on constrained servers (15s queue, 503 when saturated). Your point that this defeats multi-provider routing is valid: 20 healthy provider targets cannot help if the request is rejected before routing. That said, the current design is more nuanced than a hard 15s door-slam. Three things worth knowing: 1. The 15s is the default queue timeout, not a hard TTL. The admission controller uses a weighted adaptive limit ( 2. Admission per-provider, not per-gateway. The controller operates at the request level, not the provider level. A large batch of concurrent subagent requests may trigger the 503 before OmniRoute has a chance to fan them out. This is indeed a problem for agentic frameworks. 3. We already ship three mitigations for power users:
What I think should happen (and would welcome your input): The real fix is a per-connection admission lane so that heavy agentic sessions get their own concurrency budget that does not compete with the global pool. Something like:
This is roughly the same shape as HTTP/2 stream prioritization applied to provider routing. Would that match your mental model? If you have a specific design sketch for how per-connection lanes would interact with combo routing and the existing fair-cost queue, I would love to read it. The 502/503 poisoning of client retry loops is a real pain point I agree needs addressing. In the meantime, the env overrides above should let you tune the defaults for your workload. |
|
Hey @Minus-Brain! Excellent follow-up -- your design sketch is solid and I agree on every point. Your lane isolation + combo interaction model is exactly right. The key insight (and the most important part to get right in code) is step A.3: "if Target 1 is saturated, try Target 2 within the same lane before queuing." This is what distinguishes a lane from a simple per-key rate limiter -- the lane is a routing scope, not a bottleneck. Tracking issue opened: #9654 with your full design sketch incorporated (lane isolation, combo interaction, auto-eviction TTL, Retry-After headers) plus acceptance criteria and implementation ideas. I will start prototyping on a Regarding testing: much appreciated. Once there is a branch with a working prototype, I will ping you on #9654 and you can pull it from a nightly or a pre-release build. The combo interaction part (step A.2-A.3) is the trickiest to get right -- your real-agentic-framework traffic patterns (Cursor, Claude Code subagents) are exactly the load profile this needs to validate against. Thank you again for the quality of this discussion -- it went from "here is a problem" to "here is the spec" in two messages, which is rare and valuable. |
|
Hey @Minus-Brain! Thank you again for the thoughtful update and benchmark framing. Your thread is the right one for the architecture work, and issue #9654 already tracks it. If you have concrete latency/error numbers from agentic traffic after branch changes, post them in #9654 and we can tune the lane model against real workload data. No extra action needed on this thread right now. |
|
Thanks for the detailed proposal -- the admission-control/load-shedding work you're referencing has moved forward since this was posted: see #9176 (bounded phase-aware admission replacing the binary heavyweight-chat lease) and #10054 (adaptive admission promoted to the primary multi-agent scheduling path). If you're still seeing 503/502 subagent drops on a current version, please open a fresh issue with your version and a reproduction -- the architecture has changed enough that the original analysis may no longer map 1:1. |
Uh oh!
There was an error while loading. Please reload this page.
Hi team,
First of all, huge thanks for all the incredible work on OmniRoute! It’s an essential tool for routing LLM workloads across multiple providers.
I’d like to open a discussion regarding the recent Admission Control / Load Shedding changes introduced around v3.8.49 (chatBodyAdmission, 503 chat_admission_busy, 15s queue drop timeouts, and 502 Stream ended before producing a non-ping SSE event).
The Core Problem: Defeating the Multi-Provider Paradigm
While I understand these guards were introduced to prevent V8 Heap Out-of-Memory (OOM) crashes on constrained servers, the current load-shedding mechanism inadvertently defeats the primary purpose of an AI Gateway.
When using autonomous agentic frameworks (Cursor, Claude Code, CrewAI, AutoGen, Subagents), sending concurrent requests with large contexts (15k–100k+ tokens) is the norm, not an edge case.
With forced load shedding and ultra-short queue limits (15s):
Parallel subagent workflows completely break: Incoming concurrent calls are fast-rejected with 503 chat_admission_busy at the front door before OmniRoute even attempts to distribute them across available provider targets.
Multi-provider rotation becomes useless: Having 20 healthy backend accounts or Combo targets doesn't help if OmniRoute drops the request before routing it.
Spurious 502/503 errors poison client loops: Autonomous clients treat 503 as terminal failures or trip internal retry loops, stalling execution.
In my own benchmarks on a modest server setup, running 50 concurrent requests with ~15k tokens each never caused OmniRoute to crash or OOM. Forced front-door rejection is harming setups that have plenty of RAM to handle parallel loads.
Root Cause Analysis: Why do heavy requests consume so much RAM?
A 100,000-token text request is only ~500 KB of raw UTF-8 JSON. In theory, proxying a 500 KB payload should consume negligible memory.
However, in Node.js / V8:
JSON.parse(body) expands 500 KB of raw text into 30–50 MB of V8 Heap JS Objects (messages, tool schemas, metadata).
Pipeline Cloning: In the processing pipeline, the request body is often deep-cloned multiple times (logging, token estimation, Combo target retries, target format transformations, prompt compression).
A single heavy request can temporarily inflate to 200 MB–300 MB of V8 Heap allocations. When 5 concurrent requests hit at the same millisecond, V8 heap usage spikes by ~1.5 GB before Garbage Collection can reclaim it, triggering OOM.
The current solution treats this by shedding load at the front door (503). But we can solve the memory inflation at the source instead.
Proposed Architectural Solutions (Without 503 Front-Door Drops)
For requests that do not require deep transformation or prompt compression, OmniRoute should avoid running JSON.parse on the full body.
Piping raw HTTP byte streams (stream.Readable / undici) directly to the upstream provider reduces memory overhead from ~200 MB down to a few KB of buffer memory per request.
This allows a single instance to handle hundreds of concurrent agent requests effortlessly.
Lazy Parsing: Only extract top-level routing metadata (model, stream) via lightweight inspection/regex or fast header checks, leaving the heavy messages[] array unparsed until strictly needed.
Shallow Copies for Combos: Avoid structuredClone or JSON.parse(JSON.stringify(body)) when attempting retries across Combo targets. Using read-only references or shallow object copies prevents multi-fold memory multiplication.
Make aggressive queue dropping and memory-based admission control strictly opt-in or configurable via environment variables (e.g., OMNIROUTE_DISABLE_ADMISSION_CONTROL=1).
Allow operators with sufficient server RAM to set infinite/long queue wait times (maxWaitMs) and disable front-door rejection entirely.
Summary
The goal of a multi-provider gateway is to swallow parallel load and distribute it cleanly. Shifting from load shedding (503 rejection) to zero-copy streaming and clone elimination would restore subagent reliability while keeping memory usage near zero.
Would love to hear the team's thoughts on this direction!
All reactions