Software Architecture / Interactions: distribution, failure, and operation / Lesson 10
Bound failure with deadlines, retries, and isolation
Combine caller budgets, transient-failure policy, circuit states, and resource pools without amplifying an outage.
On this page
Lesson goals
- Distinguish a timeout, end-to-end deadline, retry budget, and circuit breaker.
- Design a degraded response that preserves the reservation promise.
Start with the callerโs remaining time
A timeout limits how long one attempt waits; a deadline bounds the total useful time for an operation. If an API has already spent most of its caller budget, starting another long dependency call can waste resources after the caller has abandoned the request. Propagate a remaining deadline or equivalent cancellation signal through supported interfaces. Reserve time for response assembly and cleanup. A timeout does not prove the remote operation stopped, so state-changing calls also require outcome and retry semantics that account for possible completion.
Retry only a justified transient failure
A transient connection fault may recover, while invalid input or a business rejection usually will not improve through immediate repetition. Define retryable conditions, maximum attempts, backoff with jitter where appropriate, and a total deadline. Retry only when the operation is safe under repetition or uses a verified stable identity protocol. Retries at several layers multiply traffic: a browser, gateway, and service may each repeat the same failing request. Put ownership at a deliberate layer and inspect the complete call path before choosing a policy.
attempts_per_layer = [3, 3, 2]
maximum_leaf_attempts = 1
for attempts in attempts_per_layer:
maximum_leaf_attempts *= attempts
print(f"maximum_leaf_attempts={maximum_leaf_attempts}")
print("illustrative worst case, not a traffic measurement")
maximum_leaf_attempts=18
illustrative worst case, not a traffic measurement
If every parent attempt exhausts all child attempts, the leaf can receive eighteen calls. Actual traffic depends on failures and policy; the calculation starts no requests or services.
A circuit breaker avoids repeatedly calling a failing dependency
A circuit breaker observes a chosen class of failures and temporarily rejects calls when an opening condition is met. A limited probe or half-open state later tests recovery. It differs from retry: retry attempts the operation again, while an open circuit deliberately prevents an attempt. Thresholds, observation windows, and probe concurrency need workload-specific evidence. A breaker can also misclassify legitimate business rejections as dependency failures if its error classification is careless. Expose circuit state and rejected-call counts so operators can distinguish protection from unexplained application failure.
Separate resources before one slow path consumes all of them
Bulkheads reserve separate concurrency or connection resources for workloads with different importance or dependencies. A slow mail provider should not consume every slot needed to inspect confirmed reservations. The sample policy is illustrative and deliberately avoids universal numbers. Every pool still needs a bound, queue policy, and observable saturation signal. Too many isolated pools can waste capacity or create new bottlenecks. Compare the intended protected user task with the cost of reserved resources, and verify that shared database or thread limits do not defeat the apparent isolation.
reservation_queries:
resources: dedicated bounded request pool
on_saturation: explicit temporary failure
mail_delivery:
resources: separate bounded worker pool
on_saturation: retain durable pending work
policy_values: chosen from isolated measurements
The two paths have different completion semantics. This is a design note rather than configuration for a particular runtime, and no capacity values are recommended.
Make a fallback honest about its limits
A cached catalog can be a reasonable degraded read if users see its freshness and reservation commands still validate authoritative state. Returning a fabricated booking confirmation is not a safe fallback. Define which functions remain available, what information becomes stale, and when the degraded mode ends. Test a dependency failure in an isolated environment and inspect resource release, duplicate effects, and caller outcomes. This lesson executes arithmetic only; it does not tune a real circuit breaker, inject production faults, or establish a service recovery guarantee.
Common errors and fixes
Retries make an outage much larger.
- Why it happens
- Several layers retry without a shared deadline or owner.
- How to fix it
- Choose one policy owner and bound total attempts and time.
- Prevent it next time
- Review worst-case amplification through the full call tree.
A timeout is interpreted as proof of no state change.
- Why it happens
- Local waiting behavior was confused with remote execution.
- How to fix it
- Query the outcome or retry with a supported stable identity.
- Prevent it next time
- Model post-timeout uncertainty for every write.
A fallback returns a fake confirmation.
- Why it happens
- Availability was prioritized without preserving correctness.
- How to fix it
- Return a truthful pending or failure state with recovery guidance.
- Prevent it next time
- Review fallback semantics against business invariants.
Check your understanding
1. Should capacity exhausted count toward opening a mail-provider circuit?
Show worked solution
No. It is a reservation business outcome unrelated to provider health. Classify failures at the relevant boundary and open the circuit only for the dependency conditions defined by policy. Mixing unrelated rejections can block healthy operations and hide the actual cause of failure.
2. A three-second caller budget contains two sequential two-second calls. What is missing?
Show worked solution
The local timeouts exceed the end-to-end budget before overhead or retries. Allocate remaining time deliberately, stop starting work after the deadline, and reserve response time. If the workflow cannot meet the user promise, redesign it or change the promise explicitly. Arbitrarily increasing the caller timeout does not establish acceptable behavior.
Key takeaways
- Bound time, attempts, and resource use together.
- Protection mechanisms must preserve truthful business outcomes.