Späť na blog

Circuit Breaker vs Rate Limiter vs Bulkhead: Kedy Ktorý Pattern Použiť

|
| resilience, circuit-breaker, rate-limiter, bulkhead, java, spring-boot, resilience4j

Pridali sme vsetky tri patterny a aj tak prisiel pager; problem bol v ich kombinacii. “Pridajme circuit breaker všade.” Takto vytvoríš systém, ktorý zlyháva kreatívnymi spôsobmi, ktoré nikto nepredvídal.

Circuit breaker, rate limiter a bulkhead riešia rôzne problémy. Použitie zlého z nich robí tvoj systém menej odolný, nie viac.

Testované na: Resilience4j 2.2, Spring Boot 3.2, Java 21, Prometheus + Grafana

Definícia Problémov

Circuit Breaker: Fail Fast pre Downstream Failures

Účel: Prestaň volať failing service
Chráni: TVOJ systém pred plytvaním zdrojmi na beznádejné volania
Trigger: Downstream failures (5xx, timeouty)

Bez circuit breaker:
Service A → Service B (down)
         → Threads blokované čakajúc na timeout
         → Thread pool vyčerpaný
         → Service A tiež zlyhá (kaskáda)

S circuit breaker:
Service A → Circuit OPEN → Okamžité zlyhanie (bez čakania)
         → Zdroje zachované
         → Service A pokračuje v práci (degraded)

Rate Limiter: Ochrana pred Preťažením

Účel: Limituj rýchlosť prichádzajúcich requestov
Chráni: DOWNSTREAM systém pred preťažením
Trigger: Príliš veľa requestov (bez ohľadu na úspech/zlyhanie)

Bez rate limiter:
100,000 RPS → Service → Database
                      → Database preťažená
                      → Všetko zlyhá

S rate limiter:
100,000 RPS → Rate Limiter (1000 RPS) → Service → Database
           → 99,000 zamietnutých okamžite
           → Database happy, 1000 requestov uspeje

Bulkhead: Izolácia Failure Domén

Účel: Izoluj zdroje per závislosť
Chráni: Systém pred jednou závislosťou, ktorá spotrebuje všetky zdroje
Trigger: Vyčerpanie zdrojov (threads, connections)

Bez bulkhead:
Service A → Service B (slow) → Používa všetkých 200 threads
         → Service C (fast) → Žiadne threads dostupné!
         → B aj C volania zlyhajú

S bulkhead:
Service A → Service B → Bulkhead (50 threads max)
         → Service C → Bulkhead (50 threads max)
         → Service B slow? Len 50 threads blokovaných
         → Service C stále funguje!

Rozhodovacia Matica

ScenárCircuit BreakerRate LimiterBulkhead
Downstream service zlyháva
Príliš veľa prichádzajúcich requestov
Jedna pomalá závislosť
Prevencia kaskádového zlyhania
Ochrana zdieľaného zdroja
Client-side ochrana
Server-side ochrana

Implementácia s Resilience4j

Circuit Breaker

// CircuitBreakerConfig.java
@Configuration
public class CircuitBreakerConfig {

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            // Otvor circuit po 50% zlyhaní
            .failureRateThreshold(50)
            // ... v posledných 10 volaniach
            .slidingWindowType(SlidingWindowType.COUNT_BASED)
            .slidingWindowSize(10)
            // Zostaň open 30 sekúnd
            .waitDurationInOpenState(Duration.ofSeconds(30))
            // Povoľ 5 testovacích volaní v half-open stave
            .permittedNumberOfCallsInHalfOpenState(5)
            // Tieto zaznamenaj ako zlyhania
            .recordExceptions(IOException.class, TimeoutException.class)
            // Tieto ignoruj
            .ignoreExceptions(BusinessException.class)
            .build();

        return CircuitBreakerRegistry.of(config);
    }
}

// PaymentService.java
@Service
public class PaymentService {

    private final CircuitBreaker circuitBreaker;
    private final PaymentClient paymentClient;

    public PaymentService(CircuitBreakerRegistry registry, PaymentClient client) {
        this.circuitBreaker = registry.circuitBreaker("payment-service");
        this.paymentClient = client;
    }

    public PaymentResult processPayment(PaymentRequest request) {
        return circuitBreaker.executeSupplier(() ->
            paymentClient.charge(request)
        );
    }

    // S fallback
    public PaymentResult processPaymentWithFallback(PaymentRequest request) {
        return Try.ofSupplier(
            CircuitBreaker.decorateSupplier(circuitBreaker,
                () -> paymentClient.charge(request))
        ).recover(CallNotPermittedException.class,
            ex -> PaymentResult.queued("Circuit open, queued for retry")
        ).recover(Exception.class,
            ex -> PaymentResult.failed(ex.getMessage())
        ).get();
    }
}

Rate Limiter

// RateLimiterConfig.java
@Configuration
public class RateLimiterConfig {

    @Bean
    public RateLimiterRegistry rateLimiterRegistry() {
        RateLimiterConfig config = RateLimiterConfig.custom()
            // 100 requestov za sekundu
            .limitForPeriod(100)
            .limitRefreshPeriod(Duration.ofSeconds(1))
            // Čakaj max 500ms na permit
            .timeoutDuration(Duration.ofMillis(500))
            .build();

        return RateLimiterRegistry.of(config);
    }
}

// ApiController.java
@RestController
public class ApiController {

    private final RateLimiter rateLimiter;

    public ApiController(RateLimiterRegistry registry) {
        this.rateLimiter = registry.rateLimiter("api");
    }

    @GetMapping("/data")
    public ResponseEntity<Data> getData() {
        return RateLimiter.decorateSupplier(rateLimiter, () ->
            ResponseEntity.ok(dataService.fetch())
        ).get();
    }
}

// Alebo s anotáciami (Spring Boot)
@Service
public class DataService {

    @RateLimiter(name = "api", fallbackMethod = "fallback")
    public Data fetch() {
        return repository.findAll();
    }

    public Data fallback(RequestNotPermitted ex) {
        throw new TooManyRequestsException("Rate limit exceeded");
    }
}

Bulkhead

// BulkheadConfig.java
@Configuration
public class BulkheadConfig {

    @Bean
    public ThreadPoolBulkheadRegistry bulkheadRegistry() {
        ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
            // Max 10 concurrent volaní
            .maxThreadPoolSize(10)
            .coreThreadPoolSize(5)
            // Queue 20 čakajúcich volaní
            .queueCapacity(20)
            // Drž idle threads 60s
            .keepAliveDuration(Duration.ofSeconds(60))
            .build();

        return ThreadPoolBulkheadRegistry.of(config);
    }
}

// ExternalServices.java
@Service
public class ExternalServices {

    private final ThreadPoolBulkhead paymentBulkhead;
    private final ThreadPoolBulkhead inventoryBulkhead;
    private final ThreadPoolBulkhead notificationBulkhead;

    public ExternalServices(ThreadPoolBulkheadRegistry registry) {
        // Každý service má izolovaný thread pool
        this.paymentBulkhead = registry.bulkhead("payment");
        this.inventoryBulkhead = registry.bulkhead("inventory");
        this.notificationBulkhead = registry.bulkhead("notification");
    }

    public CompletableFuture<PaymentResult> processPayment(Order order) {
        return paymentBulkhead.executeSupplier(() ->
            paymentClient.charge(order)
        );
    }

    public CompletableFuture<InventoryResult> reserveInventory(Order order) {
        return inventoryBulkhead.executeSupplier(() ->
            inventoryClient.reserve(order)
        );
    }
}

Kombinovanie Patterns

Správne: Circuit Breaker + Bulkhead

// Správna kombinácia: CB vnútri Bulkhead
@Service
public class OrderService {

    private final CircuitBreaker circuitBreaker;
    private final ThreadPoolBulkhead bulkhead;

    public CompletableFuture<OrderResult> createOrder(Order order) {
        // 1. Bulkhead limituje concurrent volania (resource isolation)
        // 2. Circuit breaker zlyháva rýchlo ak je service down
        Supplier<OrderResult> decoratedSupplier =
            CircuitBreaker.decorateSupplier(circuitBreaker, () ->
                externalService.process(order)
            );

        return bulkhead.executeSupplier(decoratedSupplier);
    }
}

Správne: Rate Limiter na Entry Point

// Rate limiter na API gateway / controller úrovni
@RestController
@RateLimiter(name = "api")  // Prvá línia obrany
public class OrderController {

    @PostMapping("/orders")
    public Order createOrder(@RequestBody OrderRequest request) {
        // CB + Bulkhead vnútri pre downstream volania
        return orderService.create(request);
    }
}

Zle: Rate Limiter pre Downstream Failures

// ZLE: Rate limiter tu nepomôže!
@Service
public class PaymentService {

    @RateLimiter(name = "payment")  // Nepomôže ak je payment service DOWN
    public PaymentResult charge(Payment payment) {
        return paymentClient.charge(payment);  // Zlyhá bez ohľadu na rate
    }

    // SPRÁVNE: Použi circuit breaker
    @CircuitBreaker(name = "payment")
    public PaymentResult chargeCorrect(Payment payment) {
        return paymentClient.charge(payment);
    }
}

Benchmarky

Scenár: Payment Service Spadne

Setup:
- 100 RPS prichádzajúcich
- Payment service: 30s timeout → potom zlyhá
- Thread pool: 200 threads

BEZ akéhokoľvek pattern:
- Všetkých 200 threads blokovaných čakajúcich na timeout
- Po 2 sekundách: thread pool vyčerpaný
- Všetky requesty zlyhajú (vrátane non-payment)

S Circuit Breaker:
- Prvých 10 volaní: timeout (30s každé)
- Circuit sa otvorí po 50% zlyhaní
- Následné volania: okamžité zlyhanie (< 1ms)
- Thread pool usage: max 10 threads
- Non-payment endpointy: neovplyvnené

S Bulkhead:
- Payment bulkhead: 20 threads max
- Všetkých 20 threads blokovaných (30s timeout)
- Non-payment endpointy: fungujú (180 threads dostupných)
- ALE: payment requesty stále čakajú 30s pred zlyhaním

S Circuit Breaker + Bulkhead:
- Prvých 10 volaní: timeout v bulkhead (max 20 threads)
- Circuit sa otvorí
- Následné volania: okamžité zlyhanie
- Non-payment endpointy: neovplyvnené
- Thread usage: minimálne

Výsledky Load Testu

Target: 1000 RPS, Payment service down

| Konfigurácia | p99 Latency | Error Rate | Thread Usage |
|--------------|-------------|------------|--------------|
| Bez ochrany | 30,000ms | 100% | 200/200 |
| Len CB | 50ms | 100%* | 15/200 |
| Len Bulkhead | 30,000ms | 100% | 20/200 |
| CB + Bulkhead | 50ms | 100%* | 10/200 |
| Rate Limiter | 30,000ms | 100% | 200/200 |

* Errors sú rýchle zlyhania, nie timeouty

Monitoring

Prometheus Metriky

// Resilience4j auto-exponuje metriky
// application.yml
resilience4j:
  circuitbreaker:
    configs:
      default:
        registerHealthIndicator: true
    instances:
      payment:
        baseConfig: default

management:
  metrics:
    tags:
      application: order-service

Kľúčové Metriky

# Circuit Breaker stav
resilience4j_circuitbreaker_state{name="payment"}
# 0=CLOSED, 1=OPEN, 2=HALF_OPEN

# Failure rate
resilience4j_circuitbreaker_failure_rate{name="payment"}

# Bulkhead dostupné threads
resilience4j_bulkhead_available_concurrent_calls{name="payment"}

# Rate limiter dostupné permits
resilience4j_ratelimiter_available_permissions{name="api"}

Alerty

groups:
- name: resilience
  rules:
  - alert: CircuitBreakerOpen
    expr: resilience4j_circuitbreaker_state == 1
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Circuit breaker {{ $labels.name }} je OPEN"

  - alert: BulkheadSaturated
    expr: resilience4j_bulkhead_available_concurrent_calls < 2
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Bulkhead {{ $labels.name }} blízko kapacity"

Checklist

## Výber Resilience Pattern

### Circuit Breaker - Použi keď:
- [ ] Downstream service môže zlyhať alebo byť pomalý
- [ ] Chceš rýchle zlyhanie namiesto timeout
- [ ] Kaskádové zlyhania sú riziko

### Rate Limiter - Použi keď:
- [ ] Ochrana pred traffic spikes
- [ ] API má usage kvóty
- [ ] Database/service má kapacitný limit

### Bulkhead - Použi keď:
- [ ] Viaceré závislosti zdieľajú thread pool
- [ ] Jedna pomalá závislosť nemá ovplyvniť ostatné
- [ ] Je potrebná resource izolácia

### Pravidlá Kombinácie:
- [ ] Circuit Breaker: per downstream service
- [ ] Rate Limiter: na API entry point
- [ ] Bulkhead: izoluj rôzne závislosti
- [ ] Nikdy: Rate Limiter pre downstream failures

Záver

Tri patterns, tri rôzne účely:

  1. Circuit Breaker: Downstream service zlyháva → fail fast
  2. Rate Limiter: Príliš veľa prichádzajúcej traffic → zamietni prebytok
  3. Bulkhead: Izoluj zlyhania → jedna závislosť nemôže spotrebovať všetky zdroje

Použitie zlého patternu je horšie ako žiadny pattern.


Súvisiace články

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "Circuit Breaker vs Rate Limiter vs Bulkhead: Kedy Ktorý Pattern Použiť". https://www.michal-drozd.com/sk/blog/circuit-breaker-rate-limiter-bulkhead/ (Publikované 19. septembra 2025).