Senior · 6 chapters
Streaming in production
Resumable generation when a client drops mid-stream, persisting partial output, proxies and CDNs that buffer, backpressure
A stream that looks correct in local development can still lose paid work, duplicate output, or stall behind an intermediary in production.
Read these first
What you will know
Handle a client disconnecting mid-generation without losing the output or the money
The user closes the tab at token 300 of 900. Decide whether the generation dies or completes into storage, and make billing agree with that choice.
Persist partial output so a reconnecting client can resume
A durable sequence or cursor is needed to distinguish an acknowledged prefix from duplicated or missing tokens after retries and process failure.
Identify a proxy or CDN that buffers the stream and defeats it
Buffering proxies collect the whole response before forwarding, which silently defeats streaming end to end; verify behavior with timed chunks and production-like headers.
Apply backpressure when the consumer is slower than the model
Use a bounded buffer and an explicit policy for blocking, cancellation, or dropping work so a slow client cannot exhaust process memory.
Explain what happens to a generation when nobody is listening
Cancellation, provider retention, and billing semantics may diverge, so an abandoned request can continue consuming capacity unless ownership is made explicit.
Chapters
- 1
Abandoned generations
A generation is a unit of work with its own lifetime: it starts when your server hands a provider a prompt and ends at a stop sequence or the max_tokens ceiling, whatever happens to the browser in between. This chapter separates it from the HTTP request that started it, covers the three ways a vanished client is actually detected and how far apart they are in speed, and prices what an abandoned generation holds — tokens, a worker, a pooled connection, and rate limit.
- 2
Choosing a disconnect policy
When a client vanishes mid-generation there are exactly two available responses: cancel the upstream call, or detach it and let it finish into storage under a generation id. This chapter states both precisely, prices the choice from the expected cost of not having the answer, gives the handler shape each one requires including the serverless case, and shows what each policy leaves on a cost dashboard when it is working and when it has silently stopped.
- 3
The durable chunk log
Treating a generation as an append-only log — every emitted chunk given a monotonic sequence number under a generation id, written durably before it is forwarded — turns the answer from a value one process is holding into a record that survives the socket and the pod. This chapter covers the minimum schema, why reversing the write and the flush loses tokens rather than duplicating them, what per-token writes cost and how coalescing fixes it, and why sequence numbers must come from one writer.
- 4
Resuming from an acknowledged sequence
Replaying everything stored re-renders text already on screen, and replaying from the server's own last write skips whatever died between the insert and the flush, so the boundary has to be the last sequence the client actually rendered. This chapter builds that cursor and the resume endpoint that serves backlog and live tail as one operation, prices the load it adds, and explains why exactly-once delivery is unavailable and idempotent application is the property worth engineering for.
- 5
Backpressure on a slow reader
A relay that forwards provider chunks without checking whether the write succeeded keeps pulling at decode speed while the client's send buffer is full, so undelivered chunks accumulate until the pod is killed. This chapter does the arithmetic that turns 15 events per second of difference into 250 MB an hour, honours the writable signal, sets out the three policies for a full queue with a bound and a metric, and gives four measurements that separate unbounded buffering from a genuine leak.
- 6
Locating a buffering hop
When six hops separate a browser from your pod and you own two of them, finding the one that collects the whole response before forwarding takes a probe rather than a guess. This chapter gives the signature that identifies buffering from the client side, a model-free probe route whose chunk schedule you control, the ladder of tests that addresses each hop in turn, the headers that ask intermediaries to leave a body alone, and the synthetic check that stops an edge configuration change from regressing it quietly.
External resources
- AI SDK UI: Chatbot Resume Streamsai-sdk.dev
Reference design for resumable streams backed by a Redis buffer.
- zirkelc/ai-resumable-streamgithub.com
Reference implementation of resume-and-stop for UI message streams.
- AI chat stream resumption: when Redis is enough, and when you need durable sessionsably.com
Decides between buffered replay and durable sessions for your failure modes.
- Resumable token streaming: why AI streams break on reconnectably.com
Names the four requirements: persistence, buffering, session tracking, ordering.
- AI Token Streaming: From SSE to Durable Sessionswebsocket.org
Why SSE has no backpressure and where proxies kill idle connections.
- SSE Deep Dive: How Server-Sent Events Stream LLM Responses #realtimecommunicationFree World Coder
The transport under LLM streaming, including proxy buffering pitfalls
- How to keep AI agent sessions alive across dropped connections and device switchesAbly Realtime
Resuming generation and persisting partial output when the client drops