Chaos Engineering for Teams That Have Never Broken Anything on Purpose
A platform team at a mid-sized SaaS company once ran a tabletop exercise: what happens if the primary Redis cluster becomes unreachable for ninety seconds? Nobody could answer with confidence. The application code had a fallback path that read from the database when the cache missed, written two years earlier by an engineer who had since left. Nobody knew if it still worked. Nobody knew if the database could absorb the traffic that would suddenly bypass the cache. Nobody knew if the connection pool would exhaust before the ninety seconds ended or whether it would recover on its own afterward.
This is not a story about a company with weak engineers. It is the default condition of almost every production system past a certain size. Teams accumulate fallback paths, retry logic, circuit breakers, and failover mechanisms over years, and almost none of that code runs except during the exact failure it was written for — which, by definition, happens rarely, unpredictably, and usually at the worst possible time. The mechanism designed to protect the system during an outage is itself untested, because testing it looks exactly like causing the outage it exists to prevent.
That is the specific problem this article addresses: not whether a system is reliable under expected load, which most QA and performance testing programs already cover reasonably well, but whether it survives partial, realistic degradation of the things it depends on — a slow database, a DNS resolver returning stale records, a downstream API responding with 5xx errors, a disk quietly filling up, an availability zone disappearing. Chaos engineering is the discipline built to answer that question directly, by causing the failure on purpose, under controlled conditions, before it happens on its own.
The reason most engineering organizations that attempt chaos engineering either abandon it or never get past a single Chaos Monkey demo is not that the idea is wrong. It is that they skip the sequencing. They install a tool, terminate an instance in production during a Tuesday afternoon, generate either a non-event or a minor incident, and conclude that chaos engineering is either pointless or too dangerous for their organization. Both conclusions are usually wrong, and both come from starting in the wrong place.
What Chaos Engineering Actually Tests, and What It Does Not
Chaos engineering is frequently confused with adjacent practices, and the confusion causes real damage — either by making teams think they are already doing it when they are not, or by making them expect chaos engineering to do a job it was never designed for.
It is not load testing. Load testing asks whether a system holds up when traffic increases along a dimension you control and expect. Chaos engineering asks whether a system holds up when a dependency degrades in a way you did not schedule and cannot fully predict. A load test tells you your checkout service handles 5,000 requests per second. A chaos experiment tells you what happens to that same checkout service when the payment gateway it calls starts responding in 8 seconds instead of 200 milliseconds, while load stays completely normal.
It is not disaster recovery testing in the traditional sense, though the two overlap and reinforce each other. A DR test typically validates a known, rehearsed failover procedure — restore from backup, promote a standby region, execute a runbook. Chaos engineering is often used to discover failure modes nobody wrote a runbook for yet, because nobody knew they existed. Google's internal DiRT (Disaster Recovery Testing) program, described in the Chaos Engineering book published by O'Reilly with contributions from Google, Netflix, and other practitioners, sits at the boundary between the two: large-scale, company-wide exercises that combine planned failover validation with genuinely unscripted failure injection.
It is not fuzzing or unit-level fault injection, although those are legitimate and complementary techniques. Fuzzing targets a single function or parser with malformed input. Chaos engineering operates at the level of a running, deployed system and its real infrastructure dependencies — network, compute, storage, other services — rather than at the level of a code path.
It is not "randomly breaking production for fun." This is the single most damaging misconception, largely inherited from an oversimplified retelling of Netflix's Chaos Monkey. The actual practice, as defined by the engineers who formalized it, is disciplined, hypothesis-driven, and scoped. The canonical definition, published by the authors of the Principles of Chaos Engineering (several of them former Netflix engineers who went on to found Gremlin), states it precisely:
"Chaos Engineering is the discipline of experimenting on a system in order to build confidence in the system's capability to withstand turbulent conditions in production."
Confidence-building through experimentation is the operative phrase. An experiment has a hypothesis, a measured steady state, a controlled variable, and a defined method of stopping it. Randomly triggering failures without measurement is not chaos engineering — it is just an outage you caused yourself, with none of the learning.
The five principles laid out at principlesofchaos.org are worth taking seriously as a checklist for whether something calling itself a chaos experiment actually is one:
- Build a hypothesis around steady-state behavior — define what "normal" looks like in terms of measurable output (throughput, error rate, latency percentiles), not internal implementation detail.
- Vary real-world events — the injected fault should resemble something that actually happens to this system: a dependency timing out, a host disappearing, a network partition, a spike in traffic composition.
- Run experiments in production — because staging environments diverge from production in traffic patterns, data shape, scale, and configuration in ways that hide the failure modes that matter. This principle is the most frequently misapplied by teams new to the practice, and later sections in this article deal with why it should be the last stage of a rollout, not the first.
- Automate experiments to run continuously — a one-time chaos exercise finds one moment's worth of weaknesses. Systems, dependencies, and code change continuously, so verification has to as well.
- Minimize blast radius — every experiment is designed so that, if the hypothesis is wrong and the system does not tolerate the fault gracefully, the damage is contained and recoverable.
Every failure in adopting chaos engineering that this article discusses traces back to violating one of these five principles, usually principle three (running in production before the organization is ready) or principle five (failing to actually bound the blast radius before pulling the trigger).
Why This Matters More Now Than the Framing Suggests
It would be easy to write this as a generic argument that failure happens and testing for it is prudent. That argument has been true for twenty years and is not, by itself, a reason to read another article about it. The more specific and more current reason this deserves attention now is architectural: the systems most companies operate today fail in ways that are structurally different from the systems chaos engineering was invented to protect.
Netflix built Chaos Monkey around 2011 while migrating from a monolithic data center architecture to AWS, specifically to force engineers to build services that could tolerate the transient instance failures that come standard with commodity cloud infrastructure. That was a relatively narrow failure class: an EC2 instance disappears, and the service in front of it needs to keep working. Contemporary systems have a much wider surface of correlated, partial, and slow failure:
- A SaaS company running on Kubernetes has pods that get evicted under memory pressure, nodes that get replaced by the cluster autoscaler, and service meshes that silently retry failed calls in ways application code never sees.
- A fintech platform depends on four or five external providers for KYC, card processing, fraud scoring, and banking rails, each with its own latency profile, error budget, and outage history — none of which the platform team controls.
- A serverless architecture on AWS Lambda has cold starts, concurrency throttling, and downstream throttling from services like DynamoDB or Kinesis that behave completely differently from a fleet of long-running virtual machines.
- Almost every modern backend now depends on at least one large third-party API for search, payments, email, analytics, or AI inference, each an unmonitored, unowned, single point of correlated failure shared with every other customer of that provider.
The unifying property across all of these is that the failure classes are partial and dependency-shaped rather than binary and host-shaped. A host either is up or it is down, and that failure mode is comparatively easy to test for and design around with basic redundancy. A downstream API that responds successfully 97% of the time but takes 12 seconds on the other 3%, or a DNS resolver that occasionally returns a stale record for a decommissioned load balancer, is a failure mode that standard health checks, standard redundancy, and standard load testing do not catch. These are exactly the conditions chaos engineering, done correctly, is built to surface.
AWS made this argument explicit and operational when it launched a dedicated managed service for the purpose. The AWS Fault Injection Service documentation frames the product around the same idea: an "experiment template" defines actions (the specific faults to inject), targets (which resources), and stop conditions (CloudWatch alarms that automatically halt the experiment if things go wrong) — a managed version of the same disciplined structure the Principles of Chaos Engineering describe, now packaged as a first-party AWS product rather than something only Netflix-scale engineering organizations could build internally. That a major cloud provider now sells fault injection as a managed service, with automatic circuit breakers built into the product itself, is a signal that the practice has moved from a niche capability of hyperscale companies to something ordinary engineering teams are expected to have access to and use responsibly.
The Adoption Failure Pattern
Before laying out a better sequence, it is worth being specific about how chaos engineering adoption actually fails, because the failure pattern is consistent enough to name.
A team reads about Chaos Monkey, installs an open-source chaos tool, and — often within the first week — runs an experiment that terminates a production instance or injects network latency into a live service, without first establishing a measured steady state, without a stop condition, and without informing the on-call engineer. One of three things happens:
- The system absorbs the fault with no visible effect, the team concludes "nothing happened" and treats the exercise as a waste of time, not realizing that a null result with no rigorous measurement is not evidence of resilience — it is evidence of nothing.
- The system does not absorb the fault, an alert fires, an on-call engineer who was not told an experiment was running spends forty minutes investigating what looks like a spontaneous incident, and the postmortem concludes that fault injection is unsafe.
- The fault cascades in an unanticipated way — the instance termination that was supposed to be routine cache-node loss triggers a stampede of reconnecting clients that overwhelms the remaining nodes — producing an actual customer-facing outage.
All three outcomes are more or less guaranteed by the same root cause: the team started with an untargeted experiment against a system whose steady-state behavior was not well instrumented, without a scoped blast radius, and without organizational buy-in on what was about to happen. None of that is a knock against fault injection as a technique. It is evidence that fault injection has prerequisites, the same way performance testing has prerequisites (you cannot usefully load test a system you cannot monitor).
The practical implication is that the correct first deliverable of a chaos engineering initiative is not an experiment. It is an honest assessment of whether the organization has the three prerequisites that make experimentation safe and informative at all.
The Three Prerequisites: A Diagnostic Checklist
Before running the first fault injection experiment anywhere, even in a disposable staging sandbox, verify each of the following. If any answer is no, address that gap first — running experiments without it does not accelerate learning, it just adds risk without adding signal.
1. Observability sufficient to detect a degraded state, not just a down state.
- Can you see p50, p95, and p99 latency per service, not just average latency or uptime?
- Can you see error rates broken down by dependency, not just an aggregate 5xx count?
- Do you have a dashboard someone can watch during an experiment, live, without needing to query logs after the fact?
- Is there an alerting threshold that would fire on the specific degraded condition you're about to cause, or would it only fire on total outage?
2. A rollback or abort mechanism that works faster than the damage accumulates.
- Can the fault you are about to inject be stopped programmatically and immediately, or does undoing it require a manual, multi-step process?
- Have you tested the abort mechanism itself, separately from the fault it is meant to stop?
- Is there a person with the authority and the access to pull the abort trigger who is actively watching during the entire experiment window?
3. Organizational agreement on ownership, scope, and communication.
- Does the on-call engineer for the target system know the experiment is happening, when, and what it will do?
- Is there a named owner for the experiment who is accountable for the decision to run it and the decision to stop it?
- Has anyone who could be paged during the experiment window been told not to treat the resulting alerts as a real incident, and been given a way to distinguish the two quickly?
A team that cannot answer "yes" to all three groups is not ready to inject faults anywhere with real consequences, including a staging environment that other teams depend on for their own testing. That is not a criticism — most engineering organizations, even good ones, are missing at least one of these when they first consider chaos engineering. It is simply the honest starting point.
A Staged Rollout Model: The Blast-Radius Maturity Curve
Existing maturity models for chaos engineering (and there are several published by vendors) tend to organize around tooling sophistication — manual experiments, then scripted, then automated, then continuous. That framing is useful for a mature practice deciding how to invest further, but it is close to useless for a team asking "what do we do in month one." What actually determines whether an organization is ready for the next stage is not tooling maturity. It is how contained the blast radius is and how reversible the consequences are if the hypothesis turns out to be wrong.
The following model is built specifically around that axis, for this article, because that is the dimension that determines safety and organizational trust, and trust is the actual constraint on how fast a chaos engineering program can grow.
Stage 0 — Game days. No code runs, no infrastructure is touched. A cross-functional group (engineering, on-call, sometimes product and support) works through a written failure scenario together: "our primary payment processor starts returning timeouts for 15% of requests — walk through what actually happens, step by step, in our real system." This surfaces knowledge gaps — nobody knows the retry configuration, nobody knows if the circuit breaker library is actually wired up correctly, nobody knows who gets paged — cheaply, with zero risk to any running system. Google's DiRT program and many enterprise incident-response programs run large-scale versions of this. It costs a few hours of senior engineering time and reliably produces a punch list of gaps worth fixing before stage 1.
Stage 1 — Fault injection in an isolated, non-production environment. The first time a fault is actually injected into a running system, it should be in an environment that (a) does not serve real customer traffic, (b) is topologically similar enough to production that the results generalize, and (c) can be broken without any external consequence. This is where a team builds the muscle memory of the full experiment lifecycle — hypothesis, steady-state measurement, injection, observation, abort, rollback, retrospective — without live-traffic stakes attached to getting it wrong. Note that "isolated" is doing real work in that sentence: a shared staging environment that ten other teams rely on for their own testing is not a safe stage-1 target, because breaking it has real organizational cost even though it is not customer-facing.
Stage 2 — Production-adjacent experiments. This is the stage most maturity models skip, and its absence is a large part of why teams jump straight from "safe staging tests" to "risky production tests" with nothing in between. Production-adjacent means the experiment runs against production infrastructure and production-realistic conditions but is shielded from real customer impact — a shadow deployment that receives mirrored production traffic but whose responses are discarded, a canary instance serving a tiny percentage of real traffic with an aggressive automatic rollback, or a fully production-configured environment serving synthetic transactions generated to look statistically like real usage. AWS's own tutorial for simulating a connectivity event with FIS is explicitly built around this kind of scoped target — a specific subnet or a specific percentage of traffic, not "everything."
Stage 3 — Bounded production experiments. The first real production fault injection should target the smallest unit that can produce a meaningful result: one instance out of a fleet of fifty, one availability zone out of three, a single non-critical background job, or a feature already gated behind a flag so it can be disabled instantly for everyone if needed. The defining property of stage 3 is that a human is actively watching in real time, the stop condition is automatic (not "someone will notice and manually cancel it"), and the scope is small enough that even a fully wrong hypothesis produces an incident smaller than the organization's normal incident severity threshold.
Stage 4 — Continuous, automated production experimentation. This is where Netflix's Chaos Monkey (which randomly terminates instances during business hours on a schedule, by default, according to its own GitHub documentation) and its more targeted sibling tools live, alongside the "automate experiments to run continuously" principle. Reaching this stage without stages 0 through 3 first is exactly the failure pattern described earlier. Reaching it after them is a genuinely different, much lower-risk proposition, because by this point steady-state monitoring, abort mechanisms, and organizational trust in the process already exist and have been exercised repeatedly at smaller scale.
A team does not need to spend months at each stage. A well-run organization with strong existing observability might move from stage 0 to stage 2 in a matter of weeks. The point of the model is not to enforce a slow pace — it is to enforce that each stage's prerequisites are genuinely met before advancing, rather than skipped because the tooling made it technically possible to skip them.
How the Right Approach Differs by Architecture
The stage model above holds regardless of architecture, but what an experiment actually looks like — and which fault classes matter most — differs substantially between a monolith, a microservices architecture, and a serverless system. Treating "install a chaos tool" as architecture-agnostic advice is one of the more common mistakes in how this practice gets introduced.
Monolithic architectures
A monolith concentrates failure differently than a distributed system does. There are fewer network hops between components (most calls are in-process function calls, not network calls), so the classic microservices fault classes — one service's latency cascading into another's timeout — matter less internally. But a monolith typically has a small number of genuinely critical external dependencies (one primary database, sometimes one cache layer, a handful of third-party APIs), and its failure mode when one of those goes down is often total rather than partial, because there is no service boundary to contain the blast radius internally.
The chaos engineering priorities for a monolith are therefore narrower and arguably higher-value per experiment:
- Database connection exhaustion and slow-query behavior. What happens when the primary database's query latency triples? Does the application server's thread pool or connection pool exhaust, and does that then take down endpoints that have nothing to do with the slow query?
- External API dependency failure. A monolith calling out to a payment processor, an email provider, or an identity provider from within a request-handling thread is a classic source of total outages when that one dependency degrades, because the failure is not contained to a separate service — it blocks the same process handling every other request.
- Disk pressure on the host. Logging, temp file writes, and session storage on local disk are more common in monolith deployments than in service-oriented ones, and disk-full conditions are a frequently overlooked failure class precisely because they develop slowly and asymptomatically until they don't.
- Deployment and restart behavior. Because there's one large process instead of many small ones, a monolith's restart or redeploy is a heavier, riskier event — testing what happens to in-flight requests during a rolling restart matters more here than in a system built from many small, quickly-restartable services.
A monolith without a service mesh or dedicated infrastructure orchestration usually needs simpler tooling: OS-level fault injection tools that can throttle network interfaces, fill disk space, or kill processes on a single host (the Linux tc command for network shaping, or open-source tools like Chaos Toolkit, are frequently sufficient) rather than a cloud-native platform built around targeting individual pods or containers.
Microservices architectures
This is the environment chaos engineering was largely built for, and where the discipline pays off fastest, because the number of possible partial-failure combinations grows combinatorially with the number of services and their call graph. The priority fault classes shift toward the interactions between services rather than the internals of any one:
- Dependency timeout and slow-response propagation. Service A calls Service B, which calls Service C. If C slows down without failing outright, does that latency propagate up through B's own timeout, or does B's timeout fire first and shed the request cleanly? This single question — "does a slow dependency degrade gracefully or does it become a full outage" — is arguably the single most valuable chaos experiment a microservices team can run, and it maps directly onto AWS FIS's Lambda and ECS/EKS latency-injection actions, or a service mesh's built-in fault injection (Istio's virtual services, for example, support declarative delay and abort injection without any separate chaos tool at all).
- Retry storms and cascading amplification. When B times out calling C, does B retry? Does A also retry its call to B independently? If every layer retries independently with no coordination, a transient blip in C can turn into several multiples of C's normal traffic hitting it simultaneously — a self-inflicted denial-of-service commonly called a retry storm. The AWS Builders' Library article on timeouts, retries, and backoff with jitter, written by principal engineer Marc Brooker, is the standard primary-source treatment of why naive retry logic amplifies rather than absorbs failure, and why jittered exponential backoff plus a retry budget is the accepted mitigation.
- Network partition between services. In a system running across multiple nodes or availability zones, a partial network partition — where A can reach B but not C, rather than a clean full outage — produces behavior that is much harder to predict than total unavailability, because different parts of the system disagree about what state the world is in.
- DNS and service-discovery failures. Microservices architectures depend heavily on internal DNS or service discovery (Kubernetes DNS, Consul, cloud-provider internal DNS) to find each other. A stale DNS cache entry pointing to a decommissioned instance, or a service-discovery registry that is slow to deregister an unhealthy instance, produces failures that look confusing precisely because the infrastructure "looks" healthy from the outside while individual calls quietly fail or hang.
- Clock skew between nodes. Distributed systems that rely on timestamps for ordering, deduplication, cache expiry, or distributed locking are vulnerable to clock drift between nodes. Google's Spanner database, described in the original OSDI 2012 paper, was built around an entire subsystem (TrueTime) specifically to bound and expose clock uncertainty to the rest of the system, precisely because the authors treated unbounded clock skew as a correctness hazard serious enough to justify custom hardware (GPS and atomic clocks in Google's data centers) to control it. Most engineering teams do not have Google's budget for that, which is exactly why testing what a few hundred milliseconds of clock drift does to token expiry, cache TTLs, or distributed lock leases is worth doing deliberately rather than discovering it in production.
Kubernetes-native environments have particularly mature tooling for this layer: LitmusChaos, a project incubating under the CNCF, provides pod-level fault injection (pod kills, CPU/memory stress, network chaos) as native Kubernetes custom resources, and AWS FIS's EKS actions (aws:eks:pod-delete, aws:eks:pod-network-latency, aws:eks:pod-network-packet-loss) cover much of the same ground for teams already standardized on AWS.
Serverless architectures
Serverless (primarily AWS Lambda, Azure Functions, and Google Cloud Functions, plus the managed services they typically sit in front of) inverts several assumptions that the previous two architectures share. There is no long-running host to terminate, no persistent connection pool to exhaust in the traditional sense, and often no direct network layer for the team to manipulate — the cloud provider owns that entirely. The failure classes that matter shift accordingly:
- Cold-start latency under invocation-rate change. A function that has scaled to zero and needs to cold-start when traffic resumes behaves very differently under load than one already warm. AWS FIS's
aws:lambda:invocation-add-delayaction, which can inject up to 900,000 milliseconds of startup delay, exists specifically because this failure mode is otherwise nearly impossible to force on demand for testing. - Downstream throttling propagating back as a functional failure rather than a clear error. A Lambda function calling DynamoDB, Kinesis, or another AWS service that throttles under load needs to handle that throttling explicitly (via the SDK's built-in retry and backoff, or its own logic). AWS FIS directly supports testing this with actions like
aws:dynamodb:global-table-pause-replicationand Kinesis's provisioned-throughput and expired-iterator exception injection — surfacing exactly the failure a serverless data pipeline would otherwise only discover during a real traffic spike. - Concurrency limit exhaustion. Serverless platforms cap concurrent executions per account or per function. A downstream dependency slowing down doesn't just make individual invocations slower — it can cause invocations to pile up against that concurrency ceiling, silently throttling or queuing unrelated invocations of the same function. This is a fault class with essentially no equivalent in a fixed-fleet monolith or microservices deployment, because there the "ceiling" is capacity you provisioned and can observe directly, whereas in serverless it is a platform-level limit that behaves like a hidden dependency.
- API integration error injection.
aws:lambda:invocation-http-integration-responselets a team force a Lambda function fronted by API Gateway to return a specific HTTP status and headers, which is the serverless-native equivalent of the dependency-error injection microservices teams do at the network layer — useful for verifying that client applications (mobile apps, frontend SPAs, third-party API consumers) handle a sudden wave of 429s or 503s gracefully rather than retrying in a tight loop.
The overarching architectural lesson is that "chaos engineering" is not one technique applied uniformly. It is a discipline whose specific experiments have to be redesigned around what "a realistic partial failure" actually looks like in the architecture in front of you. A team copying a Netflix-style instance-termination experiment onto a serverless architecture, where there is no instance to terminate, is applying the letter of the practice while missing the actual point of it.
Fault Classes: A Reference Table
The following table consolidates the fault classes referenced throughout this article, mapped to the architectures where they matter most, a representative tool or mechanism for injecting each one, and the specific signal a team should be watching for during the experiment — not just "did it break," but "what does breaking well versus breaking badly actually look like here."
| Fault class | Where it matters most | Representative injection mechanism | What "graceful" looks like when watching the experiment |
|---|---|---|---|
| Dependency timeout / slow response | Microservices, monolith (external APIs) | Service mesh fault injection (Istio delay), AWS FIS Lambda/ECS/EKS latency actions, Toxiproxy | Caller's own timeout fires before user-facing latency becomes unacceptable; no thread/connection pool exhaustion upstream |
| Instance/host/pod termination | Monolith (single host), microservices, Kubernetes | AWS FIS terminate-instances, EKS pod-delete, Chaos Monkey |
Traffic reroutes to healthy instances within the load balancer's health-check interval; no dropped in-flight requests beyond expected connection-drain window |
| Network partition (partial, not full) | Microservices, multi-AZ/multi-region systems | AWS FIS disrupt-connectivity (network ACL-based), Istio network policies, tc/iptables on-host |
System on each side of the partition degrades to a defined, safe mode rather than producing inconsistent writes or split-brain state |
| DNS failure / stale resolution | Microservices, hybrid/on-prem-to-cloud | Local DNS sinkholing, Route 53 health-check failover testing, custom resolver injection | Clients fail over to a secondary endpoint or fail fast with a clear error, rather than hanging until a long default OS-level DNS timeout expires |
| Disk pressure / disk fill | Monolith, stateful services, databases | AWS FIS EBS/ECS/EKS I/O-stress and disk-fill actions, fallocate/dd on a test host |
Application detects and alerts on low disk space before write failures occur; log rotation and temp-file cleanup engage before exhaustion |
| Retry storm / thundering herd | Microservices, any system with client-side retries | Combine dependency timeout injection with observing real client retry behavior (no separate "tool" needed — it emerges from the timeout test) | Retries are capped, jittered, and budgeted; downstream load during recovery does not exceed a bounded multiple of steady-state load |
| Region or AZ failover | Multi-region systems, regulated/high-availability platforms | AWS FIS with Application Recovery Controller zonal autoshift, Route 53 failover testing | Failover completes within the RTO the business has actually committed to (in writing, to customers or regulators), with no manual, undocumented steps required |
| Clock skew | Distributed systems using timestamps for ordering, locking, or cache expiry | chrony/ntpd manipulation in a test environment, container-level clock offset tools |
Time-based logic (token expiry, lock leases, cache TTLs) tolerates a bounded, documented skew window without producing incorrect ordering or double-processing |
| Downstream throttling / rate limiting | Serverless, systems calling managed cloud services (DynamoDB, Kinesis, third-party APIs) | AWS FIS DynamoDB/Kinesis throttle-injection actions, deliberately exceeding a sandboxed API's documented rate limit | Backpressure propagates upstream as a controlled queue or a clear client-facing error, not as silent data loss or an unbounded retry loop |
| Third-party API total outage | Any system depending on an external SaaS or payment/identity provider | Toxiproxy or a mock endpoint substituted for the real provider in a sandboxed environment | A defined degraded mode activates (queue-and-retry-later, alternate provider, clear user-facing message) rather than an unhandled exception surfacing to the end user |
This table is a starting reference, not an exhaustive one, and it is deliberately ordered from the fault classes most teams should test first (dependency timeouts and instance loss — common, well-understood, and safe to bound tightly) toward the ones that require more organizational maturity to test safely (region failover and third-party total outage, both of which are harder to bound and more consequential if the hypothesis is wrong).
What to Test First, and What to Leave Alone Early
Sequencing which fault to test, not just which stage to run it at, matters just as much. A common mistake is treating the fault-class table as a menu to work through in whatever order is technically convenient, rather than in the order that produces safe, high-value learning early.
Test first (high value, containable blast radius):
- A single dependency's timeout behavior, injected against one instance or one percentage of traffic. This validates the most common and most consequential failure mode — a slow dependency, not a dead one — with a blast radius that is easy to bound and a rollback that is nearly instantaneous (stop the injection, latency returns to normal).
- Single-instance or single-pod termination in a fleet with real redundancy. This validates load-balancer health checks and failover, which almost every system claims to have and relatively few have actually verified under real conditions.
- Disk-fill on a single non-critical host. Low blast radius, and it frequently surfaces alerting gaps (most teams alert on disk usage percentage but not on the rate of consumption, which is what actually predicts an imminent outage).
Test with real caution, later, once stages 0–2 are behind you:
- Retry-storm scenarios, because by design they are meant to amplify — if the hypothesis about jitter and backoff is wrong, the experiment can generate meaningfully more load than the injected fault alone would suggest, on a dependency that may not be provisioned to absorb it.
- Multi-node or availability-zone-level failures, because the blast radius by definition spans more of the system, and recovery may depend on automated failover logic that has itself never been exercised.
- Anything touching a stateful data store's write path (database failover, replication pause), because a wrong hypothesis here risks data consistency issues that are much harder to "roll back" than an availability blip.
Avoid until the organization has real chaos engineering maturity (stage 3+, with strong observability and a track record of successful smaller experiments):
- Full region failover in production. The blast radius is, by definition, as large as it gets, and most organizations that need this tested have already committed to a specific recovery time objective to customers, a board, or a regulator — meaning a poorly executed test is not just a technical incident, it is a credibility incident.
- Third-party dependency total outage in live production, when that dependency is something customers directly interact with (a payment processor, an identity provider). Simulate this in a sandboxed or shadow environment first; the cost of being wrong in production, with real customer money or credentials involved, is asymmetric to the learning gained.
- Compound, multi-fault experiments (simultaneous network partition plus disk pressure plus a dependency timeout) before the organization has independently validated each fault type in isolation. Compound chaos experiments are a legitimate and valuable later-stage practice — real outages are rarely single-cause — but running them before single-fault experiments have built a baseline of confidence just multiplies the number of unknowns being tested at once, making it much harder to attribute the result to any one cause.
Ownership: Who Runs This, Who Approves It, Where It Runs
Chaos engineering programs that stall after an initial burst of enthusiasm usually stall on governance, not technology. The tooling question ("which platform do we use") is almost always easier to answer than the ownership question ("who is allowed to do this, to what, and who has to say yes first"). The following responsibility map is built for this article, distinguishing four questions that most teams conflate into one.
| Question | Startup (under ~50 engineers) | Scale-up (roughly 50–500 engineers) | Enterprise (500+ engineers, regulated or multi-team ownership) |
|---|---|---|---|
| Who designs and runs experiments? | Whoever owns the service being tested — often the same engineer who wrote the resilience mechanism being validated | A dedicated (often part-time) reliability or platform engineering function, in partnership with each service's owning team | A formal chaos engineering or reliability engineering team, executing experiments on behalf of service teams under a shared standard |
| Who approves running an experiment against a given target? | Informal — usually just the engineering lead needs to know and agree on timing | The on-call lead or engineering manager for the affected service, notified in advance with a defined rollback plan | A formal change/experiment review process, often the same one used for production changes generally, sometimes requiring sign-off from the service owner and a reliability engineering lead jointly |
| Where does the first production-facing experiment run? | A single, low-traffic instance or a low-stakes internal tool, never a customer-facing revenue path | A bounded percentage of production traffic on a non-critical path, with automatic rollback wired to existing alerting | Production-adjacent (shadow/canary) first, with a long track record there before any experiment touches a regulated or customer-money-handling path directly |
| Who is accountable if an experiment causes real customer impact? | The engineer who ran it and the lead who approved it — usually the same two or three people, which keeps accountability simple but also means it rests informally rather than being written down anywhere | The named experiment owner, per a lightweight written runbook that specifies scope, hypothesis, and abort criteria in advance | The reliability engineering team owns execution accountability; the service-owning team owns the decision of whether the underlying resilience gap the experiment reveals gets fixed and on what timeline |
Two governance principles hold regardless of company size, and are worth stating directly because they are the ones most often skipped:
The person who can decide to run an experiment should not be the only person who can stop it. If the abort mechanism depends on the same individual who initiated the experiment being available, reachable, and functioning correctly for the entire duration, that is a single point of failure inside the safety mechanism itself. A second person, or an automated stop condition tied to monitoring (as AWS FIS enforces structurally through its stop-conditions field), should be able to halt the experiment independently.
Approval to run an experiment against a system is not the same as approval to accept its consequences on behalf of customers. A team can have full technical authority over a service and still owe a heads-up, or in regulated contexts formal approval, to whoever owns the customer or compliance relationship the service supports — a payments team testing a failure mode that could delay a transaction, for instance, has a different approval bar than a team testing an internal admin tool's resilience, even if both teams have equivalent technical seniority.
Chaos Engineering and Incident Response Maturity Are the Same Investment
One of the more useful reframes for a skeptical executive is that chaos engineering is not a separate initiative competing for budget against incident response investment — it is largely the same investment, viewed from a different angle.
An organization's ability to run a chaos experiment safely depends on exactly the same capabilities that determine how well it handles a real, unplanned incident: fast and accurate detection (observability), a clear decision-maker during the event, a tested rollback or mitigation path, and a blameless process for extracting the learning afterward. A team with weak incident response — slow alerting, unclear on-call ownership, no rollback automation — will find that chaos engineering is not just hard to do well, it is actively unsafe to attempt, because the same gaps that make a real incident drag on for hours will make a deliberately-caused one do the same thing.
This produces a useful diagnostic in reverse: an organization's postmortem history is a fairly reliable predictor of its readiness for fault injection. A team whose last several postmortems point to "we didn't notice until customers reported it" has an observability gap that chaos engineering will only make more painful to discover, not less. A team whose postmortems consistently say "we noticed within minutes but the fix took two hours because nobody knew who owned that service" has an ownership and runbook gap that a chaos experiment will also immediately expose, which is a legitimate reason to run one — but the team should expect that outcome and be ready to act on it, not be surprised by it.
Practically, this means the artifacts a reliability-minded team already has — dashboards, on-call rotations, runbooks, a postmortem process — are also the artifacts a chaos engineering program depends on. Building the practice is largely a matter of applying deliberate, scheduled pressure to those existing artifacts rather than building a parallel set of new ones. Teams that frame chaos engineering as "a new program with new tools" tend to underinvest in the observability and process work that determines whether it's safe. Teams that frame it as "a way to stress-test our existing incident response capability on our own schedule, instead of waiting for production to do it for us" tend to get the sequencing right by default.
Three Illustrative Scenarios
The following three scenarios are hypothetical and illustrative, constructed to demonstrate realistic failure patterns and decision points. They do not describe any real company, client, or QAtronic engagement, and any numbers used are for illustration only, not benchmarks.
Scenario 1 — SaaS platform, cache dependency (hypothetical)
Initial situation. A mid-size B2B SaaS platform serves its main dashboard from an application layer backed by a Redis cache in front of a PostgreSQL database. Cache hit rate sits around 96% in normal operation, and the engineering team considers Redis "just an optimization" rather than a hard dependency, because there is a code path that falls through to Postgres on a cache miss.
Hidden assumption. The fallback path was written and tested once, in isolation, at low traffic, roughly two years earlier. Nobody has verified what happens when the entire cache layer becomes unavailable simultaneously rather than experiencing an occasional miss — specifically, whether Postgres's connection pool and query planner can absorb the full unfiltered request volume the cache normally intercepts.
Consequence if untested. During a real Redis failover event (a maintenance operation gone wrong, or a memory-pressure eviction storm), every request that used to hit the cache now hits Postgres directly. Connection pool exhaustion follows within seconds, unrelated endpoints that also use the same database begin timing out, and what should have been "the dashboard got 4% slower" becomes a platform-wide outage.
Decision point. The engineering lead has to decide whether to trust that the fallback path works as designed, based on a two-year-old code review, or to verify it deliberately.
Better approach. A stage-2, production-adjacent experiment: mirror a percentage of real dashboard traffic to a shadow environment with the same database but Redis access deliberately blocked, and measure actual Postgres load and query latency under the full, un-cached request volume. This is exactly the kind of dependency-timeout-adjacent, blast-radius-limited experiment recommended earlier as a first target, because it validates a specific, well-understood hypothesis (does the fallback path scale) without risking real customer traffic, and it produces a concrete answer — either the database needs a larger connection pool and read replica capacity before the fallback path can be trusted, or it genuinely can absorb the load and the team now has evidence, not assumption, behind that belief.
Scenario 2 — Fintech marketplace, downstream payment provider timeout (hypothetical)
Initial situation. A marketplace platform routes payment authorization through a single third-party processor. The integration has a 30-second HTTP timeout and retries once on failure, a configuration set by an engineer during initial integration and never revisited.
Hidden assumption. The team assumes the processor's failure mode is binary — either the API responds normally or it returns a clear error — and that a single retry is a safe, conservative choice.
Consequence if untested. During a real partial outage at the processor (documented as a known failure pattern for payment APIs generally: slow responses rather than clean failures, because the processor's own backend is degraded, not down), authorization requests take close to the full 30-second timeout before failing. The marketplace's checkout service, which handles authorization synchronously inside the request-handling thread, holds each of those threads for the full 30 seconds. Under moderate checkout traffic, the thread pool exhausts within a few minutes, and checkout becomes unavailable platform-wide — not because payments are down, but because a slow payment dependency consumed all available capacity to process anything else, including transactions that don't touch that processor at all.
Decision point. Whether to keep a 30-second synchronous timeout with the assumption that "the processor is either up or down," or to redesign around the more realistic assumption that it will sometimes be slow.
Better approach. A bounded production experiment (stage 3) using a service-mesh or SDK-level fault injection to add artificial latency (not an outright error) to a small percentage of authorization calls, while watching thread-pool saturation and checkout latency for unrelated transactions. This experiment, calibrated correctly, should lead the team to shorten the timeout to something aligned with actual acceptable checkout latency (a few seconds, not thirty), move authorization off the main request-handling thread pool entirely, and add a circuit breaker that fails fast and shows the customer a clear retry option once the processor's error rate crosses a threshold — a redesign driven by an observed, specific failure mode rather than a generic "add more retries" instinct that, per the retry-storm fault class discussed earlier, would likely make the problem worse rather than better.
Scenario 3 — Healthcare scheduling platform, DNS and multi-region failover (hypothetical)
Initial situation. A healthcare scheduling platform operates across two AWS regions for regulatory data-residency and availability reasons, with Route 53 configured for failover between them. The failover configuration was set up during initial launch and has not been exercised since, because the primary region has never gone down.
Hidden assumption. The team assumes that because failover is configured, it will work — specifically, that client applications (a mix of a web app and clinic-facing desktop software with longer DNS cache lifetimes than typical browsers) will pick up the new region's endpoint promptly once Route 53 updates.
Consequence if untested. During an actual regional impairment, Route 53 correctly redirects new DNS lookups to the secondary region within its configured TTL. But a meaningful share of the desktop client software, deployed at clinics, has its own OS-level and application-level DNS caching that ignores the TTL and continues resolving to the now-unhealthy primary region for much longer than expected — a failure mode invisible in any test that only checks whether Route 53's own failover behaves correctly, because the actual failure is in client-side caching behavior nobody at the platform team controls directly.
Decision point. Whether to treat "Route 53 failover is configured" as sufficient evidence of resilience, or to validate the full path including realistic client behavior.
Better approach. A scheduled, announced game day (stage 0/1 combination) that simulates the primary region becoming unhealthy in a controlled test environment mirroring the real client population — including at least one machine running the actual clinic-facing desktop software with its real DNS cache configuration — measuring actual time-to-recovery from the client's perspective, not just from Route 53's own status. This is the kind of experiment that a purely infrastructure-focused chaos tool would never surface on its own, because the defect is not in the infrastructure being tested; it is in an assumption about client behavior that only a realistic, full-path experiment exposes. It is also a strong illustration of why "run experiments in production" (or production-realistic conditions) matters as a principle — a synthetic test against Route 53's API alone would have reported success while the real failover path was still broken for a meaningful share of real users.
A Practical Implementation Checklist
The following sequence consolidates the article's recommendations into a single operational checklist for a team starting from zero. Each step assumes the previous one is genuinely complete, not just attempted.
- Inventory critical dependencies and their known failure history. List every external and internal dependency a core user-facing flow relies on, and note any documented outage or degradation history (status pages, past incident reports) for each. This produces the prioritized list of what to test first, ranked by "how plausible is this failure" rather than by what's technically easiest to simulate.
- Instrument steady-state metrics before touching anything. For each system you plan to test, confirm you can see latency percentiles, error rates by dependency, and saturation metrics (connection pool usage, thread pool usage, queue depth) in real time. If you cannot measure the "before," do not run the experiment — you will not be able to interpret the "during."
- Run at least one game day before any code-level fault injection. Pick the single most plausible dependency failure from step 1 and walk through it as a tabletop exercise with the people who would actually respond. Fix whatever gaps in knowledge or tooling this reveals before moving to step 4.
- Select one fault class and one target for the first real experiment, using the "test first" list above (dependency timeout or single-instance loss are the strongest starting points for almost any architecture) and the smallest reasonable blast radius available to you.
- Write down the hypothesis, the steady-state baseline, the injection method, and the abort condition before running anything, even informally. If you cannot state in one sentence what you expect to happen and how you will know if you're wrong, you are not ready to run the experiment yet.
- Notify the people who need to know, and name one person other than the experiment operator who can independently trigger the abort.
- Run the experiment at a low-traffic, well-staffed time, watch it live, and stop it the moment the stop condition is met — do not extend "just a bit longer" to see what happens next, that judgment call belongs in the next experiment's design, not a live deviation from this one.
- Document the result honestly, including a null result. "The system tolerated this exactly as expected" is a valid, valuable outcome, and it should be documented with the same rigor as a surprising failure — it is evidence, and evidence has a shelf life as the system continues to change underneath it.
- Fix what the experiment revealed, and re-run it after the fix, rather than treating the finding as a backlog item that competes indefinitely with feature work. An unresolved, known resilience gap discovered by a chaos experiment and then left unaddressed is a worse organizational position than not knowing about it, because now it's a documented risk someone chose not to fix.
- Only after several successful, well-governed experiments at one stage, advance to the next one — wider blast radius, closer to real production traffic, or a new fault class — using the same rigor each time.
Illustrative Data: Blast Radius and Recovery Time as an Experiment Design Trade-off
The chart below is a hypothetical, illustrative model, not a benchmark drawn from any real study, constructed to make a specific point concrete: the relationship between how much of a system an experiment touches and how quickly a team can realistically detect and stop a problem if the hypothesis is wrong. It is intended purely as a design heuristic, not a measured industry figure.
Illustrative scenario: how experiment scope relates to typical detection-to-abort time in a team with moderate (not best-in-class, not poor) observability maturity
| Experiment scope (illustrative) | Approx. % of production capacity affected | Illustrative typical detection-to-abort time | Illustrative worst-case customer impact if hypothesis is wrong |
|---|---|---|---|
| Single instance / single pod | 1–3% | 30–90 seconds (automated health check + alert) | Negligible — absorbed by redundancy |
| Single availability zone | 15–35% (varies by AZ count) | 2–5 minutes (requires human correlation across services) | Noticeable latency/error spike, no full outage if multi-AZ redundancy works as designed |
| Single critical dependency, full outage | Varies widely by dependency criticality | 3–10 minutes (depends on whether the dependency's failure mode was anticipated in monitoring) | Feature-level or platform-level outage if fallback logic is untested |
| Full region | 50–100% | 10–30+ minutes (cross-team coordination typically required) | Platform-wide outage if failover has gaps, potentially hours to fully resolve |
Text representation of the trend:
Detection-to-abort time (illustrative, minutes)
30+ | ****
|
20 | ****
|
10 | ****
|
5 | ****
|
1 | ****
+--------------------------------------------------
single single AZ single dep. full region
instance (full outage)
What this illustrates: detection-to-abort time does not scale linearly with blast radius, it scales roughly with how many teams and systems have to correlate their observations before someone can confidently say "this is the experiment, not a real incident" and pull the trigger. This is the underlying reason the staged model earlier in this article insists on small, single-team-owned blast radii early — not because small experiments are inherently more valuable, but because the organization's ability to detect and abort quickly, which is what makes any experiment safe, degrades sharply once an experiment's scope crosses team boundaries. Building that cross-team detection and coordination capability is itself a legitimate goal of a mature chaos engineering program — it just should not be the starting point.
Illustrative Data: Where Fault Injection Effort Tends to Pay Off, by Fault Class
This second chart is also an illustrative, hypothetical model built for this article to support a specific argument — that effort invested in testing certain fault classes tends to produce disproportionate resilience improvement relative to the engineering effort required to test them, largely because some fault classes are cheap to test but reveal expensive, systemic weaknesses (dependency timeouts), while others are expensive to test and tend to reveal narrower, already-somewhat-understood weaknesses (full region failover). These are not measured percentages from any real dataset; they are a reasoning tool.
| Fault class | Illustrative relative effort to test | Illustrative relative value of what it typically reveals | Why |
|---|---|---|---|
| Dependency timeout / slow response | Low | High | Cheap to simulate (latency injection at the mesh or SDK level), and almost every system has at least one undertested synchronous dependency |
| Single instance/pod loss | Low | Medium | Cheap and safe, but many teams already have reasonably solid redundancy here, so it more often confirms resilience than reveals a gap |
| Retry storm | Medium | High | Requires careful setup to observe safely, but retry misconfiguration is extremely common and its consequences (self-inflicted overload) are severe |
| DNS / service discovery failure | Medium | Medium-High | Requires realistic client population to test well (as in the healthcare scenario above), but frequently reveals invisible client-side assumptions |
| Disk pressure | Low | Medium | Cheap to simulate, moderately common cause of real incidents, but often already partially covered by existing capacity alerting |
| Clock skew | Medium-High | Medium | Harder to simulate realistically across a distributed system, and only matters for systems with meaningful time-dependent logic |
| Region failover | High | Medium (if failover is already well-designed) to Very High (if it has never been tested) | Expensive and organizationally heavy to test properly, but the value is binary — either it reveals a critical, previously-unknown gap, or it mostly confirms what a well-designed system should already do |
The practical reading of this table, paired with the earlier "test first" guidance, is that the fault classes cheapest to test safely (dependency timeouts, single-instance loss, disk pressure) also tend to be reasonably high-value, which is a fortunate alignment rather than a coincidence — those are exactly the fault classes involved in the highest number of everyday partial outages, which is also why they were the natural place for the discipline to start historically.
Tooling Landscape, Briefly, and Deliberately Not the Point
It would be easy to fill several thousand words comparing chaos engineering tools, and doing so would miss the actual argument of this article, which is that tooling choice is rarely the constraint. A brief, honest orientation:
- Cloud-managed services — AWS Fault Injection Service is the most fully documented example, with a wide and growing action library covering EC2, ECS, EKS, Lambda, RDS, DynamoDB, Kinesis, and network-layer faults, plus built-in stop conditions tied to CloudWatch alarms. Azure and Google Cloud have their own equivalents and partnerships (Microsoft's Azure Chaos Studio is the direct analog). These are the lowest-friction starting point for teams already committed to one cloud provider, precisely because the safety mechanisms (stop conditions, IAM-scoped permissions, resource targeting) are built into the product rather than something the team has to construct itself.
- Kubernetes-native, open source — LitmusChaos (CNCF incubating) and Chaos Mesh are the two most established options for teams running on Kubernetes who want fault injection expressed as Kubernetes custom resources, integrated with existing GitOps and RBAC.
- Service-mesh built-in fault injection — Istio and similar meshes support declarative fault injection (fixed delays, percentage-based abort injection) directly in routing configuration, with no separate chaos tool required, which is often the fastest path to a first experiment for a team already running a mesh.
- Commercial platforms — Gremlin is the most established vendor in this space (founded by several of the original Netflix chaos engineering team members), offering a managed attack library across network, resource (CPU/memory/disk), and state-based fault categories, plus scenario orchestration and safety controls aimed at making the governance side of this article's argument easier to operationalize, not just the injection mechanics. Vendor-published claims about ease of adoption or safety should be read as vendor-reported, evaluated against the prerequisites and staging model in this article rather than taken as a substitute for them.
- General-purpose, host-level tools — Chaos Toolkit, Toxiproxy (for simulating network conditions between services at the proxy layer), and standard Linux utilities (
tc,iptables) remain the right choice for monoliths and simpler architectures where a full platform is unnecessary overhead.
The selection criterion that actually matters is whether the tool makes it easy to express a bounded blast radius and an automatic stop condition as a first-class part of defining the experiment, not as an afterthought the team has to build separately. A powerful tool without that property makes it easier to skip the discipline this entire article argues is the actual point.
When Chaos Engineering Is the Wrong Investment Right Now
It is worth stating plainly where this practice is not the right next investment, because the enthusiasm chaos engineering tends to generate in engineering organizations can crowd out more urgent, more basic reliability work.
- If basic monitoring and alerting gaps mean the team regularly learns about outages from customers rather than internal alerts, that gap should be closed first. Chaos engineering without the ability to detect a self-inflicted degraded state quickly just produces incidents with an extra step.
- If the system has no redundancy at all in a given layer (a true single point of failure with no failover path, no read replica, no backup instance), testing that layer's failure mode mostly just confirms what everyone already knows: it will go down, and there is currently no fallback. That is valuable information exactly once, and after that first confirmation, further chaos experiments against that same untreated single point of failure add little beyond re-confirming a known, unaddressed gap — the engineering investment belongs in building the redundancy, not in repeatedly proving its absence.
- If the organization has no capacity to act on findings, because the team is fully consumed by feature delivery with no room to fix discovered gaps, running experiments mainly produces a growing list of known, unaddressed risks — which can be worse for morale and for actual risk posture than not looking, because now those risks are documented and still unaddressed rather than simply unknown.
- If regulatory or contractual constraints on the specific system are unclear, particularly in healthcare, financial services, or any system handling regulated data, legal and compliance should weigh in before production-adjacent or production experiments begin, not after — the argument that "we were testing resilience" is a reasonable one to make to a regulator only if the testing itself was conducted within whatever change-control and data-handling obligations already apply.
None of these are permanent disqualifiers. They are sequencing signals: fix the more foundational gap first, then chaos engineering becomes a much higher-leverage investment rather than a premature one layered on top of unaddressed basics.
Frequently Asked Questions
Is chaos engineering the same as penetration testing? No. Penetration testing looks for security vulnerabilities that an adversary could exploit. Chaos engineering looks for resilience gaps in how a system handles infrastructure and dependency failures, whether or not any adversary is involved. Some organizations run both under a broader "resilience" umbrella, but the skills, tooling, and risk models are different enough that conflating them usually weakens both practices.
Do we need Netflix-scale infrastructure before this is worth doing? No. The specific tools Netflix built (Chaos Monkey, and the broader Simian Army it grew into) were built for their scale and their specific AWS-based architecture, but the underlying discipline — hypothesis, steady state, bounded blast radius, controlled injection — applies at any scale. A ten-person engineering team testing what happens when its single Postgres instance's connection pool saturates is doing legitimate chaos engineering, just at a scale where the entire program might be a handful of experiments run manually rather than a continuous automated platform.
Should chaos experiments run in production from day one? No, and this is the most consequential misreading of the "run experiments in production" principle. That principle describes where a mature chaos engineering program eventually operates, once observability, rollback mechanisms, and organizational trust are established — not where a team should start. The staged model in this article exists specifically to sequence the path there safely.
What's the difference between a chaos experiment and just causing an incident? A chaos experiment has a stated hypothesis, a measured steady-state baseline, a bounded and pre-defined scope, an automatic or immediately available stop condition, and advance notice to the people who would otherwise treat the resulting alerts as a real incident. Remove any of those elements and what remains is, functionally, just an unplanned outage that happens to have been caused on purpose.
How often should experiments run once a program is established? There is no universal cadence, and any specific number offered without context should be treated skeptically. The right frequency is tied to how often the target system changes — a service that deploys multiple times a day and depends on external providers with their own release cadence benefits from continuous, automated experiments (the fourth stage in this article's model), while a stable, infrequently changed internal system may only need periodic re-validation, particularly after a significant architecture change or a new dependency being introduced.
Can chaos engineering replace disaster recovery testing or a formal DR plan? No. It complements DR testing by finding failure modes nobody anticipated well enough to write a runbook for, but a formal DR plan with a defined recovery time objective, recovery point objective, and rehearsed failover procedure is still necessary, particularly for regulated systems. Chaos engineering is often what reveals that an existing DR plan has an untested gap, not a replacement for having the plan in the first place.
The Actual Decision in Front of Most Engineering Leaders
The honest version of this argument is not "every company should adopt chaos engineering immediately." It is narrower and more specific: every company running a system with dependencies it does not fully control — which is nearly every company past its first year of operation — currently has resilience mechanisms (fallback paths, retries, failover configurations, circuit breakers) that were written once and have never been genuinely exercised. That is not a hypothetical risk. It is a known, currently-existing gap between what the team believes the system does under stress and what it has actually verified.
The choice is not between testing this deliberately or never encountering it. The failure will occur regardless — dependencies degrade, disks fill, DNS entries go stale, regions have bad days. The only real choice is whether the first time that specific failure happens, it happens on a Tuesday afternoon with the team watching, a rollback ready, and a small, contained blast radius — or whether it happens on its own schedule, unannounced, at whatever scale the dependency graph happens to produce that day.
Staged correctly — game days before code, isolated environments before production-adjacent ones, single-instance blast radii before regional ones, and governance decided before the first experiment runs rather than after the first near-miss — deliberate failure injection is one of the few engineering practices that gets safer the more an organization does it, because each well-run experiment strengthens exactly the observability and response muscles that make the next one lower-risk. Skipped or rushed, it produces the opposite: a team that tried chaos engineering once, generated an unplanned incident, and concluded — reasonably, given how it was run — that the whole discipline was too dangerous for them.
The question worth taking back to an engineering team is not "should we do chaos engineering." It is: which of our fallback paths, failover mechanisms, or retry configurations have we actually watched work under real conditions, versus simply trusted because someone wrote them correctly once and code review approved them? For most teams, honestly answered, that list is shorter than the list of mechanisms they are currently depending on.
Assessing that gap — reviewing which resilience mechanisms in a system have genuinely been exercised versus merely assumed, and sequencing a fault-injection program that closes the gap without introducing new risk in the process — is a specific, bounded piece of engineering work. QAtronic works with engineering teams on exactly that kind of structured reliability assessment: mapping real dependency risk, evaluating what a system's existing observability can and cannot detect, and designing the first bounded experiments so the initial results build organizational trust in the practice rather than undermining it.