Circuit breaker¶
Cuando una BD lleva minutos caída, el retry agresivo agota pools y
sobrecarga el servidor cuando vuelve. El circuit breaker corta:
cuando detecta N fallos consecutivos, abre el circuito y todas las
llamadas rebotan inmediatamente con CircuitOpenError. Tras un
cooldown, prueba con una sola request (HALF_OPEN) y reabre si
falla.
dorm.contrib.circuit_breaker (3.4+).
API¶
from dorm.contrib.circuit_breaker import (
CircuitBreaker, CircuitOpenError,
circuit_breaker, reset_circuit_breakers, get_state,
)
# Singleton per-name (recomendado)
cb = circuit_breaker("default", failure_threshold=5, open_window_s=30.0)
def safe_count() -> int | None:
try:
with cb:
return Author.objects.count()
except CircuitOpenError:
return None # cache, default value, etc.
# Async
async def asafe_count() -> int | None:
try:
async with cb.aprotect():
return await Author.objects.acount()
except CircuitOpenError:
return None
Estados¶
failures ≥ threshold
┌───────┐ ────────────────────────▶ ┌────────┐
│CLOSED │ │ OPEN │
└───────┘ ◀──── reset / probe OK ── └────────┘
▲ │
│ │ open_window_s elapsed
│ ┌──────────────┐ │
└───────────│ HALF_OPEN │ ◀────────┘
probe ok └──────────────┘
│
│ probe fails
▼
back to OPEN
- CLOSED: requests pasan. Cada fallo incrementa contador.
- OPEN: cada llamada raisea
CircuitOpenErrorinstantáneo. Trasopen_window_s, transiciona a HALF_OPEN. - HALF_OPEN: una llamada pasa para probar. Éxito → CLOSED. Fallo → OPEN con timer fresco.
Configuración¶
cb = circuit_breaker(
"alias",
failure_threshold=5, # N fallos consecutivos para abrir
open_window_s=30.0, # tiempo en OPEN antes de HALF_OPEN
)
Inspección¶
Útil para Prometheus exporters / dashboards.
Reset (tests)¶
Receta: con FastAPI¶
from dorm.contrib.circuit_breaker import circuit_breaker, CircuitOpenError
@app.get("/users/{pk}")
async def get_user(pk: int):
cb = circuit_breaker("default")
try:
async with cb.aprotect():
user = await User.objects.aget(pk=pk)
except CircuitOpenError:
return JSONResponse(
{"detail": "service unavailable"},
status_code=503,
headers={"Retry-After": "30"},
)
return user
Pitfalls¶
- Per-proceso: cada worker mantiene su propio breaker. Un worker en OPEN no impide que otro intente. Para coordinación cross-worker, lleva el contador a Redis.
- Tunes prematuros:
failure_threshold=1rebota a la primera falla flake. Mide tu tasa de errores real antes de ajustar. - Falsos positivos: si tu app levanta
OperationalErrorde forma legítima (e.g.select_for_updateque tarda), el breaker cuenta esos. Filtra por excepción si hace falta.
Más¶
- Helpers
- API:
dorm.contrib.circuit_breaker