Building Resilient Systems: Lessons Learned
After years of building and operating production systems, I’ve learned that failures are inevitable. What matters is how your system responds to them. Here are the key principles I follow when designing resilient systems.
Expect Failures
The first step to building resilient systems is accepting that things will fail. Networks will partition, services will crash, and databases will become unavailable. Design for these scenarios from the start.
Key Patterns
Circuit Breaker
The circuit breaker pattern prevents cascading failures by detecting when a service is unhealthy and temporarily stopping requests to it:
class CircuitBreaker:
def __init__(self, failure_threshold=5):
self.failures = 0
self.threshold = failure_threshold
self.state = "closed"
def call(self, func):
if self.state == "open":
raise CircuitOpenError()
try:
result = func()
self.failures = 0
return result
except Exception:
self.failures += 1
if self.failures >= self.threshold:
self.state = "open"
raise
Retry with Exponential Backoff
When temporary failures occur, retrying with increasing delays often succeeds:
def retry_with_backoff(func, max_retries=3):
for attempt in range(max_retries):
try:
return func()
except TransientError:
wait = 2 ** attempt
time.sleep(wait)
raise MaxRetriesExceeded()
Timeouts
Always set timeouts for external calls. A missing timeout can cause your entire system to hang:
response = requests.get(url, timeout=5) # 5 second timeout
Observability
You can’t fix what you can’t see. Invest in:
- Metrics: Track error rates, latencies, and throughput
- Logging: Structured logs with correlation IDs
- Tracing: Distributed tracing across services
Testing Resilience
Use chaos engineering to verify your systems handle failures:
- Start with game days in staging
- Gradually introduce failure injection in production
- Build runbooks for common failure scenarios
Conclusion
Building resilient systems requires upfront investment, but it pays dividends when production incidents occur. Start with these patterns and iterate based on your system’s specific needs.