Idempotency
Las fallas de red ocurren. La forma segura de reintentar una llamada a la API sin riesgo de duplicados es usar el header Idempotency-Key — DVS lo reconoce y deduplica dentro de una ventana de 24 horas.
Cómo funciona
- Generar un UUID del lado del cliente por operación lógica (una classification, una validation).
- Enviarlo en el header
Idempotency-Keyen cada retry de esa operación. - Si se reintenta y DVS ya procesó el original, DVS retorna la response original sin reprocesar.
curl -X POST https://dvs-ingestion-api.sandbox.osigu.com/v1/classification-requests \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479" \
...
Cuándo deduplica DVS
| Escenario | Comportamiento de DVS |
|---|---|
Misma Idempotency-Key, mismo body, dentro de 24h | Retorna la response en caché. Sin nuevo procesamiento. Sin nuevos cargos. |
Misma Idempotency-Key, body diferente, dentro de 24h | Retorna 409 071-409-idempotency-mismatch. |
Misma Idempotency-Key, después de 24h | Tratado como un nuevo request. La key se reutiliza. |
Idempotency-Key distinta, mismo body | Crea un nuevo request. |
Mejores prácticas
Generar la key en el límite de la operación
Generarla una sola vez cuando se decide "voy a llamar a DVS para clasificar este documento". Reutilizarla en cualquier retry de esa misma llamada. No regenerarla por retry — eso anula el propósito.
UUID v4 es suficiente
No hay que complicarse — uuid.uuid4() o crypto.randomUUID() es suficiente. Evitar IDs secuenciales (predecibles).
Persistir la key junto con la operación
Almacenar la key en la base de datos junto con la operación que representa. En recuperación tras un crash, es posible reintentar con seguridad leyendo la key persistida.
Una key por operación, no por request
Si la aplicación envía 1000 classifications, generar 1000 keys. No compartir keys entre documentos distintos.
Condiciones de carrera
Si dos requests paralelos llegan a DVS con la misma Idempotency-Key, solo uno se procesa; el otro espera a que el primero termine (hasta 5 segundos) y retorna la misma response. Esto es seguro pero agrega latencia — es recomendable serializar los retries del lado del cliente.
Ejemplo: bucle de retry seguro
import uuid, time, httpx
def classify_with_retry(token, body, max_attempts=3):
idempotency_key = str(uuid.uuid4()) # UNA key por operación lógica
for attempt in range(max_attempts):
try:
return httpx.post(
"https://dvs-ingestion-api.sandbox.osigu.com/v1/classification-requests",
headers={
"Authorization": f"Bearer {token}",
"Idempotency-Key": idempotency_key, # reutilizada en cada retry
},
json=body,
timeout=30,
).raise_for_status().json()
except httpx.HTTPError:
if attempt == max_attempts - 1:
raise
time.sleep(2 ** attempt)