You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The HTTP router fully buffers every request body: the typed handlers extract Json<T> (collecting and parsing the entire payload), and dispatch re-serializes it into a fresh buffer (serialize_request_body -> Vec<u8>), which is held until the worker has consumed the body. For multimodal requests carrying inline media, a single request can occupy tens to hundreds of MB of router memory for its whole dispatch lifetime, roughly twice over (parsed struct + serialized bytes). Router memory therefore scales as in_flight x payload_size, large payloads add a full store-and-forward hop to TTFT, and parsing/re-encoding large JSON bodies costs significant CPU.
Most of the buffered bytes are irrelevant to the router: routing needs the model and, for some policies, the prompt text/tokens — never the media payload.
Proposal
Opt-in streaming pass-through for large request bodies:
New flag --stream-request-bodies-over <bytes> (default off). Bodies at or below the threshold keep today's buffered path (retains router retries, per-worker body mutation, and text-based routing for small requests).
Above the threshold, when the active policy reports needs_request_text() == false and the request needs no body mutation (the no-mutation fast path from perf(router): skip the Value re-encode when proxied requests need no mutation #2139 already identifies this), skip extraction: select the worker from headers/URL, and forward the raw body stream (axum::body::Body -> reqwest::Body::wrap_stream). Only flow-control windows stay in memory.
A progress timeout (no bytes forwarded for N seconds -> abort) guards slow uploaders holding worker slots.
Notes / constraints
Streamed bodies cannot be resent: the router-level retry (RetryExecutor) and the one-shot stale-connection resend do not apply above the threshold. send_with_stale_conn_retry already tolerates unclonable bodies (try_clone() == None falls through), and deployments fronted by a retrying proxy commonly run --disable-retries anyway. Document the tradeoff on the flag.
Backpressure propagates end to end (worker read rate -> router -> client), which is the desired behavior for large uploads.
Follow-ups tracked separately: admission-queue buffering invariant, and header-hint routing so text-needing policies can also stream.
Problem
The HTTP router fully buffers every request body: the typed handlers extract
Json<T>(collecting and parsing the entire payload), and dispatch re-serializes it into a fresh buffer (serialize_request_body->Vec<u8>), which is held until the worker has consumed the body. For multimodal requests carrying inline media, a single request can occupy tens to hundreds of MB of router memory for its whole dispatch lifetime, roughly twice over (parsed struct + serialized bytes). Router memory therefore scales asin_flight x payload_size, large payloads add a full store-and-forward hop to TTFT, and parsing/re-encoding large JSON bodies costs significant CPU.Most of the buffered bytes are irrelevant to the router: routing needs the model and, for some policies, the prompt text/tokens — never the media payload.
Proposal
Opt-in streaming pass-through for large request bodies:
--stream-request-bodies-over <bytes>(default off). Bodies at or below the threshold keep today's buffered path (retains router retries, per-worker body mutation, and text-based routing for small requests).needs_request_text() == falseand the request needs no body mutation (the no-mutation fast path from perf(router): skip the Value re-encode when proxied requests need no mutation #2139 already identifies this), skip extraction: select the worker from headers/URL, and forward the raw body stream (axum::body::Body->reqwest::Body::wrap_stream). Only flow-control windows stay in memory.max_payload_sizemoves to a counting stream wrapper that aborts past the limit (same shape as the capped response reader from fix(router): cap upstream body buffering and sanitize upstream error-code labels #2138).Notes / constraints
RetryExecutor) and the one-shot stale-connection resend do not apply above the threshold.send_with_stale_conn_retryalready tolerates unclonable bodies (try_clone() == Nonefalls through), and deployments fronted by a retrying proxy commonly run--disable-retriesanyway. Document the tradeoff on the flag.