Skip to main content

Resilience Strategies Overview

Polly Dart provides six core resilience strategies that can be combined to build robust applications. Each strategy addresses specific failure scenarios and can be configured to match your application's needs.

Strategy Categories

🔄 Reactive Strategies

These strategies respond to failures after they occur:

🔄 Retry

Purpose: Automatically retry failed operations with configurable backoff strategies.

Use Case: Transient network failures, temporary service unavailability.

Learn More

⚡ Circuit Breaker

Purpose: Prevent cascading failures by temporarily blocking calls to failing services.

Use Case: Protecting against persistent failures, giving services time to recover.

Learn More

🎯 Fallback

Purpose: Provide alternative responses when primary operations fail.

Use Case: Graceful degradation, cached responses, default values.

Learn More

🏁 Hedging

Purpose: Execute multiple parallel attempts and use the fastest successful response.

Use Case: Redundant services, optimizing for response time.

Learn More

⚡ Proactive Strategies

These strategies prevent failures before they impact your system:

⏱️ Timeout

Purpose: Cancel operations that take too long to complete.

Use Case: Preventing hanging requests, ensuring responsive UX.

Learn More

🚦 Rate Limiter

Purpose: Control the rate of operations and manage concurrency.

Use Case: Preventing service overload, managing resource usage.

Learn More

Strategy Execution Order

The order in which you add strategies to your pipeline matters. Strategies are applied in sequence, creating a nested execution flow:

Example: Different Orders, Different Behaviors

// Order 1: Retry → Circuit Breaker → Timeout
final pipeline1 = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(maxRetryAttempts: 3))
.addCircuitBreaker()
.addTimeout(Duration(seconds: 10))
.build();

// Order 2: Circuit Breaker → Retry → Timeout
final pipeline2 = ResiliencePipelineBuilder()
.addCircuitBreaker()
.addRetry(RetryStrategyOptions(maxRetryAttempts: 3))
.addTimeout(Duration(seconds: 10))
.build();

Pipeline 1 Behavior:

  • Retry strategy sees circuit breaker results
  • Circuit breaker sees individual operation attempts
  • Each retry attempt gets its own 10-second timeout

Pipeline 2 Behavior:

  • Circuit breaker sees retry results (fewer calls to track)
  • Retry strategy works within circuit breaker state
  • The entire retry sequence gets a 10-second timeout

Choosing the Right Strategies

Common Combination Patterns

Web API Calls

final webApiPipeline = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(
maxRetryAttempts: 3,
delay: Duration(seconds: 1),
backoffType: DelayBackoffType.exponential,
))
.addCircuitBreaker(CircuitBreakerStrategyOptions(
failureRatio: 0.5,
breakDuration: Duration(seconds: 30),
))
.addTimeout(Duration(seconds: 30))
.addFallback(FallbackStrategyOptions.withValue('Cached response'))
.build();

Database Operations

final databasePipeline = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(
maxRetryAttempts: 2,
delay: Duration(milliseconds: 100),
))
.addTimeout(Duration(seconds: 10))
.build();

High-Frequency Operations

final highFrequencyPipeline = ResiliencePipelineBuilder()
.addRateLimiter(RateLimiterStrategyOptions(
permitLimit: 100,
window: Duration(seconds: 1),
))
.addTimeout(Duration(milliseconds: 500))
.build();

Critical Real-Time Operations

final realTimePipeline = ResiliencePipelineBuilder()
.addHedging(HedgingStrategyOptions(
maxHedgedAttempts: 2,
delay: Duration(milliseconds: 50),
actionProvider: (args) => (context) => callBackupService(),
))
.addTimeout(Duration(milliseconds: 200))
.build();

Strategy Compatibility Matrix

Strategy 1Strategy 2CompatibilityNotes
RetryCircuit Breaker✅ ExcellentOrder matters for behavior
RetryTimeout✅ ExcellentTimeout per attempt vs total
RetryFallback✅ ExcellentFallback after retries exhausted
RetryHedging⚠️ CautionCan create exponential request growth
Circuit BreakerTimeout✅ ExcellentTimeout prevents hanging during failures
Circuit BreakerFallback✅ ExcellentFallback when circuit is open
Circuit BreakerRate Limiter✅ GoodComplementary protection mechanisms
TimeoutFallback✅ ExcellentFallback on timeout
HedgingRate Limiter⚠️ CautionRate limiting can block hedged attempts

Performance Considerations

Strategy Overhead

StrategyOverheadWhen Active
RetryLowOnly on failures
Circuit BreakerVery LowAlways (state tracking)
TimeoutLowAlways (timer setup)
FallbackVery LowOnly on failures
HedgingMediumAlways (parallel executions)
Rate LimiterLowAlways (permit checking)

Memory Usage Guidelines

// ✅ Good: Reuse pipelines across operations
class ApiService {
static final _pipeline = ResiliencePipelineBuilder()
.addRetry()
.addTimeout(Duration(seconds: 30))
.build();

Future<Data> fetchData() => _pipeline.execute(() => _httpGet('/data'));
}

// ❌ Bad: Creating pipelines per operation
Future<Data> fetchData() {
final pipeline = ResiliencePipelineBuilder().addRetry().build(); // Don't do this
return pipeline.execute(() => _httpGet('/data'));
}

Configuration Best Practices

1. Start Simple

Begin with basic configurations and add complexity as needed:

// Start with this
final simple = ResiliencePipelineBuilder()
.addRetry()
.addTimeout(Duration(seconds: 30))
.build();

// Evolve to this when you understand your failure patterns
final sophisticated = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(
maxRetryAttempts: 3,
delay: Duration(seconds: 1),
backoffType: DelayBackoffType.exponential,
shouldHandle: (outcome) => isTransientError(outcome),
onRetry: (args) => logRetryAttempt(args),
))
.addCircuitBreaker(CircuitBreakerStrategyOptions(
failureRatio: 0.6,
minimumThroughput: 20,
samplingDuration: Duration(minutes: 1),
breakDuration: Duration(minutes: 5),
))
.addTimeout(Duration(seconds: 30))
.build();

2. Environment-Specific Configuration

final Duration timeout = isProduction 
? Duration(seconds: 30)
: Duration(minutes: 5); // Longer timeout for debugging

final int maxRetries = isProduction ? 3 : 1; // Fewer retries in dev

final pipeline = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(maxRetryAttempts: maxRetries))
.addTimeout(timeout)
.build();

3. Observability First

Always include logging and metrics:

final observablePipeline = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(
onRetry: (args) async {
logger.warning('Retry attempt ${args.attemptNumber + 1} for ${args.context.operationKey}');
metrics.incrementCounter('retries');
},
))
.addCircuitBreaker(CircuitBreakerStrategyOptions(
onOpened: (args) async {
logger.error('Circuit breaker opened');
alerts.sendAlert('Service degradation detected');
},
))
.build();

Testing Strategies

Each strategy provides mechanisms for testing:

// Test retry behavior
test('should retry on transient failures', () async {
var attempts = 0;
final pipeline = ResiliencePipelineBuilder()
.addRetry(RetryStrategyOptions(maxRetryAttempts: 2))
.build();

try {
await pipeline.execute((context) async {
attempts++;
if (attempts < 3) throw SocketException('Network error');
return 'success';
});
} catch (e) {
fail('Should have succeeded after retries');
}

expect(attempts, equals(3));
});

// Test circuit breaker behavior
test('should open circuit after threshold', () async {
final circuitBreaker = CircuitBreakerStrategy(
CircuitBreakerStrategyOptions(failureRatio: 0.5, minimumThroughput: 2)
);

// Cause failures to trip the circuit
for (int i = 0; i < 3; i++) {
try {
await circuitBreaker.executeCore((context) async {
throw Exception('failure');
}, ResilienceContext());
} catch (e) {}
}

expect(circuitBreaker.state, equals(CircuitState.open));
});

Next Steps

Now that you understand the strategy landscape:

  1. Pick a strategy that matches your immediate needs
  2. Study the detailed guides for implementation patterns
  3. Experiment with combinations to build comprehensive resilience
  4. Monitor and tune your configurations based on real-world behavior

For beginners:

  1. Retry Strategy - Most commonly used
  2. Timeout Strategy - Essential for responsiveness
  3. Fallback Strategy - User experience focused

For production systems:

  1. Circuit Breaker Strategy - Prevents cascading failures
  2. Rate Limiter Strategy - Controls resource usage
  3. Hedging Strategy - Optimizes performance