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
| Stan | Zachowanie |
|---|---|
closed | Ruch płynie normalnie; porażki są liczone |
open | Fast-fail — requesty nie idą do backendu, natychmiastowy failover |
half-open | Przepuszczane 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"
}
| Kod | HTTP | Kiedy |
|---|---|---|
rate_limit_exceeded | 429 | Klient przekroczył limit |
unauthorized | 401 | Zły/brak tokenu admina |
invalid_backend_index | 400 | Zły indeks w Admin API |
bad_gateway | 502 | Upstream nieosiągalny lub odrzucił |
service_unavailable | 503 | Brak zdrowego/dostępnego backendu |
gateway_timeout | 504 | Upstream 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