Resilience Patterns — Graceful Degradation, Canaries, Circuit Breakers
Tại sao resilience patterns quan trọng
Một dependency fails. A service slows down. Network partition happens.
Without resilience:
- Request times out waiting for dependency
- User experiences timeout error
- Or worse, cascading failure kills your entire service
With resilience patterns:
- Service detects failure
- Degrades gracefully (serves something rather than nothing)
- Recovers when dependency heals
- User experience degraded but acceptable
Resilience patterns are defensive mechanisms — they can't prevent all failures, but they contain them and minimize impact.
Internal Model: Graceful degradation
What is graceful degradation?
Graceful degradation means: when some part of system is unavailable, serve degraded functionality rather than fail completely.
Examples:
E-commerce website:
- Full feature: Show product + real-time inventory + recommendations + shipping cost
- Degraded (if inventory service down): Show product + cached inventory + no recommendations + estimated shipping
- User still gets useful experience
Search service:
- Full feature: Real-time search with instant completion suggestions
- Degraded (if index down): Search previous results from cache
- User gets search results, just not real-time
Social media timeline:
- Full feature: Real-time feed from network
- Degraded (if real-time service down): Show cached feed from last sync
- User sees slightly stale content but not blankKey distinction from circuit breaker
People often confuse graceful degradation with circuit breaking. They're different:
Graceful degradation:
- Try to serve reduced functionality
- Goal: maximize availability (serve some users, something)
- Example: use cached data if fresh data unavailable
Circuit breaker:
- Stop trying to call failing service
- Goal: fail fast (don't waste resources on failing service)
- Example: If service fails 3 times, stop calling it for next 30 seconds
When to use each:
Scenario: Database is slow (99% latency increase)
├─ Graceful degradation: Serve cached data from Redis instead
│ (User gets data, just cached)
└─ Circuit breaker: Stop querying database, return cached immediately
Scenario: Payment service is down
├─ Graceful degradation: Not applicable (can't degrade payment processing)
└─ Circuit breaker: Stop trying to charge, return error immediatelyDesigning degradation tiers
Not all features are equally important. Design degradation explicitly:
Service: E-commerce
Tier 1 (Critical): Must work
- Browse products
- Add to cart
- Checkout
Tier 2 (Important): Should work
- Personalized recommendations
- Real-time inventory status
- Product reviews
Tier 3 (Nice-to-have): Can degrade
- Search suggestions
- Similar products
- Analytics tracking
During outage:
- If database down → Tier 1 only (basic product catalog from cache)
- If recommendation service down → Skip Tier 3, show Tier 1+2
- If analytics down → Skip Tier 3, nothing else breaksImplementing graceful degradation
Pattern 1: Fallback to cache
try:
product = db.query("product", id)
catch DatabaseError:
product = cache.get("product", id) // older data, but somethingPattern 2: Feature flag to disable expensive operations
if feature_flags.enable_recommendations:
recommendations = get_recommendations(user_id)
else:
recommendations = []Pattern 3: Timeout-based fallback
try:
result = call_service_with_timeout(timeout=100ms)
except TimeoutError:
result = get_cached_result() // use stale dataCost of graceful degradation
Graceful degradation requires:
- Caching layer (Redis, Memcached)
- Fallback logic in code
- Feature flags to disable expensive features
- Testing to ensure degraded path works
Cost is real. Only use for genuinely important features.
Internal Model: Canary deployments
What is canary deployment?
Canary deployment means: release change to small subset of users first, monitor, then roll to everyone.
Canary deployment timeline:
Time 0: Deploy to canary (5% of users)
Monitor for 15 minutes
Metrics look good?
Time 15min: Deploy to 25% of users
Monitor for 15 minutes
Metrics look good?
Time 30min: Deploy to 50% of users
Monitor for 15 minutes
Metrics look good?
Time 45min: Deploy to 100% of users
Rollout completeCanary vs big bang deployment
Big bang:
Deploy feature to 100% of users
1% chance it breaks → affects 100% of users
Recovery takes 30 minutes
Impact: 300,000 users affected for 30 minCanary (5% initial):
Deploy feature to 5% of users
1% chance it breaks → affects 5% of users
Detect issue within 1 minute
Rollback
Impact: 15,000 users affected for 1 minCanary reduces blast radius by 10x.
Metrics to watch during canary
Don't just deploy and ignore. Monitor actively:
Before deployment:
Error rate: 0.1%
p99 latency: 150ms
CPU: 40%
After canary deployment (5% of traffic):
Error rate: 0.12% (slight increase, within variance)
p99 latency: 155ms (acceptable)
CPU: 40% (unchanged)
✓ Metrics look normal, proceed to next stage
After 25% deployment:
Error rate: 0.8% (significant increase!)
p99 latency: 300ms (doubled!)
CPU: 60% (increased)
✗ Metrics bad, ROLLBACK immediatelyImplementing canary with Istio on GKE
GCP's service mesh (Istio) makes canary easy:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-service
spec:
hosts:
- my-service
http:
- match:
- headers:
user-agent:
exact: "canary-test"
route:
- destination:
host: my-service
subset: v2
weight: 100
- route:
- destination:
host: my-service
subset: v1
weight: 95
- destination:
host: my-service
subset: v2
weight: 5 # Send 5% traffic to v2This sends 5% traffic to v2, 95% to v1. Google Cloud Monitoring watches error rate, latency. If metrics bad, you can revert this config instantly (no pod restart needed).
Common canary mistakes
Mistake 1: Canary period too short
- Deploy to canary, wait 1 minute, then roll to everyone
- Issue takes 5 minutes to surface
- Result: Issue hits 100% of users
Fix: Wait long enough for issue to surface (usually 15–30 minutes)
Mistake 2: Canary too small
- Deploy to 0.1% of users
- Statistical noise masks real issue
- Issue hits 100% users later
Fix: Canary should be 5–10% (enough to detect real issues)
Mistake 3: Canary in isolated geography
- Canary only to US users
- US load pattern different from India
- Issue only surfaces in India
Fix: Canary should span geographies
Internal Model: Circuit breaker pattern
What is circuit breaker?
Circuit breaker pattern: if service fails repeatedly, stop calling it and return error immediately.
Three states:
CLOSED (normal):
- Service available
- Calls go through
- On failure, increment failure counter
OPEN (failing):
- Failure counter exceeded threshold
- Stop calling service
- Return error immediately (don't wait for timeout)
HALF_OPEN (recovering):
- After timeout period, try again
- If succeeds, go back to CLOSED
- If fails, go back to OPENAnalogy: electrical circuit breaker
In electrical systems:
Normal flow: Current flows → CLOSED
Short circuit: Too much current → OPEN (breaks circuit)
Recovery: After fix, try again → HALF_OPEN
If ok: Circuit holds → CLOSED
If still broken: Open again → OPENSame pattern applies to services.
When does circuit breaker help?
Scenario: Database is completely down
Without circuit breaker:
Request 1: Try DB → timeout after 30s → fail
Request 2: Try DB → timeout after 30s → fail
Request 3: Try DB → timeout after 30s → fail
(Each request wastes 30 seconds)
With circuit breaker (threshold=3 failures):
Request 1: Try DB → timeout after 30s → fail (counter=1)
Request 2: Try DB → timeout after 30s → fail (counter=2)
Request 3: Try DB → timeout after 30s → fail (counter=3) → OPEN
Request 4: Circuit open → return error immediately (no wait!)
Request 5: Circuit open → return error immediately
...
Request N: Circuit becomes HALF_OPEN → try DBBenefit: Requests 4–N fail fast (milliseconds) instead of timing out (30 seconds).
Implementing circuit breaker on GCP
Option 1: Application-level (Go example)
import "github.com/grpc-ecosystem/go-grpc-middleware"
cb := circuitbreaker.New(
Name: "database",
MaxConsecutiveFailures: 3,
OpenTimeout: 30 * time.Second,
)
result, err := cb.Execute(func() (interface{}, error) {
return db.Query(ctx, sql)
})Option 2: Istio / Service Mesh
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: database
spec:
host: database
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 3
interval: 30s
baseEjectionTime: 30sIstio automatically stops sending traffic to pods failing health checks.
Circuit breaker vs retry
Don't confuse them:
Retry: Try again immediately (or with backoff)
Try request → fail → wait 100ms → try againCircuit breaker: Stop trying for a period
3 failures → open circuit → fail fast → after 30s, try againTogether:
Try request → fail → retry with backoff → still failing → circuit open → fail fastCost of circuit breaker
If circuit opens:
- Service returns errors
- Users see "service unavailable"
- Availability goes down
But availability goes down less than if system was thrashing (retrying forever).
Trade-off: Explicit error vs thrashing.
Production architecture patterns using these
Pattern 1: Canary + monitoring
Deploy new version:
1. Deploy to canary (5%)
2. Watch error rate, latency
3. If metrics good, proceed to 25%
4. If metrics bad, automatic rollback
Benefit: Catch bad deployments before hitting everyonePattern 2: Graceful degradation + feature flags
Service dependency fails:
1. Feature flag: enable_basic_mode = true
2. API strips non-essential features
3. Return degraded response
4. When dependency recovers, flag: enable_basic_mode = false
Benefit: Service stays available, users get somethingPattern 3: Circuit breaker + fallback
Database slow/failing:
1. Circuit breaker detects failures
2. Stops sending traffic to database
3. Returns cached data (stale, but something)
4. When database recovers, circuit closes
Benefit: Stops wasting resources on slow database, returns data from cacheSummary
Resilience patterns prevent failures from cascading:
□ Graceful degradation → serve reduced functionality when dependency unavailable
□ Canary deployment → roll out slowly, detect issues early
□ Circuit breaker → fail fast when service failing, don't waste resources
□ Feature flags → disable expensive features without deploy
□ Monitoring → watch metrics during deployment and degradation
□ Fallback → use cache or alternative when primary unavailable