Reverse Proxy

Circuit breaker i retry

Maszyna stanów closed → open → half-open, fast-fail, bezpieczna polityka ponowień i koperty błędów z request ID.

Circuit breaker#

Każdy backend ma własny circuit breaker z trzema stanami:

text
CLOSED ──(failure_threshold porażek)──▶ OPEN
   ▲                                      │
   │                              (open_timeout)
   │                                      ▼
   └──(half_open_probes sukcesów)── HALF-OPEN
StanZachowanie
closedRuch płynie normalnie; porażki są liczone
openFast-fail — requesty nie idą do backendu, natychmiastowy failover
half-openPrzepuszczane są probe; sukces zamyka, porażka otwiera z powrotem

Konfiguracja#

yaml
circuit_breaker:
  failure_threshold: 5
  open_timeout: 30s
  half_open_probes: 1
  • failure_threshold — ile porażek otwiera breaker (domyślnie 5),
  • open_timeout — jak długo breaker zostaje open przed próbą (domyślnie 30s),
  • half_open_probes — ile sukcesów probe zamyka breaker (domyślnie 1).

Przykład: tolerantny breaker dla chwiejnego backendu#

yaml
circuit_breaker:
  failure_threshold: 10
  open_timeout: 15s
  half_open_probes: 3

Przykład: szybki failover dla krytycznej ścieżki#

yaml
circuit_breaker:
  failure_threshold: 3
  open_timeout: 60s
  half_open_probes: 2

Retry policy#

Ponowienia są bezpieczne z założenia:

  • tylko requesty idempotentne (GET, HEAD, OPTIONS, PUT, DELETE),
  • tylko requesty bez body — uploady nigdy nie są duplikowane,
  • łączna liczba prób 1–10 (domyślnie 2).
yaml
retry:
  max_attempts: 3
text
request GET /api/users
  → backend A: timeout
  → retry #1 → backend B: 200 OK

Dlaczego nie POST?

Ponowienie POST mogłoby utworzyć zasób dwa razy. Proxy świadomie nie ponawia requestów z body — jeśli potrzebujesz retry dla zapisów, rób to po stronie klienta z kluczem idempotencji.

Koperty błędów#

Każdy błąd gatewaya ma spójną strukturę JSON z request ID:

json
{
  "error": {
    "code": "bad_gateway",
    "message": "upstream request failed"
  },
  "request_id": "9f2c1a77b3e4d5a6"
}
KodHTTPKiedy
rate_limit_exceeded429Klient przekroczył limit
unauthorized401Zły/brak tokenu admina
invalid_backend_index400Zły indeks w Admin API
bad_gateway502Upstream nieosiągalny lub odrzucił
service_unavailable503Brak zdrowego/dostępnego backendu
gateway_timeout504Upstream przekroczył timeout

Request ID trafia też do logów i metryk — jeden identyfikator łączy klienta, log i dashboard.

Metryki breakera#

text
backend_circuit_breaker_open{backend="http://localhost:8081"} 1
proxy_retries_total 7
backend_errors_total{backend="http://localhost:8081"} 23

Przykład: pełna ścieżka odporności#

text
1. Request → rate limiter OK
2. Picker → backend A (healthy, closed)
3. Backend A: connection refused
4. Retry #1 → backend B: 200 OK
5. Backend A: failure_count = 1
   ... po 5 porażkach ...
6. Backend A: breaker OPEN → fast-fail
7. Po 30s: HALF-OPEN → probe
8. Probe OK → CLOSED → backend A wraca