posts/IwxbMPLKOxGqHVCHSjxgHAlupXQC3ZVFFrjxhMnR.png

Idempotencia en Jobs de Laravel: evita duplicados y errores críticos

Aprende qué es la idempotencia en Jobs de Laravel, por qué es crítica en colas y procesos asíncronos, y cómo implementarla correctamente para evitar duplicados, inconsistencias y errores en producción.

Cuando trabajamos con Jobs y colas en Laravel, asumimos algo que en la práctica no siempre se cumple: que un Job se ejecutará una sola vez.

En entornos reales —Redis, Supervisor, Horizon, reintentos automáticos, timeouts o despliegues— un Job puede ejecutarse más de una vez, incluso cuando el código está “bien”.

Aquí es donde entra un concepto clave de arquitectura backend:

Idempotencia

Un Job idempotente es aquel que puede ejecutarse múltiples veces sin producir efectos secundarios no deseados.

En este artículo veremos:

  • Qué es la idempotencia aplicada a Jobs

  • Por qué es crítica en Laravel

  • Errores comunes

  • Estrategias prácticas para implementarla correctamente

¿Qué es idempotencia?

En términos simples:

Una operación es idempotente si ejecutarla una o varias veces produce el mismo resultado final.

Ejemplo conceptual:

  • No idempotente:
    Crear una orden, descontar stock y cobrar al cliente cada vez que corre el Job.

  • Idempotente:
    Verificar si la orden ya fue procesada antes de crearla o cobrarla.

En Jobs, la pregunta clave siempre es:

¿Qué pasa si este Job se ejecuta dos veces?

Si la respuesta es “se duplica información, se cobra de nuevo o se rompe algo”, no es idempotente.

¿Por qué los Jobs pueden ejecutarse más de una vez?

Aunque no lo parezca, es completamente normal que ocurra:

  • Reintentos automáticos (

    tries
    )

  • Timeouts de workers

  • Fallos de red o base de datos

  • Reinicio de Supervisor

  • Deploys mientras hay Jobs en proceso

  • Deadlocks

  • Horizon reencolando Jobs

Laravel garantiza al menos una ejecución, no exactamente una.

Errores comunes al no manejar idempotencia

Algunos ejemplos reales en producción:

  • Registros duplicados

  • Correos enviados múltiples veces

  • Cobros repetidos

  • Inventarios negativos

  • Logs inconsistentes

  • Depreciaciones o cálculos financieros duplicados

Todos estos problemas no se solucionan con try/catch.

Se solucionan diseñando Jobs idempotentes.

Estrategias para hacer Jobs idempotentes en Laravel

1. Llaves únicas en base de datos (la base de todo)

Si un Job crea un registro, la base de datos debe protegerte.

$table->unique(['order_id', 'type']);

Y luego:

Model::firstOrCreate([
    'order_id' => $orderId,
    'type' => 'payment',
]);

Si el Job corre dos veces, el segundo no hace nada.

2. Flags de estado antes de ejecutar lógica crítica

Antes de ejecutar la lógica principal:

if ($order->status === 'processed') {
    return;
}

Después del proceso:

$order->update(['status' => 'processed']);

Esto aplica especialmente en:

  • Facturación

  • Procesos financieros

  • Asignaciones

  • Depreciaciones

  • Integraciones externas

3. Locks con Cache (Redis recomendado)

Laravel permite locks distribuidos:

Cache::lock("job:process-order:{$orderId}", 60)->block(5, function () {
    // lógica crítica
});

Esto evita que dos workers ejecuten el mismo Job al mismo tiempo.

Ideal para:

  • Procesos largos

  • Jobs pesados

  • Integraciones externas

4. Uso correcto de
ShouldBeUnique

Laravel ofrece:

class ProcessOrder implements ShouldQueue, ShouldBeUnique
{
    public function uniqueId()
    {
        return $this->orderId;
    }
}

Esto evita que el Job se encole dos veces, pero ojo:

NO reemplaza la idempotencia interna

Si el Job falla a mitad del proceso y se reintenta, el código aún debe ser seguro.

5. Registrar ejecuciones (auditoría)

En procesos críticos, registra ejecuciones:

JobExecution::create([
    'job' => self::class,
    'reference_id' => $orderId,
    'executed_at' => now(),
]);

Antes de ejecutar:

if (JobExecution::where('job', self::class)
    ->where('reference_id', $orderId)
    ->exists()) {
    return;
}

Muy útil para:

  • Auditoría

  • Depuración

  • Procesos financieros o legales

Regla de oro para Jobs en Laravel

Todo Job debe asumirse como si pudiera ejecutarse más de una vez.

Si tu Job:

  • Inserta datos

  • Modifica estados

  • Llama APIs

  • Cobra dinero

  • Mueve inventario

Debe ser idempotente. Siempre.

Conclusión

La idempotencia no es un “extra” ni una optimización avanzada.
Es un requisito de arquitectura cuando trabajas con colas y procesos asíncronos.

Laravel te da herramientas:

  • Base de datos

  • Locks

  • Unique Jobs

  • Estados

Pero la responsabilidad final es del diseño del Job.

Un Job idempotente:

  • Es seguro

  • Es predecible

  • Escala mejor

  • No genera errores silenciosos en producción

Y, sobre todo, duerme tranquilo al desarrollador.

Comparte esta publicación

0 comentarios

Únete a la conversación y comparte tu experiencia.

Dejar un comentario

Comparte dudas, propuestas o mejoras para la comunidad.