ai-gateway: raise drainTimeout so model-deploy churn doesn't kill live completions - #812
Conversation
…completions Every model deploy/undeploy changes the ai-gateway filter set (per-model EPP ext_proc + catch-all ext_authz), triggering an Envoy in-place filter-chain update whose superseded chain is force-closed at the end of the 60s drain window — severing in-flight streaming completions (measured on app-dev: an unrelated model's event killing a held ai.<domain> stream). Envoy Gateway feeds shutdown.drainTimeout into Envoy's --drain-time-s, which governs LDS-triggered filter-chain draining on a running pod, so raising it lets a superseded chain hold its connections long enough for the completion to finish and close naturally. Set >= the ai-gateway-discovery REQUEST_TIMEOUT (30m). Scoped to the ai-gateway EnvoyProxy only; does not reduce churn, only stops it cutting live streams (the topology fix is upstream).
Envoy Gateway derives terminationGracePeriodSeconds = drainTimeout + 5m, so the bare 31m drainTimeout would make every pod termination (rollout, node drain, autoscaler, upgrade) block up to 36m — and hit that ceiling each rollout via the lingering-Prometheus-scrape drain bug (envoyproxy/gateway#4125). Patch terminationGracePeriodSeconds back to 360s via envoyDeployment.patch: the long 31m window applies only to the running-pod LDS churn drain (--drain-time-s, which protects in-flight streams across model deploys), while pod termination stays at the 6m status quo. The two timers are independent at runtime.
|
Checked this against the EG source and against measurements I took on app-dev. The mechanism is right and the effect is verified — two notes below, neither structural. VerifiedThe grace-period derivation is exactly as described. The patch will win — this is settleable from source, not just "verify on deploy". In For completeness on the other end of the mapping: EG's The effect is measured, not theoretical. While the 31m was live on app-dev I re-ran the exact experiment that first demonstrated the problem: held a stream on Findings1. The The decoupling still stands on its own merits — 36 minutes of termination grace on every rollout, node drain and autoscaler scale-down is bad regardless of whether anything forces it to the ceiling — so I'd correct the justification rather than change the code. 2. Worth confirming the 6m tradeoff is the intended one. With 3. Nice to know, not blocking. The 4. Minor. "Cost is bounded by concurrent long streams, not by churn rate" is the right framing — better than the "chains pile up per churn event" worry I had earlier. Chains do accumulate while their connections live (I saw 3, then 5, draining concurrently on app-dev), but they're released as soon as those close, so the bound holds. |
Summary
Stops long LLM completions on the ai-gateway from being cut mid-stream when the listener churns on model deploy/undeploy — without slowing pod termination.
Problem
Every model deploy/undeploy changes the ai-gateway filter set (per-model EPP
ext_proc+ catch-allext_authzfilters). That triggers an Envoy in-place filter-chain update: the superseded chain is drained and any connection still on it is force-closed at the end of the drain window. With the 60s default, an in-flight streaming completion is severed mid-body — measured on app-dev: an unrelated model's event killed a heldai.<domain>stream ~60s later (CURLE_PARTIAL_FILE). It lands entirely on the longest-running requests, so it's invisible in ordinary testing.Change
Raise
EnvoyProxy.shutdown.drainTimeoutto31mon the ai-gateway EnvoyProxy. Envoy Gateway feedsdrainTimeoutinto Envoy's--drain-time-s, which governs LDS-triggered filter-chain draining on a running pod, so a churn-superseded chain now holds its connections long enough for the completion to finish and close naturally (≥ the ai-gateway-discoveryREQUEST_TIMEOUTof 30m). A drained chain is released as soon as its connections close, so cost is bounded by concurrent long streams, not by churn rate.Decoupled from pod termination (important)
EG derives
terminationGracePeriodSeconds = drainTimeout + 5m, so a bare31mwould make every pod termination (rollout, node drain, autoscaler scale-down, upgrade) block up to 36m — and hit that ceiling on every rollout via the lingering-Prometheus-scrape drain bug (envoyproxy/gateway#4125). To avoid that,envoyDeployment.patchforcesterminationGracePeriodSecondsback to the 360s default. The two timers are independent at runtime:31m→--drain-time-s: the running-pod LDS churn drain (protects in-flight streams).360s→ the k8s SIGKILL deadline on termination (rollouts/node drains stay at today's 6m).Scope / blast radius
httpsgateway and everything else are untouched.#1917).Test plan
helm template … --set aiGateway.enabled=truerendersdrainTimeout: 31m+ theterminationGracePeriodSeconds: 360patch;helm lintclean.ai.<domain>, deploy/undeploy another model, confirm the stream is not dropped andtotal_filter_chains_draining(nottotal_listeners_draining) moved.