5 views
Heap snapshots are the nuclear option for memory leaks — and nuclear options pause the world. Under incident load, an unthrottled v8.writeHeapSnapshot() can freeze the event loop for seconds, inflate RSS while serializing, and dump customer PII into an unencrypted tarball on an ephemeral disk. The unfair advantage is a gated capture path: rate-limited, redacted, streamed to S3, and never triggered by a panicked engineer SSH’ing into a hot pod.
⚡ TL;DR: Expose a signed admin endpoint (or SIGUSR2) that acquires a process-wide mutex, writes to /tmp with a hard size budget, scrubs string samples, uploads to a private S3 bucket with SSE-KMS, then deletes local files. Cap to 1 snapshot / 15 min / instance. Pair with Node Memory Leaks: Detached Cache Graphs and Node Flamegraphs on ECS.
Node Heap Snapshots in Production: production guidance
typescript
// src/heap-snapshot-gate.ts
import { writeHeapSnapshot } from "node:v8";
import { randomUUID } from "node:crypto";
import { unlinkSync, statSync, createReadStream } from "node:fs";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
let inflight = false;
let lastAt = 0;
const MIN_INTERVAL_MS = 15 * 60_000;
const MAX_BYTES = 2 * 1024 * 1024 * 1024;
export async function captureHeap(reason: string) {
const now = Date.now();
if (inflight) throw new Error("snapshot_inflight");
if (now - lastAt < MIN_INTERVAL_MS) throw new Error("snapshot_rate_limited");
inflight = true;
lastAt = now;
const id = randomUUID();
const path = `/tmp/heap-${id}.heapsnapshot`;
try {
// ✅ Advertise pause to metrics before the stop-the-world
metrics.gauge("heap.snapshot.paused", 1);
writeHeapSnapshot(path);
const size = statSync(path).size;
if (size > MAX_BYTES) throw new Error(`snapshot_too_large:${size}`);
await s3.send(new PutObjectCommand({
Bucket: process.env.HEAP_BUCKET!,
Key: `heaps/${process.env.SERVICE}/${id}.heapsnapshot`,
Body: createReadStream(path),
ServerSideEncryption: "aws:kms",
Metadata: { reason },
}));
return { id, size };
} finally {
try { unlinkSync(path); } catch {}
metrics.gauge("heap.snapshot.paused", 0);
inflight = false;
}
}
typescript
// ❌ Free-for-all SIGUSR2 with no rate limit during a sev-1
process.on("SIGUSR2", () => writeHeapSnapshot());
Scrub PII before anyone downloads
Chrome DevTools heap files contain string values. Treat them like prod dumps.typescript
Never email raw // src/scrub-heapsnapshot.ts
import { Transform } from "node:stream";
const SECRET = /(api[_-]?key|password|authorization|Bearer\s+\S+|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/gi;
export function scrubStrings() {
return new Transform({
transform(chunk, _enc, cb) {
// ✅ Blank obvious secret-shaped strings in snapshot text
cb(null, Buffer.from(String(chunk).replace(SECRET, "[REDACTED]")));
},
});
}
.heapsnapshot files in Slack. Upload only to a bucket with object lock and short-lived signed URLs for the on-call who requested capture.
Capture under load without cascading
| Control | Why |
|---|---|
| Single-flight mutex | Two concurrent snapshots OOM the task |
| Flip readiness false briefly | Stop new traffic during pause |
| Capture on a canary task | Prefer one replica, not the whole ASG |
Alarm on heap.snapshot.paused |
Detect accidental loops |
Auto-delete local /tmp |
Ephemeral disk fills kill the container |
bash
# ✅ Trigger on ONE ECS task, not the whole service
aws ecs execute-command --cluster prod --task $TASK \
--interactive --command "node -e \"require('./dist/heap-snapshot-gate').captureHeap('sev1-rss')\""
Closing checklist
✅ Dos – ✅ Rate-limit + single-flight every production snapshot – ✅ Stream to SSE-KMS S3; delete local immediately – ✅ Scrub secrets/PII-shaped strings before sharing – ✅ Prefer one canary task over fleet-wide capture – ✅ Record reason, size, and operator identity in metadata ❌ Don’ts – ❌ Don’t bind rawwriteHeapSnapshot to an unauthenticated HTTP route
– ❌ Don’t snapshot every pod during a memory incident
– ❌ Don’t leave multi-GB files on ephemeral disks overnight
– ❌ Don’t paste heap paths into public tickets
Related reading
- Node Memory Leaks: Detached Cache Graphs That Heap Snapshots Reveal
- Node Flamegraphs on ECS: Continuous Profiling That Survives Scale
- Event Loop Blockers: Perfetto Traces That Metrics Alone Will Miss
- AsyncLocalStorage Overhead: Measure Before Wrapping Every Request Path
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.
