Yeda AI Tips · #086

English

Prueba tus trabajos en segundo plano: idempotencia y reintentos

Tu trabajo en segundo plano se va a ejecutar dos veces. Si no lo probaste para ese caso, a alguien le cobran doble. No es pesimismo — es el contrato de entrega. La documentación de Sidekiq lo dice sin rodeos: "Sidekiq will execute your job at least once, not exactly once." Las colas estándar de Amazon SQS prometen lo mismo: entrega al-menos-una-vez, y "more than one copy of a message might be delivered". Los duplicados no son un bug de tu cola. Son la especificación.

Por qué toda cola re-ejecuta tu trabajo

Un sistema de colas solo puede garantizar que un trabajo no se perdió reenviándolo cada vez que no está seguro de que terminó. Y los casos de incertidumbre están en todas partes:

Ejecutar dos veces significa cobrar dos veces, enviar dos correos, despachar dos veces — salvo que la segunda ejecución sea inofensiva. Esa propiedad tiene nombre: idempotencia. La guía de tareas de Celery define la meta con exactitud: "the function won't cause unintended effects even if called multiple times with the same arguments".

Las dos pruebas que todo trabajo necesita

PruebaQué verificasQué detecta
IdempotenciaEjecutas el trabajo dos veces con los mismos argumentos; el estado final es idéntico a ejecutarlo una vez — un cobro, un correo, una filaEntregas duplicadas, doble encolado por doble clic, fallos entre el trabajo y la confirmación
Recuperación tras reintentoHaces fallar el trabajo a mitad de camino (lanzas una excepción después del primer efecto secundario) y lo re-ejecutas; termina sin corromper el estadoTrabajo a medias, tormentas de reintentos, trabajos "veneno"

En código, la prueba de idempotencia es casi aburrida — y ese es justamente el punto:

def test_charge_job_is_idempotent(db, payment_gateway):
    charge_order(order_id=42)
    charge_order(order_id=42)          # duplicate delivery

    assert payment_gateway.charge_count(order_id=42) == 1
    assert db.order(42).status == "paid"

La prueba de reintento inyecta el fallo que la cola tarde o temprano va a inyectar por ti:

def test_charge_job_recovers_after_crash(db, payment_gateway):
    with payment_gateway.fail_after_charge():   # crash between work and ack
        with pytest.raises(GatewayTimeout):
            charge_order(order_id=42)

    charge_order(order_id=42)                   # the retry
    assert payment_gateway.charge_count(order_id=42) == 1

Si alguna de las dos pruebas no puede pasar, el arreglo suele ser uno de tres patrones: una clave única en el efecto secundario (por ejemplo, una idempotency key enviada al proveedor de pagos), una verificación de estado antes de actuar ("omitir si ya está pagado"), o hacer el trabajo completo transaccional para que un fallo revierta a un estado limpio y re-ejecutable — la página de buenas prácticas de Sidekiq menciona ese par directamente: haz los trabajos idempotentes y transaccionales.

Trucos avanzados

Ahora una entrega duplicada es comportamiento seguro, no un ticket de soporte.

Recursos

Read this article in English

¿Construyendo una función con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Habla con nosotros · Lee el blog