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 MoreStrategy 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 1 | Strategy 2 | Compatibility | Notes |
---|---|---|---|
Retry | Circuit Breaker | ✅ Excellent | Order matters for behavior |
Retry | Timeout | ✅ Excellent | Timeout per attempt vs total |
Retry | Fallback | ✅ Excellent | Fallback after retries exhausted |
Retry | Hedging | ⚠️ Caution | Can create exponential request growth |
Circuit Breaker | Timeout | ✅ Excellent | Timeout prevents hanging during failures |
Circuit Breaker | Fallback | ✅ Excellent | Fallback when circuit is open |
Circuit Breaker | Rate Limiter | ✅ Good | Complementary protection mechanisms |
Timeout | Fallback | ✅ Excellent | Fallback on timeout |
Hedging | Rate Limiter | ⚠️ Caution | Rate limiting can block hedged attempts |
Performance Considerations
Strategy Overhead
Strategy | Overhead | When Active |
---|---|---|
Retry | Low | Only on failures |
Circuit Breaker | Very Low | Always (state tracking) |
Timeout | Low | Always (timer setup) |
Fallback | Very Low | Only on failures |
Hedging | Medium | Always (parallel executions) |
Rate Limiter | Low | Always (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:
- Pick a strategy that matches your immediate needs
- Study the detailed guides for implementation patterns
- Experiment with combinations to build comprehensive resilience
- Monitor and tune your configurations based on real-world behavior
Recommended Reading Order
For beginners:
- Retry Strategy - Most commonly used
- Timeout Strategy - Essential for responsiveness
- Fallback Strategy - User experience focused
For production systems:
- Circuit Breaker Strategy - Prevents cascading failures
- Rate Limiter Strategy - Controls resource usage
- Hedging Strategy - Optimizes performance