graceful-shutdown · git:20260720.1dc980e · 2026-07-20 · sha256 b8643bd628ebcf48

graceful-shutdown git:20260720.1dc980eA

Immutable. This exact content is served forever at /api/v1/blob/b8643bd628ebcf48.

---
name: graceful-shutdown
description: Drain servers on SIGTERM so deploys and scale-downs drop zero requests and corrupt no jobs. Use when implementing shutdown handling or chasing errors that spike during every deploy.
---

# Graceful shutdown

Every deploy kills your processes. Errors that spike at deploy time are
not bad luck; they are an unimplemented shutdown sequence.

## Method

1. **Trap SIGTERM and flip readiness first.** On signal: mark the
   readiness/health endpoint unready so the load balancer stops sending
   new work, but keep serving in-flight requests. Liveness stays green;
   you are draining, not dead.
2. **Wait out the routing lag.** There is a gap between "unready" and
   "traffic actually stops" (LB health-check interval, k8s endpoint
   propagation). Sleep a few seconds before closing listeners; closing
   immediately resets connections the balancer already routed.
3. **Drain in dependency order.** Stop accepting new HTTP connections,
   finish in-flight requests, stop pulling new jobs from queues, finish
   or checkpoint running jobs, flush buffers (logs, metrics, producer
   queues), then close DB pools and connections last; everything above
   needs them.
4. **Bound the drain with a deadline.** Grace budget minus routing-lag
   sleep is your drain window (k8s `terminationGracePeriodSeconds`
   default 30s; set it above your request timeout). At the deadline,
   log what was abandoned and exit nonzero context: requests longer than
   the grace period must be redesigned as jobs.
5. **Make workers stop between units.** Job loops check a shutdown flag
   between items; long items heartbeat and checkpoint so the next worker
   resumes instead of restarting (see background-jobs). Un-acked messages
   returning to the queue is the mechanism working, provided jobs are
   idempotent.
6. **Verify under fire.** Rolling deploy while a load test runs: zero
   5xx and zero connection resets is the pass bar. Also send SIGTERM
   locally and watch the ordered log lines; shutdown code that never
   runs in dev does not exist.

## Boundaries

- SIGKILL cannot be handled; crash-safety (idempotent jobs, transactional
  writes, WAL) is the layer below this one, and you need it anyway.
- In-process caches and sticky sessions make every shutdown lossy by
  design; externalize session state rather than extending grace periods.
- Do not block shutdown on flushing to a dependency that is itself down;
  bound each flush and prefer losing telemetry to hanging the deploy.