2
respostas

[Dúvida] PHP e Laravel - Tratando solicitações concorrentes (mesmo registro)

Olá, Pensando na seguinte solicitação Tenho várias pessoas disputando o mesmo registro no banco de dados Como uma API ela começa a execução em X tempo e devolve a resposta e Y tempo Pode duas pessoa querendo o prêmio XYZ (que está no banco de dados) no mesmo momento, ou com um delay por exemplo de 50ms terem a resposta SIM eles pegaram o mesmo prêmio, só que vai ver o ganhador na verdade foi o 2° a pegar no mesmo momento, pois o UPDATE dele executou 2° O 3° que executou fora dessa concorrência já vai ter a resposta NÃO.

Pensei em tratar com filas Vi que o laravel tem o queueshttps://laravel.com/docs/10.x/queues A filas não seria para todas as chamadas, pois posso atrasar meu servidor "atoa"

Seria por prêmios as filas

Exemplo no meu banco tenho 5 prêmios Se 50 pessoas clicarem no mesmo tempo não vou ter 1 fila para atender os 50 clicks Vou ter 5 filas no máximo Se 10 tentou o prêmio 1 >>> 10 na mesma fila Se 15 tentou o prêmio 2 >>> 15 na mesma fila Se 05 tentou o prêmio 3 >>> 05 na mesma fila Se 19 tentou o prêmio 4 >>> 19 na mesma fila Se 01 tentou o prêmio 5 >>> 01 na mesma fila

O 1° dos 10 O 1° dos 15 O 1° dos 05 O 1° dos 19 O 1° dos 01 Vão ser respondidos no mesmo momento -> são filas paralelas

A fila que mais vai demorar é a de 19 já que tem mais pessoas mas não vai responder para o 2° de qualquer tentativa que eles também ganhou aquele prêmio

A fila é a melhor opção no laravel para isso?

Vi também que tem o horizon, mas ainda não fui a fundo

Estou aberto a sugestões Obrigado desde já

2 respostas

Olá, Ewerton!

Pelo que entendi, você está lidando com um problema de concorrência e está considerando usar filas para resolver isso. Sua ideia de usar filas para cada prêmio é bastante interessante e pode realmente ajudar a lidar com a concorrência.

O Laravel Queues é uma excelente ferramenta para lidar com tarefas assíncronas e pode ser útil para o seu caso. No entanto, é importante lembrar que as filas não resolvem diretamente problemas de concorrência em um registro específico. Elas simplesmente permitem que você lide com solicitações de maneira assíncrona.

Para o seu caso, uma abordagem possível seria usar transações de banco de dados juntamente com filas. Quando uma pessoa tenta pegar um prêmio, você pode criar uma transação de banco de dados. Dentro dessa transação, você verifica se o prêmio ainda está disponível. Se estiver, você atribui o prêmio à pessoa e conclui a transação. Se a transação falhar devido a um conflito de concorrência (ou seja, outra pessoa pegou o prêmio ao mesmo tempo), você pode tentar novamente após um pequeno atraso.

Aqui está um exemplo de como isso pode ser feito:

DB::transaction(function () use ($prize, $user) {
    // Tente pegar o prêmio
    $prize = Prize::find($prize->id);
    if ($prize->isAvailable()) {
        $prize->user_id = $user->id;
        $prize->save();
    } else {
        // O prêmio não está mais disponível
        throw new Exception('Prêmio não disponível');
    }
}, 5); // O número 5 aqui é o número de tentativas que o Laravel fará antes de desistir

No exemplo acima, se duas pessoas tentarem pegar o prêmio ao mesmo tempo, uma delas terá sucesso e a outra receberá uma exceção. Você pode então lidar com essa exceção e informar ao usuário que o prêmio já foi pego.

Quanto ao Horizon, ele é uma ferramenta para gerenciar filas do Laravel e pode ser útil se você tiver muitas tarefas em fila e quiser um painel para gerenciá-las.

Espero ter ajudado e bons estudos!

Se tudo tiver em uma fila, as tarefas serão executadas dentro de uma fila de forma sequencial (1 único processo por fila), então não teria problema, correto?

Mas fora da fila só usar o transaction não adianta, certo? Porque duas pessoas podem entrar no mesmo momento na transação ainda

o transaction seria igual o beginTransaction do PDO ele só garante se alguma SQL falhar durante a transação as outras também falhe (não seja comitada - commit)