chaos-drills
Chaos Drills (Formalized Fault Injection)
The Problem: "It works on my machine"
Microservices fail in creative ways. A node crashes mid-saga, a database connection pool runs dry, or a message broker gets partitioned. If you only test the happy path, you are relying on hope. Resilience is not something you "turn on"; it is something you must prove by deliberately breaking things.
The Pattern
Formalize failure scenarios as Chaos Drills. These are reproducible steps that inject faults into the running system to prove that the resilience mechanisms (circuit breakers, outbox relays, DLQs, sagas) work exactly as designed.
Our Drills
You can execute these drills locally against the Docker Compose stack.
1. Kill -9 Mid-Saga (The Outbox/Inbox Test)
Goal: Prove that crashing a service after a database commit but before publishing the event does not lose data or duplicate side effects.
Drill:
- Put a breakpoint or a
Task.Delay(10000)right after the MartenSaveChangesAsync()inaccount-servicebut before theOutboxRelaypublishes. - Send a
POST /transfersrequest. - Hard kill the
account-servicecontainer (docker kill micro-service-async-account-service-1). - Verification:
- The transfer is stuck in
PENDING. - Restart the service (
docker start micro-service-async-account-service-1). - The
OutboxRelayshould wake up, find the unpublished event, and publish it. - The saga should resume and finish
COMPLETED. - The consumer inbox prevents any duplicate processing if the event had been partially published.
- The transfer is stuck in
2. Broker Partition (The DLQ/Retry Test)
Goal: Prove that temporary broker unavailability doesn't drop messages, and persistent failures correctly route to the dead-letter queue (parking lot).
Drill:
- Disconnect the
account-servicefrom therabbitmqcontainer using Docker network commands:docker network disconnect micro-service-async_default micro-service-async-rabbitmq-1 - Send a
POST /transfers. - The
transfer-serviceoutbox will fail to publish and will keep retrying. - Reconnect the broker:
docker network connect micro-service-async_default micro-service-async-rabbitmq-1 - Verification:
- The outbox publisher reconnects and successfully publishes.
- The saga completes.
- Poison Message Drill: Modify a consumer to throw an unhandled exception. Send a message. Verify it retries 3 times and is then moved to the
banking.dead-lettersqueue, triggering theParkingLotNonZeroPrometheus alert.
3. Slow DB Injection (The Circuit Breaker / Bulkhead Test)
Goal: Prove that a slow downstream service trips the circuit breaker and triggers bulkhead load shedding, protecting the gateway from thread exhaustion.
Drill:
- Inject a 15-second
Task.Delayin theaccount-service'sGET /accountsendpoint. - Fire 200 concurrent requests to the gateway's
/api/accountsendpoint. - Verification:
- The gateway's Polly concurrency limiter (bulkhead) immediately rejects requests over the
PermitLimitwith a503 Service Unavailable("Downstream is overloaded"). - The requests that get through eventually hit the timeout and trip the Polly circuit breaker.
- Subsequent requests fail fast with a
503("circuit open") without waiting. - The
transfer-serviceendpoints (/api/transfers) remain completely unaffected and responsive, proving cluster isolation.
- The gateway's Polly concurrency limiter (bulkhead) immediately rejects requests over the