"모든 배포가 네 프로세스 죽임. 질문은 in-flight request 도 같이 죽이느냐. Production-grade 면 답이 no."
배포의 수명주기
Node 서비스 redeploy 할 때 일어나는 일 (launchd, systemd, PM2, 또는 sane 한 토대 아래):
- 토대가 네 프로세스에 SIGTERM 보냄.
- 네 프로세스가 SIGKILL 치기 전 정리할 N 초 있음.
- SIGKILL 가 프로세스 갑작스럽게 종료. In-flight request 가 response 중간에 죽음.
네 프로세스가 SIGTERM 무시하면 모든 배포가 connection drop. "Zero-downtime 배포" 는 SIGTERM 처리 필요: *새 request 받기 멈춤, in-flight 마무리, 그 다음 깨끗하게 종료*.
패턴
import http from 'node:http';
const server = http.createServer(handler);
server.listen(3000);
let shuttingDown = false;
for (const sig of ['SIGINT', 'SIGTERM']) {
process.on(sig, () => {
if (shuttingDown) return; // ignore second signal
shuttingDown = true;
console.log(`got ${sig}, draining...`);
// Stop accepting new connections; finish in-flight ones
server.close((err) => {
if (err) {
console.error('drain error:', err);
process.exit(1);
}
console.log('drained cleanly');
process.exit(0);
});
// Backstop — force exit if drain takes too long
setTimeout(() => {
console.warn('drain timed out, force-exiting');
process.exit(1);
}, 10_000).unref();
});
}
중요한 다섯 줄: SIGTERM 처리, flag 설정 (idempotent), server.close() 호출, drain 시 종료, timeout 시 force-exit. 이게 전체 패턴.
잊혀진 정리
- DB connection — DB pool 에
.end()또는.close()호출. - 열린 file handle — JSONL log writer, append-only 파일 닫기.
- WebSocket connection — close frame 보내고, 클라이언트가 새 인스턴스에 재연결하게.
- Pending 작업 — queue flush, in-flight 작업을 'must retry' 로 마킹 등.
- 외부 subscription — Kafka/Redis/PubSub 에서 unsubscribe.
terminus 같은 라이브러리 사용.Healthcheck 조율
로드 밸런서가 healthcheck ping (GET /health) 보냄. Draining 시작 시 네 healthcheck 가 실패 반환 시작해야 — LB 한테 in-flight 마무리 동안 새 request 라우팅 멈추라고 말함. 없으면 LB 가 drain window 동안 새 트래픽 계속 보내, 포인트 무효:
let healthy = true;
process.on('SIGTERM', () => { healthy = false; /* then drain */ });
app.get('/health', (_req, res) => {
res.writeHead(healthy ? 200 : 503).end();
});
LB 가 503 봄, pool 에서 너 제거, drained request 마무리, 종료. 10 초 drain window 가 이제 zero 새 트래픽 포함 — drain 시작 전의 in-flight request 만.
Crash vs Graceful Stop
Graceful shutdown 은 *계획된* 종료용 — 배포, scale down, 수동 재시작. *계획 안 된* 종료엔 (uncaught exception, OOM, segfault) 관계없이 프로세스 죽음. 토대가 재시작; LB 가 healthcheck 실패 통해 알아채고 re-route. 패턴:
- 계획된 종료 → SIGTERM handler → drain → 깨끗하게 종료.
- 계획 안 된 종료 → crash, 토대 재시작, LB re-route.
둘 다 중요. 일부 팀이 uncaughtException 잡고 drain 시도; 보통 나쁜 아이디어 — 프로세스 상태가 이미 corrupt. 더 나음: 로그 + 빨리 종료 + 토대가 재시작하게.