The Timeout Problem in Microservices
In a microservice architecture, a single user request might fan out to 5–10 downstream services. Each service sets its own timeout — say 5 seconds. If Service A calls Service B which calls Service C, the effective timeout chain can be 15 seconds, causing the user to wait long past when they've already given up or reconnected.
Worse: Service C may still be processing a request for a user who abandoned it 10 seconds ago, burning resources needlessly.
What Is a Timeout Budget?
A timeout budget (also called a deadline) is a total time allowance for an entire request chain. It is established at the edge and propagated downstream. Each service consumes from the budget; when the budget is exhausted, all in-progress work is cancelled.
User Request → API Gateway (budget: 10s)
→ Service A (budget: 10s, takes 2s, passes remaining 8s downstream)
→ Service B (budget: 8s, takes 3s, passes remaining 5s downstream)
→ Service C (budget: 5s)
If Service C doesn't respond in 5 seconds, the request is cancelled — not after a compounding 15-second chain.
Propagating Deadlines with gRPC
gRPC has first-class deadline support built in:
import grpc
from datetime import datetime, timedelta
# Establish deadline at the edge
deadline = datetime.utcnow() + timedelta(seconds=10)
# Each call propagates the same deadline
channel = grpc.insecure_channel('service-a:50051')
stub = ServiceAStub(channel)
response = stub.GetData(
request,
timeout=(deadline - datetime.utcnow()).total_seconds(),
)
gRPC automatically cancels in-flight RPCs when the deadline passes, propagating cancellation signals to all downstream services.
HTTP Timeout Headers
For HTTP services, pass the remaining budget as a header:
import httpx
import time
def call_downstream(
url: str,
remaining_budget_seconds: float,
) -> httpx.Response:
# Reserve 10% for overhead (serialization, network)
downstream_timeout = remaining_budget_seconds * 0.9
return httpx.get(
url,
timeout=downstream_timeout,
headers={'X-Request-Timeout': str(remaining_budget_seconds)},
)
The X-Request-Timeout header (or a custom header like X-Deadline-Unix-Timestamp) lets the downstream service know how much time remains and avoid starting work it cannot complete.
Cascading Failures Without Budgets
Without timeout budgets:
- Service C becomes slow (100ms → 8s per request)
- Service B's threads block waiting for Service C
- Service B's thread pool exhausts
- Service A starts timing out waiting for Service B
- The API Gateway starts timing out
- Users see failures everywhere — even for endpoints that don't use Service C
With timeout budgets, Service C's slowness is contained: requests time out quickly, resources are freed, and degradation is limited to endpoints that actually depend on Service C.
Implementation Checklist
- [ ] Set a global budget at the API gateway or edge service
- [ ] Propagate the remaining budget to every downstream call
- [ ] Reserve headroom (10–20%) at each hop for serialization overhead
- [ ] Use gRPC deadlines or
X-Request-Deadlineheaders for HTTP - [ ] Check the remaining budget before starting expensive work
- [ ] Return
408 Request Timeoutor504 Gateway Timeoutwhen budget exhausted - [ ] Emit metrics on budget-exceeded events per service
Summary
Timeout budgets prevent cascading failures by giving the entire request chain a single time budget that shrinks at each hop. Use gRPC's built-in deadline propagation for gRPC services, and pass remaining budget via custom headers for HTTP services. Always check the budget before starting expensive work, and emit metrics when budgets are exhausted to identify slow dependencies.