Yeda AI Tips · #163

Español

Breaker Outside, Retry Inside

Retry and circuit breaker in the wrong order silently breaks both. Both patterns guard the same call to a flaky dependency, but they do opposite jobs — and which one you wrap on the outside decides whether an outage costs you milliseconds or takes down your latency budget. Most libraries let you nest them either way, and the wrong nesting looks fine in every green test.

Two patterns, one call

A retry assumes failures are transient: the timeout blipped, the pod was mid-restart, the connection reset. It tries again — ideally a few times, with backoff and jitter — expecting the next attempt to succeed.

A circuit breaker assumes the opposite. After a threshold of recent failures it trips open and, for a cooldown window, rejects calls instantly instead of letting them hit a dependency that is already on the floor. It's a state machine — Closed, Open, Half-Open — that stops you from hammering a service while it recovers, and stops one slow dependency from tying up every thread in your app.

They pull in different directions on purpose. Retry says "try harder." The breaker says "stop trying." That's exactly why order matters.

The rule: breaker(retry(call))

Wrap the breaker on the outside and the retry on the inside:

breaker(
  retry(
    call ) )

Now the breaker sees each call as a single unit of work. Retry burns its attempts inside; the breaker records one outcome — success or final failure. Once the breaker is Open, the entire retry sequence is skipped and the call fails in microseconds.

Flip it — retry(breaker(call)) — and every retry attempt pokes the breaker independently. A single logical request now logs three or five failures, so the breaker trips far too early on inflated counts. Worse, when it is Open, your retry loop keeps calling into it, spinning through its backoff schedule against a breaker that was designed to make you stop.

What the wrong order costs

breaker(retry(call)) — correctretry(breaker(call)) — inverted
Failures the breaker counts per request1up to N (one per attempt)
When breaker is Openwhole retry skipped, fails fastretry loop still spins over backoff
Trip thresholdtuned to real request outcomestrips early on inflated counts
Latency during an outagemicrosecondsfull retry budget wasted

Resilience4j is the classic trap here: its default aspect order is Retry(CircuitBreaker(...)) — retry on the outside — which is the inverted arrangement, and it's a documented source of inflated failure counts. If you use it, set circuitBreakerAspectOrder above retryAspectOrder so the breaker wraps the retry.

Power moves

Resources

Building resilient AI and backend systems? Yeda AI designs, audits, and ships production services that fail gracefully. This is one tip in the Yeda AI Tips series — short, practical patterns for engineers shipping with AI.