← Recipes

阅读中文版本

Add a Graceful Degradation Fallback

Serve a stale or reduced response when a dependency fails, mark it explicitly, and verify that the fallback cannot mask a data-integrity problem.

~30 minresilienceapiuser-experience

Task

Keep a critical read path usable when a non-critical dependency is unavailable, instead of returning a generic 500.

When to use this

A recommendations service is down. The product page fails entirely even though the core product data is available and a cached recommendation list could be shown.

Prerequisites

  • A clear ranking of critical versus optional dependencies
  • A bounded fallback dataset or stale cache
  • A way to signal degraded responses to clients and monitoring

Steps

1. Wrap the optional dependency with a deadline and a fallback that returns a defined value.

async function recommendations(productId) {
  try {
    return await breaker.call(() =>
      fetchRecommendations(productId, { signal: AbortSignal.timeout(300) })
    );
  } catch (error) {
    logger.warn({ error, productId }, 'recommendations degraded');
    return { items: cachedRecommendations(productId), degraded: true };
  }
}

Expected output: A failed recommendations call returns cached or empty items instead of throwing.

2. Mark the response and metric as degraded, and never fall back for data that affects price, authorization, or inventory.

res.setHeader('x-degraded', 'recommendations');
metrics.increment('recommendations.degraded');
res.json({ product, recommendations: recommendationsResult });

Expected output: Clients and dashboards can distinguish a degraded response from a complete one.

3. Test with the dependency returning errors and with it returning slow responses; verify the core path and fallback behavior.

node --test degradation.test.mjs

Expected output: The page still serves core data, the header is present, and the dependency call is bounded.

Troubleshooting

Problem

The fallback hides a real outage

Solution

Emit a metric and alert on degraded responses; a fallback without visibility becomes the new normal.

Problem

Stale data violates a product promise

Solution

Only fall back for optional content, and define the maximum staleness or disable the fallback for sensitive paths.

Limitations

  • A fallback is not a fix for the dependency; it buys time and protects the core path.
  • Cached fallbacks can be wrong or empty; the UI must handle both states honestly.

Fallbacks are for optional paths

A graceful fallback keeps the critical path alive when an optional dependency fails. Recommendations, related items, and non-essential personalization can degrade to cached or empty data. Price, inventory, authorization, and payment status cannot: a stale or invented value there is worse than an error. Rank dependencies before adding a fallback.

try {
  return await withTimeout(loadOptional(), 300);
} catch {
  metrics.increment('optional.degraded');
  return { items: cachedItems, degraded: true };
}

Make degradation visible

A fallback that silently returns stale data hides an outage. Return a header or field, emit a metric, and alert when the degraded rate crosses a threshold. The UI should show a subtle notice rather than pretending the content is complete. A fallback that becomes permanent is technical debt with a friendly name.

Verify the failure path

Test a fast error, a slow timeout, and a malformed response. Confirm the core data still renders, the fallback is bounded, and the degraded signal reaches metrics and the response. Test the recovery path too: when the dependency returns, the fallback should stop and normal responses should resume without a restart.