Laravel puedes hacer que los intentos de inserción o consulta se repitan automáticamente en caso de fallos. Hay varias formas de hacerlo:

1. Usando el método retry() de Laravel

Laravel proporciona el helper retry() que permite reintentar una operación varias veces antes de fallar definitivamente.

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Exception;

$result = retry(5, function () {
    return DB::table('users')->where('email', 'test@example.com')->first();
}, 100);

En este ejemplo:

  • Se intentará ejecutar la consulta hasta 5 veces.
  • Habrá una pausa de 100 milisegundos entre cada intento.
  • Si la consulta se ejecuta con éxito, devolverá el resultado.
  • Si sigue fallando después de 5 intentos, Laravel lanzará una excepción.

2. Manejo de Transacciones con DB::transaction()

Si el problema ocurre en una inserción o actualización, usar transacciones con reintentos puede ayudar.

use Illuminate\Support\Facades\DB;
use Exception;

retry(3, function () {
    DB::transaction(function () {
        DB::table('users')->insert([
            'name' => 'John Doe',
            'email' => 'johndoe@example.com',
        ]);
    });
}, 200);
  • Laravel intentará hacer la inserción hasta 3 veces antes de fallar.
  • Habrá una pausa de 200 milisegundos entre cada intento.

3. Reintentar en una Cola (Queue)

Si el proceso no es crítico en tiempo real, podrías encolar la tarea y permitir que Laravel la reintente automáticamente.

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class InsertUserJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $tries = 5; // Número de intentos
    public $backoff = 100; // Milisegundos entre intentos

    protected $data;

    public function __construct($data)
    {
        $this->data = $data;
    }

    public function handle()
    {
        DB::table('users')->insert($this->data);
    }
}

Para encolarlo:

dispatch(new InsertUserJob(['name' => 'John Doe', 'email' => 'johndoe@example.com']));

Si el problema es que el servidor se congela o muere, puede ser que:

  • Tu base de datos esté sobrecargada.
  • Haya bloqueos en las tablas (deadlocks).
  • La consulta sea ineficiente.

¿Se puede aplicar a todas las querys de eloquent ?

Sí, puedes aplicar reintentos a todas las consultas de Eloquent utilizando un enfoque global. Aquí hay algunas maneras de hacerlo:


1. Usar un Trait Global para Reintentar Queries

Puedes crear un trait para aplicar retry() a todas las consultas Eloquent:

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

trait RetriesQueries
{
    public static function retryQuery($callback, $attempts = 3, $sleep = 100)
    {
        return retry($attempts, function () use ($callback) {
            return $callback();
        }, $sleep);
    }
}

Luego, úsalo en cualquier modelo:

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use RetriesQueries;
}

// Para consultar:
$user = User::retryQuery(fn() => User::find(1));

// Para insertar:
$newUser = User::retryQuery(fn() => User::create(['name' => 'John Doe', 'email' => 'john@example.com']));

2. Interceptar Queries con un Global Scope

Si quieres que TODAS las queries tengan reintentos automáticamente, puedes usar un Global Scope en un ServiceProvider:

a) Crear un GlobalScope:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\DB;

class RetryScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        // Reintentar todas las consultas del modelo
        $builder->getQuery()->beforeQuery(function ($query) {
            return retry(3, function () use ($query) {
                return $query;
            }, 100);
        });
    }
}

b) Aplicarlo a todos los modelos en AppServiceProvider:

use Illuminate\Support\ServiceProvider;
use App\Scopes\RetryScope;
use Illuminate\Database\Eloquent\Builder;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Builder::macro('withRetry', function ($attempts = 3, $sleep = 100) {
            return retry($attempts, function () {
                return $this;
            }, $sleep);
        });

        // Aplicar el Scope globalmente
        Model::addGlobalScope(new RetryScope());
    }
}

Este método automáticamente aplicará reintentos a todas las consultas Eloquent.


3. Middleware para Consultas en el Query Builder

Si trabajas con el Query Builder (DB::query()), puedes envolver las consultas en una función de reintento:

use Illuminate\Support\Facades\DB;

function safeQuery($callback, $attempts = 3, $sleep = 100)
{
    return retry($attempts, function () use ($callback) {
        return $callback();
    }, $sleep);
}

// Ejemplo de uso:
$user = safeQuery(fn() => DB::table('users')->where('id', 1)->first());

¿Cuál opción elegir?

MétodoSe aplica aProsContras
TraitModelos específicosControl preciso en cada modeloDebe agregarse manualmente
Global ScopeTodos los modelos EloquentSe aplica automáticamentePuede afectar el rendimiento
Query Builder WrapperConsultas manuales con DB::query()Flexible y fácil de usarNo se aplica a Eloquent automáticamente

Si tu problema es global, te recomiendo la solución con Global Scope o el Trait. Si solo necesitas reintentos en algunas partes, usar safeQuery() o retryQuery() es más seguro.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *