Graceful Degradation
Graceful degradation ensures services remain partially functional during failures. Learn to implement resilience patterns for fault tolerance.
Challenge
Section titled “Challenge”Implement graceful degradation strategies that maintain service availability even when some components fail.
Explanation
Section titled “Explanation”Degradation Strategies
Section titled “Degradation Strategies”- Feature Flags: Disable features conditionally
- Circuit Breakers: Stop calling failing services
- Fallbacks: Use default/cached data
- Timeouts: Limit wait times
- Retry Logic: Attempt recovery
Core Principles
Section titled “Core Principles”- Partial Availability: Better than complete failure
- User Experience: Acceptable reduced functionality
- Progressive Enhancement: Core features first
- Monitoring: Track degradation events
Code Examples
Section titled “Code Examples”1. Circuit Breaker Pattern
Section titled “1. Circuit Breaker Pattern”class CircuitBreaker { constructor(fn, options = {}) { this.fn = fn; this.failureThreshold = options.failureThreshold || 5; this.successThreshold = options.successThreshold || 2; this.timeout = options.timeout || 60000;
this.failureCount = 0; this.successCount = 0; this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN this.nextAttempt = Date.now(); }
async execute(...args) { if (this.state === 'OPEN') { if (Date.now() < this.nextAttempt) { throw new Error('Circuit breaker is OPEN'); } this.state = 'HALF_OPEN'; }
try { const result = await this.fn(...args); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } }
onSuccess() { this.failureCount = 0;
if (this.state === 'HALF_OPEN') { this.successCount++; if (this.successCount >= this.successThreshold) { this.successCount = 0; this.state = 'CLOSED'; } } }
onFailure() { this.successCount = 0; this.failureCount++;
if (this.failureCount >= this.failureThreshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; } }
getState() { return { state: this.state, failureCount: this.failureCount, successCount: this.successCount, }; }}
// Usageconst paymentBreaker = new CircuitBreaker( async (amount) => { return await paymentService.charge(amount); }, { failureThreshold: 3, timeout: 30000 });
app.post('/checkout', async (req, res) => { try { const result = await paymentBreaker.execute(req.body.amount); res.json({ success: true, transaction: result }); } catch (error) { // Fallback: Process payment offline res.json({ success: true, message: 'Payment queued for processing', transactionId: generateId(), }); }});2. Fallback Strategies
Section titled “2. Fallback Strategies”class FallbackHandler { constructor() { this.fallbacks = new Map(); }
registerFallback(service, fallback) { this.fallbacks.set(service, fallback); }
async executeWithFallback(service, primaryFn, ...args) { try { return await primaryFn(...args); } catch (error) { console.warn(`Service ${service} failed, using fallback`);
const fallback = this.fallbacks.get(service); if (fallback) { return await fallback(...args); }
throw error; } }}
// Usageconst fallbackHandler = new FallbackHandler();
// Register fallback for recommendationsfallbackHandler.registerFallback('recommendations', async (userId) => { // Return popular items as fallback return await cache.get(`popular-items`) || [{ id: 1, title: 'Popular Item' }];});
fallbackHandler.registerFallback('userProfile', async (userId) => { // Return minimal profile return { id: userId, name: 'User' };});
app.get('/user/:id/profile', async (req, res) => { const profile = await fallbackHandler.executeWithFallback( 'userProfile', () => userService.getProfile(req.params.id), req.params.id );
res.json(profile);});3. Feature Flags for Graceful Degradation
Section titled “3. Feature Flags for Graceful Degradation”class FeatureFlagManager { constructor() { this.flags = new Map(); this.loadDefaults(); }
loadDefaults() { this.flags.set('premium-features', true); this.flags.set('new-checkout', false); this.flags.set('advanced-analytics', true); }
isEnabled(flag, context = {}) { if (!this.flags.has(flag)) { return false; }
const enabled = this.flags.get(flag);
// Check context conditions if (context.errorRate && context.errorRate > 0.1) { // Disable feature if error rate is high return false; }
if (context.userId && context.userId === 'test-user') { return true; // Always enable for test user }
return enabled; }
setFlag(flag, enabled) { this.flags.set(flag, enabled); }
getMetrics() { return { enabledFeatures: Array.from(this.flags.entries()) .filter(([_, enabled]) => enabled) .map(([flag, _]) => flag), }; }}
// Usageconst featureFlags = new FeatureFlagManager();
app.get('/api/checkout', (req, res) => { const context = { userId: req.user.id, errorRate: getSystemErrorRate(), };
if (featureFlags.isEnabled('new-checkout', context)) { // Use new checkout flow res.json(newCheckoutFlow()); } else { // Use legacy checkout flow res.json(legacyCheckoutFlow()); }});
app.get('/api/analytics', (req, res) => { if (featureFlags.isEnabled('advanced-analytics')) { res.json(advancedAnalytics()); } else { res.json(basicAnalytics()); }});4. Retry with Exponential Backoff
Section titled “4. Retry with Exponential Backoff”class RetryStrategy { constructor(options = {}) { this.maxRetries = options.maxRetries || 3; this.initialDelay = options.initialDelay || 1000; this.maxDelay = options.maxDelay || 30000; this.backoffMultiplier = options.backoffMultiplier || 2; }
async execute(fn, context = {}) { let lastError; let delay = this.initialDelay;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error;
// Don't retry on client errors if (error.statusCode && error.statusCode < 500) { throw error; }
if (attempt < this.maxRetries) { console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`); await this.wait(delay); delay = Math.min(delay * this.backoffMultiplier, this.maxDelay); } } }
throw new Error(`Failed after ${this.maxRetries} retries: ${lastError.message}`); }
wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }}
// Usageconst retryStrategy = new RetryStrategy({ maxRetries: 3, initialDelay: 100, backoffMultiplier: 2,});
app.post('/api/save', async (req, res) => { try { const result = await retryStrategy.execute( () => database.insert(req.body) ); res.json({ success: true, data: result }); } catch (error) { res.status(503).json({ error: 'Service temporarily unavailable' }); }});5. Graceful Service Degradation
Section titled “5. Graceful Service Degradation”class ServiceDegradationManager { constructor() { this.services = new Map(); this.degradationLevel = 0; // 0-3 }
registerService(name, levels) { this.services.set(name, levels); }
getDegradedResponse(serviceName) { const levels = this.services.get(serviceName); if (!levels) return null;
return levels[Math.min(this.degradationLevel, levels.length - 1)]; }
updateDegradationLevel(level) { this.degradationLevel = Math.max(0, Math.min(3, level)); }
getSystemStatus() { return { degradationLevel: this.degradationLevel, description: [ 'Normal', 'Degraded - limited features', 'Severely degraded - core only', 'Critical - emergency mode', ][this.degradationLevel], }; }}
// Usageconst degradationManager = new ServiceDegradationManager();
degradationManager.registerService('search', [ { type: 'full-search', query: async (q) => fullSearch(q) }, { type: 'basic-search', query: async (q) => basicSearch(q) }, { type: 'cache-only', query: async (q) => cacheSearch(q) }, { type: 'no-search', query: async (q) => [] },]);
app.get('/api/search', async (req, res) => { const service = degradationManager.getDegradedResponse('search'); const results = await service.query(req.query.q); res.json(results);});
// Monitor system and update degradationsetInterval(async () => { const health = await getSystemHealth();
if (health.cpuUsage > 90 || health.memoryUsage > 95) { degradationManager.updateDegradationLevel(3); } else if (health.cpuUsage > 75 || health.memoryUsage > 85) { degradationManager.updateDegradationLevel(2); } else if (health.errors > 0.1) { degradationManager.updateDegradationLevel(1); } else { degradationManager.updateDegradationLevel(0); }}, 10000);Best Practices
Section titled “Best Practices”-
Design
- Plan degradation paths
- Prioritize core features
- Communicate status
- Document degradation
-
Implementation
- Use circuit breakers
- Implement fallbacks
- Set timeouts
- Retry intelligently
-
Monitoring
- Track degradation events
- Monitor system health
- Alert on degradation
- Log decisions
-
User Experience
- Transparent degradation
- Set expectations
- Provide alternatives
- Recovery information
-
Testing
- Test failure scenarios
- Verify fallbacks work
- Test performance impact
- Practice recovery
Real-World Applications
Section titled “Real-World Applications”- Payment Systems: Use offline fallback
- Search Services: Degrade search quality
- Recommendations: Use cached defaults
- Analytics: Disable heavy processing
- Notifications: Queue when service overloaded