10 views
This guide covers Lambda response streaming with production-focused examples and trade-offs.
Function URL response streaming looks great in a happy-path curl demo and then dies in production when a mobile client stalls mid-transfer. The unfair advantage is treating the writable HTTP stream as a real backpressure surface: pause producers, bound buffers, and fail closed before you OOM the execution environment or burn the full timeout on a half-dead TCP session.⚡ TL;DR: Useawslambda.streamifyResponsewith explicit highWaterMark discipline; neverBuffer.concatthe whole body; honordrain/cork/ abort signals; set Function URL idle timeouts that match your producer; emit CloudWatch metrics for stalled bytes and aborted streams. Pair with Lambda Function URLs Streaming and Lambda Plus Bedrock streaming.
Why streaming fails under stall — Lambda response streaming
Lambda still bills wall-clock while the client is slow. If your handler keeps generating into an unbounded in-memory queue, you pay twice: memory + timeout. Backpressure means the Node.js Writable must force the producer to wait whenwrite() returns false.
typescript
// handler.ts — Node 20 Function URL streaming with backpressure
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
declare const awslambda: {
streamifyResponse: (
fn: (event: unknown, responseStream: NodeJS.WritableStream, context: unknown) => Promise<void>
) => unknown;
HttpResponseStream: {
from: (stream: NodeJS.WritableStream, meta: { statusCode: number; headers: Record<string, string> }) => NodeJS.WritableStream;
};
};
export const handler = awslambda.streamifyResponse(async (event, responseStream, _ctx) => {
const httpStream = awslambda.HttpResponseStream.from(responseStream, {
statusCode: 200,
headers: {
"Content-Type": "application/octet-stream",
"Cache-Control": "no-store",
},
});
// ✅ Bound the writable buffer — default is often too generous for 128–512 MB functions
(httpStream as any).writableHighWaterMark = 64 * 1024;
const abort = new AbortController();
httpStream.on("error", () => abort.abort());
httpStream.on("close", () => abort.abort());
const source = createProducer(event, abort.signal); // async generator → Readable
try {
await pipeline(Readable.from(source), httpStream);
} catch (err) {
// Client stall / reset — do not retry the whole payload in-process
console.error(JSON.stringify({ msg: "stream_aborted", err: String(err) }));
}
});
async function* createProducer(event: unknown, signal: AbortSignal) {
for await (const chunk of fetchUpstreamChunks(event)) {
if (signal.aborted) return; // ✅ stop producing
// ❌ Never: chunks.push(chunk) into a growing array
yield chunk;
}
}
Detect stalls before timeout
Instrument bytes written vs wall time. If throughput drops below a floor for N seconds, destroy the stream and let the client reconnect with Range / cursor semantics.typescript
function wrapWithStallGuard(stream: NodeJS.WritableStream, opts: { minBps: number; windowMs: number }) {
let written = 0;
let windowStart = Date.now();
const timer = setInterval(() => {
const elapsed = Date.now() - windowStart;
const bps = (written * 1000) / Math.max(elapsed, 1);
if (bps < opts.minBps) {
stream.destroy(new Error(`stall_below_${opts.minBps}_bps`));
}
written = 0;
windowStart = Date.now();
}, opts.windowMs);
const origWrite = stream.write.bind(stream);
(stream as any).write = (chunk: any, ...rest: any[]) => {
written += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
return origWrite(chunk, ...rest);
};
stream.on("close", () => clearInterval(timer));
return stream;
}
| Symptom | Likely cause | Fix |
|---|---|---|
| OOM mid-stream | Unbounded buffering | pipeline + highWaterMark |
| Full timeout, 0 bytes late | Client stalled, producer kept running | abort signal + stall guard |
| Spiky memory | Buffer.concat of all chunks |
yield/write incrementally |
| Duplicate work on retry | No resume cursor | idempotent Range / cursor tokens |
Function URL and client contract
Document that clients must read promptly. For browsers, preferfetch + ReadableStream cancel on navigation. For mobile, set read timeouts shorter than the Lambda timeout so the server can reclaim concurrency.
bash
❌ Setting Lambda timeout to 15 minutes “just in case” without stall detection — you warehouse dead sockets.
# ✅ Probe backpressure locally (illustrative)
curl -N --limit-rate 1k "https://xxx.lambda-url.region.on.aws/export" -o /dev/null
# Watch CloudWatch: Duration rises, Max Memory used stays flat if backpressure works
Closing checklist
✅ Dos– ✅ Use
streamifyResponse + pipeline, not manual res.end(bigBuffer)– ✅ Propagate abort on
error/close– ✅ Metric stalled streams and aborted transfers
– ✅ Design resume tokens for large exports
– ✅ Keep writableHighWaterMark small relative to function memory ❌ Don’ts
– ❌ Don’t buffer the entire payload to “simplify” headers
– ❌ Don’t ignore
write() === false– ❌ Don’t rely on API Gateway for multi-MB streams (use Function URLs)
– ❌ Don’t leave producers running after client disconnect
Related reading
- Lambda Function URLs Streaming Response
- Lambda Plus Bedrock: Stream Tokens Without API Gateway Caps
- Lambda Timeouts, Retries, and DLQs
- Lambda Warm Pools for Agent Tools
Related: Lambda Response Streaming: Keep AI Coding Gateways Under Client Timeouts · Kafka to Lambda Backpressure: Lag SLOs That Protect Downstream Sinks · Day 43: Distillation for Edge and Lambda
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Lambda Ephemeral Storage: Safe /tmp Strategies for Large Artifacts - CheatCoders