Skip to content
Plurafy

Graceful Degradation

Graceful degradation ensures services remain partially functional during failures. Learn to implement resilience patterns for fault tolerance.

Implement graceful degradation strategies that maintain service availability even when some components fail.

  • Feature Flags: Disable features conditionally
  • Circuit Breakers: Stop calling failing services
  • Fallbacks: Use default/cached data
  • Timeouts: Limit wait times
  • Retry Logic: Attempt recovery
  • Partial Availability: Better than complete failure
  • User Experience: Acceptable reduced functionality
  • Progressive Enhancement: Core features first
  • Monitoring: Track degradation events
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,
};
}
}
// Usage
const 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(),
});
}
});
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;
}
}
}
// Usage
const fallbackHandler = new FallbackHandler();
// Register fallback for recommendations
fallbackHandler.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);
});
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),
};
}
}
// Usage
const 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());
}
});
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));
}
}
// Usage
const 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' });
}
});
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],
};
}
}
// Usage
const 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 degradation
setInterval(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);
  1. Design

    • Plan degradation paths
    • Prioritize core features
    • Communicate status
    • Document degradation
  2. Implementation

    • Use circuit breakers
    • Implement fallbacks
    • Set timeouts
    • Retry intelligently
  3. Monitoring

    • Track degradation events
    • Monitor system health
    • Alert on degradation
    • Log decisions
  4. User Experience

    • Transparent degradation
    • Set expectations
    • Provide alternatives
    • Recovery information
  5. Testing

    • Test failure scenarios
    • Verify fallbacks work
    • Test performance impact
    • Practice recovery
  • Payment Systems: Use offline fallback
  • Search Services: Degrade search quality
  • Recommendations: Use cached defaults
  • Analytics: Disable heavy processing
  • Notifications: Queue when service overloaded