cd ../lexicon
sys/docs/lexicon/gray-failure.md
Lexicon Entry

Gray Failure

A failure mode where a component experiences partial or intermittent degradation that is invisible to conventional binary health checks, yet is perceptible to dependent services or end users through elevated latency, error rates, or data corruption. It matters because it invalidates the failover logic of most orchestration systems, which are built on a fail-stop assumption rather than a fail-slow or fail-partial one.

Gray failure sits in the observability gap between fail-stop (crash, process exit, connection refused) and fully healthy. The canonical example is a node whose liveness probe returns 200 OK in 2ms because the health-check handler is served from a separate, unburdened thread pool, while the actual data path is saturated, GC-thrashing, or stuck on a slow disk I/O queue. The control plane sees green; the data plane sees red. This divergence is often called differential observability: the perspective of the monitoring system (the observer) and the perspective of the actual traffic consumer (the client) disagree on the health of the same node.

Root causes are rarely singular. Common triggers include:

  • Resource exhaustion asymmetry: CPU throttling (cgroup limits, noisy neighbor on shared hosts) that only affects request-serving threads, not the lightweight heartbeat goroutine.
  • Firmware/driver-level degradation: a NIC or NVMe device operating at reduced link speed or with rising retransmit/error counters, well below the threshold that triggers hardware alarms.
  • Non-atomic dependency failure: a downstream cache or DNS resolver that succeeds intermittently, causing p99 latency to spike without violating a simple up/down SLA.
  • Memory fragmentation or GC pause creep: the process is alive and responsive to trivial pings, but application-level request handling stalls for hundreds of milliseconds.

Detecting gray failure requires shifting from liveness-based to workload-representative health signals. Practical mitigations include synthetic canary requests that exercise the actual serving path (not a stub handler), tail-at-scale latency-based ejection in load balancers (e.g., outlier detection in Envoy comparing a node’s p99 against cluster median), and cross-validation schemes where peer nodes vote on a suspect’s health rather than relying solely on self-reported status — because a gray-failed node’s own health check is precisely the signal you cannot trust. Systems like Google’s Falcon paper formalized this by introducing a data-plane failure detector that runs alongside the control-plane one and reconciles disagreements.

Architecturally, gray failure forces a shift away from binary circuit breakers toward graduated degradation models: weighted load shedding, request hedging (issuing a duplicate request to a second replica if the first exceeds a latency budget), and proactive quarantine of suspect nodes at reduced traffic weight rather than an all-or-nothing eviction. Ignoring this failure class is a common root cause of cascading outages, since a single gray node can silently consume a disproportionate share of retries and connection pool slots from every upstream caller, degrading the entire fleet’s tail latency while every dashboard reports 100% node availability.