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étodo | Se aplica a | Pros | Contras |
| Trait | Modelos específicos | Control preciso en cada modelo | Debe agregarse manualmente |
| Global Scope | Todos los modelos Eloquent | Se aplica automáticamente | Puede afectar el rendimiento |
| Query Builder Wrapper | Consultas manuales con DB::query() | Flexible y fácil de usar | No 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.