Prompt and context
An HTTP/3 gateway carries long-lived connections and multiplexed requests. During a rollout it must reject new work, finish accepted requests, and close safely after a deadline. Design graceful shutdown with RFC 9114 GOAWAY and explain its boundary with QUIC connection close and HTTP retries.
What the interviewer is testing
Distinguish GOAWAY, which limits future requests, from CONNECTION_CLOSE, which terminates the QUIC connection. HTTP/3 GOAWAY carries a request-stream ID limit; a server can start with a wider value and shrink it to a final boundary. Cover concurrent streams, client knowledge of unprocessed requests, idempotency, proxy propagation, timeout, and observability.
Clarifying questions to ask first
Request and connection shape
Ask about long polling, uploads, WebTransport, non-idempotent POSTs, and reverse proxies. Completion time and retry risk differ by stream type.
Drain objective and deadlines
Clarify whether this is connection draining, node maintenance, or fault isolation. Set a maximum drain deadline, hard-close deadline, and rollout batch size; otherwise old connections can consume capacity indefinitely.
Client capability
Confirm clients and proxies process GOAWAY, support reconnect and migration, and can distinguish “new stream rejected” from “request executed but response lost.”
30-second answer framework
“HTTP/3 GOAWAY advertises a request-stream ID boundary; after receiving it, a peer must not create new requests above that boundary. The server first sends a broad value, stops assigning new work, then sends a smaller final value so the client knows which requests might not have been accepted. Accepted streams drain; after the deadline the server uses QUIC CONNECTION_CLOSE. A client retries only when it can establish that a request was not executed and the operation is safe to repeat.”
Step-by-step deep answer
Step 1: Separate GOAWAY from CONNECTION_CLOSE
GOAWAY is an HTTP/3 control message that limits the highest request-stream ID; it does not immediately terminate the connection. CONNECTION_CLOSE ends QUIC and can interrupt active requests. Drain with GOAWAY first and close only at the hard deadline.
Step 2: Use a wide-then-narrow boundary
Send a large ID that covers observed concurrent requests, stop accepting new work, and then send a smaller final ID. The client must not interpret the shrinking process as permission to create new requests. Persist control-frame order and state so reconnect logic is deterministic.
Step 3: Handle accepted, unstarted, and unknown requests
Accepted streams below the final boundary continue. Requests above it were not accepted and may be retried on a new connection. If the server cannot know whether a non-idempotent request ran, expose an idempotency key or status lookup rather than replaying blindly.
Step 4: Design proxy and migration behavior
A reverse proxy that receives upstream GOAWAY must stop assigning new requests to that connection and propagate drain state downstream. QUIC connection migration is not request migration; changing paths does not safely transfer an executing HTTP stream.
Step 5: Set drain and hard-close deadlines
Record GOAWAY time, last active stream, and remaining requests. At drain timeout cancel unfinished streams; at hard-close time send CONNECTION_CLOSE and release the connection. Limit the number of instances drained at once so capacity does not collapse.
Step 6: Protect retries and side effects
Retry only confirmed-unexecuted idempotent methods or writes carrying an idempotency key, with exponential backoff and a retry budget. A lost response does not prove the request did not run; payments and inventory mutations need server-side deduplication.
Step 7: Verify the rollout
In a canary, inject long requests, concurrent streams, reconnects, shrinking GOAWAY values, proxy propagation, and hard close. Monitor rejected new streams, drain duration, unfinished streams, retry rate, duplicate side effects, connection errors, and capacity headroom. Check p99 latency and error rate throughout the rollout.
High-quality sample answer
I would stop assigning new connections, send a broad GOAWAY to stop new streams, and then send a smaller final boundary after observing concurrent requests. Accepted streams below the boundary drain; only after the deadline are they canceled and the QUIC connection closed. Clients retry only confirmed-unexecuted idempotent operations, using idempotency keys for non-idempotent writes. Proxies propagate drain state, while migration is never treated as request transfer. Canary tests cover long requests, concurrent streams, retries, proxy behavior, and duplicate effects.
Common mistakes
- Mistake: Closing QUIC immediately after GOAWAY. → Why it fails: Active requests are interrupted. → Fix: Drain first and close at the hard deadline.
- Mistake: Treating the GOAWAY limit as an execution ledger. → Why it fails: A stream ID expresses a range, not application completion. → Fix: Confirm with application state and idempotency keys.
- Mistake: Retrying every failed request. → Why it fails: A lost response may follow a side effect. → Fix: Limit retries to idempotent or keyed operations.
- Mistake: Assuming connection migration transfers active requests. → Why it fails: A path change does not change HTTP stream state. → Fix: Use a new connection for safe retries.
Follow-up questions and answers
Follow-up 1: Why can GOAWAY values shrink?
The server can first cover observed concurrent requests and then establish the final boundary for new streams. Shrinking lets the client converge without guessing all in-flight work at the first signal.
Follow-up 2: How does a client know whether a request was accepted?
Comparing a stream ID with the final boundary identifies a possible range, not whether application code ran. Combine it with the response, connection error, and an idempotency-status lookup.
Follow-up 3: Does GOAWAY automatically propagate through proxies?
Do not assume it. A proxy is an independent HTTP/3 endpoint and must translate upstream draining into downstream connection and routing policy.
Follow-up 4: Why cancel streams before hard close?
Cancellation releases application resources and records a clear reason, allowing clients to distinguish drain timeout from network failure. QUIC close is the final connection boundary, not an application cleanup mechanism.