All resources
Operations10 min read

Chaos engineering for small services without reckless outages

Use controlled failure experiments to test recovery assumptions while protecting users, data, and the team running the exercise.

A practical PingFlow guide for developers working at the boundary between systems.

At a glance

Key takeaways

  • Chaos starts with a steady-state promise
  • Choose blast radius and abort criteria
  • Test dependencies before killing core infrastructure
In this guide

Chaos starts with a steady-state promise

A chaos experiment asks whether a system maintains an important behavior when a controlled failure occurs. Define the steady state first: webhook acknowledgements remain within a latency budget, users can read public resources, or jobs complete before their useful age. Without a measurable baseline, an experiment becomes a dramatic event with no conclusion. Choose a hypothesis that matters to users and operators.

Small teams do not need a large platform to learn. A scripted provider timeout, a revoked test credential, a drained worker, or a forced database connection failure can expose a recovery gap. The experiment should be narrow, reversible, and owned. Do not inject failure into production merely because a tool makes it easy.

Choose blast radius and abort criteria

Start with a test environment or a canary slice that contains synthetic traffic and realistic dependencies. Define the maximum affected instances, tenants, regions, queue messages, or duration. An abort condition might be an error budget burn, user impact, queue age, payment failure, or missing telemetry. The person running the experiment must be able to stop it without waiting for the system to recover on its own.

Use labels, identities, and routing that make the experiment distinguishable. A test tenant or header can route traffic to a controlled dependency, but never trust a client-controlled label for authorization. Keep a rollback command and a contact list in the experiment plan. If an experiment requires a manual database change to stop, it is not yet small enough.

Test dependencies before killing core infrastructure

Dependency failures are often safer and more informative than killing the main service. Inject latency, 429 responses, malformed payloads, DNS failure, and connection resets into a test provider or stub. Observe whether timeouts, retries, circuit breakers, queue backpressure, and user messaging behave as designed. A dependency that returns slowly can be more dangerous than one that returns an immediate error because it consumes connections and worker capacity.

After dependency experiments, test instance termination, scale-to-zero, queue worker crashes, and a database failover in a controlled environment. Preserve the failure semantics the platform actually produces. A graceful stop is different from a process crash, and a network partition is different from a 503 response. The recovery path should be tested against the failure users will see.

Observe recovery, not only failure

The most important measurements are time to detect, time to choose an action, time to stop the failure, and time to recover useful service. Track queue age, retries, in-flight requests, connection pools, cache state, error rate, and downstream calls. A system that recovers technically but loses idempotency or duplicates messages has not maintained the steady state.

Telemetry is part of the experiment. If logs disappear when a dependency fails or dashboards have no data during a network problem, record that as a finding. An operator should be able to identify the affected boundary, apply the runbook, and verify recovery without relying on the person who designed the experiment.

Keep safety and ethics visible

Do not use real customer data or destructive actions in a chaos experiment. Isolate credentials, storage, queues, and payment modes. Notify on-call and affected teams, schedule a low-risk window, and make the experiment's traffic obvious. If a third-party provider is involved, use its sandbox or obtain permission. A failure experiment that harms another service is an incident, not a learning exercise.

Protect people as well as systems. Avoid running experiments during a known launch or a team handoff, and make sure someone has authority to stop. Keep the plan short enough to read under pressure. Safety is not bureaucracy; it is what lets a team keep learning without losing trust.

Convert findings into engineering work

Every experiment should end with a result: hypothesis supported, unsupported, or inconclusive. Link findings to a code, configuration, runbook, alert, or capacity change. If a retry storm appears, add backpressure and a test. If operators cannot tell which revision failed, improve telemetry. If the system cannot recover automatically, define the manual action and decide whether automation is worth the complexity.

Repeat experiments after fixes and when the architecture changes. A recovery path can regress when a new provider, queue, or deployment platform is introduced. Keep an experiment catalog with owner, date, scope, result, and next review.

Start small and build confidence

Begin with a reversible timeout in a non-production dependency, a controlled worker restart, or a failover test with synthetic data. Run the same experiment until the result is predictable, then expand the blast radius carefully. Do not graduate an experiment because the dashboard looked calm once; verify that the recovery path worked and the assumptions were recorded.

Chaos engineering is disciplined learning about failure. Define steady state, bound the blast radius, inject a realistic fault, observe recovery, protect users and providers, and turn findings into tests and runbooks. The best experiment leaves the service more understandable, not simply more dramatic.

An experiment record operators can trust

Write the steady-state query, hypothesis, scope, start and stop times, abort threshold, owner, and expected recovery signal in one record. During the experiment, capture the first alert, the operator action, the time the fault stopped, and the time the user-visible behavior recovered. If telemetry is missing, record that as a result instead of filling the gap with assumptions.

Close the record with a decision and an owner. A supported hypothesis should become a recurring check or runbook; an unsupported one should remove a mistaken assumption; an inconclusive one should get a safer follow-up. This discipline keeps chaos work focused on learning and prevents a dramatic failure from being mistaken for engineering progress.

Share the result with the teams that own the dependency and the user journey, not only with the engineers who ran the test. A recovery improvement may belong in a queue policy, an alert, a customer message, or a provider contract. Cross-team visibility turns one experiment into a stronger system instead of a private demonstration.

Keep experiments reversible

Start with a narrow target, synthetic or low-risk traffic, and an automatic abort. Preflight the dashboard, alert route, access permissions, and recovery command before injecting a fault. If a dependency cannot be restored reliably, do not experiment against it in production. Small services gain the most from experiments that produce one concrete improvement to a timeout, fallback, alert, or runbook and leave the system no less recoverable than before.

Implementation example

Write steady state, hypothesis, scope, fault, start and stop times, abort threshold, owner, and recovery signal before injecting anything. Begin with a narrow target and synthetic or low-risk traffic. Preflight dashboards, alerts, permissions, and the recovery command so the experiment can teach something without becoming an uncontrolled outage.

yaml
experiment: dependency-timeout
scope: canary
abort_if: error_rate > 2% for 2m
owner: platform

Verify and troubleshoot

Capture time to detect, choose, stop, and recover; first alert; operator action; backlog age; duplicate or lost work; and the final user outcome. If telemetry is missing, record that as a result. Test that the fault injector stops automatically and that the service returns to baseline after recovery, not merely that the process remains alive.

Operations and recovery

Limit blast radius, use an explicit abort, and communicate the experiment to dependency owners. Convert a useful result into a timeout, fallback, alert, or runbook change with an owner. If recovery is uncertain, do not run the experiment in production; a dramatic failure without a reversible recovery path is not engineering evidence.

References and further reading

Use the Principles of Chaos Engineering, the platform's fault-injection controls, and the service's incident response runbook. Keep experiments small enough that a new operator can understand their risk and stop them.

Keep exploring