posts/uDIplnyOR4L9jVwagZYDRQGUDfl2fjx49EggcJUU.png

Transacciones en procesos batch con Laravel: evita datos corruptos

Aprende cómo usar transacciones de base de datos en procesos batch con Laravel para garantizar consistencia, evitar estados intermedios y proteger operaciones masivas en Jobs y colas.

En Laravel es muy común ejecutar procesos batch:
actualizaciones masivas, importaciones, cálculos financieros, depreciaciones, conciliaciones, cierres mensuales o sincronizaciones externas.

El problema es que muchos de estos procesos tocan múltiples registros, y si algo falla a mitad del camino, el sistema puede quedar en un estado inconsistente.

Aquí es donde las transacciones de base de datos dejan de ser una opción y se convierten en una obligación arquitectónica.

¿Qué es un proceso batch?

Un proceso batch es cualquier operación que:

  • Afecta muchos registros

  • Se ejecuta en segundo plano

  • Puede tardar segundos o minutos

  • Normalmente corre en Jobs o comandos

Ejemplos reales:

  • Importar miles de filas desde un CSV

  • Actualizar estados de órdenes

  • Calcular depreciaciones mensuales

  • Asignar inventario

  • Recalcular saldos o métricas

El problema de no usar transacciones

Supongamos este escenario:

  1. Se procesan 500 registros

  2. En el registro 320 ocurre un error

  3. Los primeros 319 ya quedaron guardados

  4. El proceso se corta

Resultado:

  • Datos incompletos

  • Estados mezclados

  • Dificultad para reintentar

  • Bugs silenciosos en producción

El error no es el Job, es la falta de control transaccional.

Transacciones en Laravel: lo básico

Laravel usa transacciones vía el facade

DB
:

DB::transaction(function () {
    // operaciones de base de datos
});

Si ocurre una excepción:

  • Todo se revierte automáticamente

  • No quedan cambios parciales

Esto es atómico: o todo pasa, o nada pasa.

Transacciones en procesos batch: enfoque correcto

❌ Error común

Meter todo el batch en una sola transacción gigante:

DB::transaction(function () {
    foreach ($items as $item) {
        $this->process($item);
    }
});

Problemas:

  • Locks largos

  • Alto consumo de memoria

  • Riesgo de timeouts

  • Deadlocks

✅ Enfoque recomendado: transacciones por bloque

Procesar en chunks, con transacción por bloque:

Model::chunkById(100, function ($items) {
    DB::transaction(function () use ($items) {
        foreach ($items as $item) {
            $this->process($item);
        }
    });
});

Ventajas:

  • Menos locks

  • Mejor performance

  • Recuperación controlada

  • Escalable

Transacciones dentro de Jobs

En Jobs es aún más importante, porque pueden:

  • Reintentarse

  • Ejecutarse más de una vez

  • Fallar por timeout

Ejemplo típico:

public function handle()
{
    DB::transaction(function () {
        $this->updateRecords();
        $this->registerLogs();
        $this->updateStatus();
    });
}

Si algo falla:

  • El Job lanza excepción

  • Laravel puede reintentarlo

  • No quedan datos corruptos

¿Qué y qué no debe ir dentro de una transacción?

✅ Debe ir dentro

  • Inserts / updates

  • Cambios de estado

  • Relaciones pivot

  • Registros financieros

  • Logs críticos

❌ No debe ir dentro

  • Llamadas a APIs externas

  • Envío de correos

  • Procesamiento de archivos

  • Sleeps o delays

Regla clara:

La transacción solo debe cubrir operaciones de base de datos.

Manejo de errores en batch con transacciones

Ejemplo controlado:

try {
    DB::transaction(function () use ($item) {
        $this->process($item);
    });
} catch (\Throwable $e) {
    Log::error('Batch error', [
        'item_id' => $item->id,
        'error' => $e->getMessage(),
    ]);
}

Esto permite:

  • Continuar el batch

  • Registrar fallos

  • Reintentos selectivos

Transacciones + Idempotencia: combinación obligatoria

Una transacción no reemplaza la idempotencia.

Lo correcto es:

  • Idempotencia → evita duplicados

  • Transacción → evita estados parciales

Ambas trabajan juntas.

Caso real: cierres mensuales / depreciaciones

Escenario típico:

  • Calcular cargos

  • Insertar movimientos

  • Actualizar saldos

  • Marcar mes como cerrado

Todo eso debe vivir dentro de una transacción.

Si falla:

  • No hay cierres a medias

  • El Job puede reintentarse

  • La contabilidad queda intacta

Reglas prácticas para procesos batch en Laravel

  1. Nunca asumas que todo saldrá bien

  2. Usa

    chunkById
    , no
    get()

  3. Transacciones pequeñas y controladas

  4. Nunca mezcles APIs externas dentro

  5. Diseña pensando en reintentos

  6. Loggea errores, no los ocultes

Conclusión

Las transacciones en procesos batch no son una optimización, son una garantía de integridad.

En Laravel:

  • Los Jobs fallan

  • Los workers se reinician

  • Los procesos se repiten

La única defensa real contra datos corruptos es:

  • Buen diseño

  • Transacciones bien aplicadas

  • Procesos predecibles

Un batch sin transacciones es una bomba de tiempo.

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.