Chaos engineering is the discipline of breaking systems on purpose to discover failure modes before customers do. It is not the discipline of randomly turning off servers, despite its common caricature. The mature practice is hypothesis-driven: state what should happen when a dependency fails, run an experiment that triggers the failure, observe whether the hypothesis holds, and feed the gap back into design.
The motivation: a service that has never been observed failing is not a reliable service. It is a service whose failure modes have not yet been discovered.
The Hypothesis-Driven Method
Four steps constitute a chaos experiment:
- Steady-state — define the normal behaviour: latency p99 < 300ms, error rate < 0.1%, throughput > 1000 RPS. This is the metric the experiment will try to perturb.
- Hypothesis — “If I terminate 30% of the payment-service’s pods, the steady-state metric will remain inside the SLO because the load balancer has redundancy and the degraded-resolution path will return a 503, not a hang”.
- Injection — deliberately trigger the failure: kill 30% of the pods in production (or, more conservatively, in a staging mirror) and observe.
- Conclude — if the hypothesis holds, file the experiment as confirmation and increase the blast radius next time. If it fails, abort the experiment, then treat the gap as a bug — the system broke under a failure mode the design should have covered.
Two non-negotiables: always have an abort condition, and always run during business hours. A chaos experiment run overnight, with no one to abort, is the worst-case-nightmare — an incident of your own making.
Failure Injection Taxonomy
The failure space is broader than just “kill a pod”:
| Failure class | Example injection | Tool family |
|---|---|---|
| Compute / network failure | Terminate an instance, kill a pod | Chaos Monkey, ChaosMesh PodKill |
| Network latency / loss | Inject 200ms latency between two services, drop 5% of packets | tc/netem, Toxiproxy, ChaosMesh NetworkDelay/PacketLoss |
| Disk failure | Fill the disk to 95%, mark the disk read-only | ChaosMesh IoChaos, stress-ng |
| DNS / dependency failure | Make a dependent host unreachable | Toxiproxy, custom DNS records |
| Time skew | Skew the clock on one node by +/- 1 minute | ChaosMesh TimeChaos (relevant to Spanner-style systems) |
| Process-level corruption | Exhaust file handles, exhaust threads, leak memory | stress-ng, ChaosMesh StressChaos |
| Region / zone failure | Blackhole an entire AZ | AWS Fault Injection Simulator; manual route53 changes |
The class of injection should match the class of risk you’re hypothesising about. If you’re worried about long-tail latency, inject network delay, not pod kills. If you’re worried about regional resilience, inject a region blackhole.
Blast Radius and Steady State
Two design axes for experiments:
| Axis | Range | Ramp strategy |
|---|---|---|
| Blast radius | Single pod → cluster → region → multi-region | Start small, double with each successful experiment |
| Steady-state metric | Looser (e.g., “no user impact”) → tighter (“p99 < 600ms”) | Start loose, tighten as the system proves resilient |
The discipline of doubling the blast radius after each successful experiment surfaces the resilience ceiling gradually. The first time a hypothesis fails is the bug you fix; subsequent experiments probe adjacent failure modes.
Mature chaos programs run continuous experiments (every deployment, intentional small failures) and periodic larger-blast-radius experiments (quarterly, ambitious). The continuous experiments are alarm bells; the periodic are stress tests.
Game Day Structure
A game day is a scheduled exercise where the team runs experiments deliberately, observes outcomes, and writes up findings. A typical structure:
- Pick a hypothesis — start with one the team is uncertain about, not one everyone is confident will hold.
- Pre-game briefing — review steady-state metrics; declare abort conditions; assign observer roles; ensure on-call hasn’t been distracted.
- Run — inject the failure; observe metrics; assert whether the hypothesis holds.
- Post-game — within 24 hours, produce a written report: hypothesis, observed outcome, gap, design fix. If the hypothesis failed, an executable item goes to the team’s backlog.
A game day is the closest a team gets to deliberate, low-stakes practice for the high-stakes incidents that customers will eventually trigger. Teams that game-day quarterly go into real incidents already practiced.
Resilience Patterns vs Chaos Experiments
Two complementary disciplines:
| Pattern | What it is | What chaos validates |
|---|---|---|
| Timeout | Bound the maximum wait for a dependency | The system fails closed under dependency stall — no thread pile-up |
| Retries w/ jittered backoff | Retry transient failures with bounded attempts; jitter avoids coordinated retry storms | A transient failure does not become a thundering retry herd |
| Circuit breaker | Stop calling an unhealthy dependency; let it recover | The system stops cascading failure when a dependency is unhealthy |
| Bulkhead | Isolate thread pools / connections per dependency | A failure downstream of one dependency does not starve the others |
| Graceful degradation | Partial response when a dependency is missing | The user-visible path survives a dependency loss with degraded but useful behaviour |
The patterns are the design; chaos is the verification that the design actually held. Each pattern in isolation has well-known failure modes (timeouts without backpressure, retries without jitter, circuit breakers that never re-open); chaos experiments surface the gap between intent and reality.
Practice Trajectory
- List five “this should just work” beliefs about your system — for example, “if the cache goes down, we fall through to the database”. These are your first five experiment hypotheses.
- Pick one experiment and run it in staging: kill the cache pod; measure the steady-state metrics during the fallback. Identify whether the hypothesis held; fix the gap if not.
- Schedule a quarterly game day with your team. Pick a hypothesis about an AZ failure. Document the abort conditions and the post-game write-up template.
- Audit one resilience pattern in your code — say, retries. Jitter the backoff; rerun with and without jitter in a stress test, observe the difference in herd-retry behaviour.
- Take an incident post-mortem from last year. Reconstruct the failure as a chaos experiment; run it in staging. Did the system recover the same way? Are the mitigations from the post-mortem still in place and observable?
When It’s the Right Tool
| Situation | Takeaway |
|---|---|
| System has a steady-state hypothesis but you have never tested it | Run a chaos experiment — verifying the hypothesis is a SLO |
| An incident post-mortem suggested a fix | Run a chaos experiment as a regression test for the fix |
| Newer system, low traffic, no observed incidents yet | Generate some — small blast radius first; learn the failure shape early |
| System is mission-critical, traffic greater than a chaos experiment would naturally simulate | Game-day-style scheduled exercises at controlled scale, with abort conditions |
| “We don’t run chaos because we don’t want to break production” | Run in staging first; production chaos as the long-term goal — the system that survives staging-only chaos is a system you can’t trust in production |