Resposta ao OOM Killer
Quando o Kubernetes marca seu pod Node.js como OOMKilled, você tem minutos para decidir: vazamento, pico de tráfego ou limites mal configurados. Esta página faz a triagem de heap vs RSS e seleciona a mitigação correta.
Busque em todas as páginas da documentação
Quando o Kubernetes marca seu pod Node.js como OOMKilled, você tem minutos para decidir: vazamento, pico de tráfego ou limites mal configurados. Esta página faz a triagem de heap vs RSS e seleciona a mitigação correta.
Cartão de receita de referência rápida - pronto para copiar e colar.
# 1. Confirme OOMKilled (não CrashLoop por outros motivos)
kubectl describe pod <pod> -n production | grep -E "OOMKilled|Reason|Exit Code"
# 2. Verifique métricas de memória: heap vs RSS
# Grafana: process_resident_memory_bytes vs v8 heap used
# 3. Correlacione com deploy e tráfego
kubectl rollout history deployment/<svc>
# Compare RPS spike com a inclinação da memória
# 4. Mitigue
kubectl scale deployment/<svc> --replicas=<N+2> # pico de tráfego
kubectl rollout undo deployment/<svc> # deploy ruim
# vazamento: limite o tráfego, reinicialização em rolling, agende snapshot do heapQuando usar isto:
OOMKilled e Exit Code 137heapUsed permanece estável (buffers nativos, gRPC, sharp)// src/observability/memory-metrics.ts
import { monitorEventLoopDelay } from "node:perf_hooks";
import v8 from "node:v8";
const loopDelay = monitorEventLoopDelay({ resolution: 20 });
loopDelay.enable();
export function startMemoryReporter(log: (obj: object) => void, intervalMs = 30_000) {
setInterval(() => {
const mem = process.memoryUsage();
const heapStats = v8.getHeapStatistics();
log({
msg: "memory_sample",
rssMb: Math.round(mem.rss / 1024 / 1024),
heapUsedMb: Math.round(mem.heapUsed / 1024 / 1024),
heapTotalMb: Math.round(mem.heapTotal / 1024 / 1024),
externalMb: Math.round(mem.external / 1024 / 1024),
arrayBuffersMb: Math.round(mem.arrayBuffers / 1024 / 1024),
heapLimitMb: Math.round(heapStats.heap_size_limit / 1024 / 1024),
eventLoopP99Ms: Math.round(loopDelay.percentile(99) / 1e6),
});
loopDelay.reset();
}, intervalMs).unref();
}O que isso demonstra:
rss vs heapUsed sinaliza crescimento nativo/bufferexternal e arrayBuffers rastreiam a pressão de Buffer e TypedArray| Métrica | O que mede | Cresce quando |
|---|---|---|
heapUsed | Objetos JavaScript do V8 | Closures vazadas, Maps crescendo, objetos cacheados |
rss | Memória residente total do processo | Heap + add-ons nativos + pilhas de thread + mmap |
external | Objetos C++ vinculados ao JS | Buffers, criptografia nativa, alguns drivers ORM |
arrayBuffers | Memória ArrayBuffer desanexada | Cargas úteis binárias, uploads de arquivos |
| Sinal | Provável vazamento | Provável tráfego/capacidade |
|---|---|---|
| Tráfego | RPS estável | RPS aumenta 2-10x |
| Inclinação da memória | Linear ao longo de horas | Mudança abrupta no pico |
| Reinícios | Mesmo pod, mesmo padrão | Todos os pods, carga proporcional |
heapUsed | Acompanha o aumento do RSS | Pode permanecer moderado |
| Correção | Correção de código + snapshot | Escalar HPA, ajustar pool, aumentar limites |
OOMKilled?
├─ Deploy nos últimos 60 min? → fazer rollback primeiro
├─ Pico de RPS? → escalar réplicas, verificar tamanhos de pool
├─ heapUsed ≈ RSS crescendo? → caça a vazamentos (snapshot, clinic heapprofiler)
├─ RSS >> heapUsed? → verificar Buffers, sharp, gRPC, engine Prisma
└─ Limites muito apertados? → aumentar request/limit de memória com evidênciasresources:
requests:
memory: "512Mi"
limits:
memory: "768Mi" # margem acima do RSS em estado estávelNODE_OPTIONS=--max-old-space-size=512 abaixo do limite do k8s (deixe ~30% para não-heap)heapdump paralisa o event loop. Correção: Copie o pod, drene o tráfego, tire o snapshot no clone.external - Detectores de vazamento monitoram apenas o heap. Correção: Registre arrayBuffers e external a cada 30s.Map com chave userId cresce para sempre. Correção: LRU com entradas máximas ou Redis.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
clinic heapprofiler | Vazamento reproduzível em staging | Produção sob carga do cliente |
clinic doctor | Event-loop + memória combinados | Você já sabe que é OOM |
| Aumentar limites apenas | Pico de capacidade comprovado | RSS sobe sob tráfego estável |
| Mudar para worker threads | Loop bloqueante intensivo em CPU | API intensiva em I/O (não corrigirá vazamento) |
137 (128 + 9 SIGKILL). Eventos do Kubernetes mostram OOMKilled como a razão.
Use kill -USR2 <pid> se estiver conectado a heapdump, ou faça exec em um pod drenado. Nunca tire snapshot do único réplica sob tráfego total.
Eles causam exaustão e timeouts antes do OOM, mas o crescimento do pool ocioso aumenta o RSS. Verifique pg pool totalCount vs max.
Apenas em diagnósticos controlados. Forçar o GC oculta sinais de vazamento nas métricas.
Versões da Stack: Esta página foi escrita para Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, e NestJS 11.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026