Tenho muitos lugares em meu aplicativo onde os usuários estão fazendo algo que não deveria ser simultâneo em bases por usuário e namespace. Então isso significa que:
- Dois usuários podem pressionar o mesmo botão ao mesmo tempo
- Um usuário não pode pressionar o mesmo botão uma segunda vez até que o primeiro toque seja concluído
- Um usuário pode pressionar botões diferentes ao mesmo tempo
Quais soluções eu tentei:
- Bloqueio consultivo com função hash no usuário uuid - tem colisões e esse bloqueio consultivo, mas o bloqueio bloqueia todas as interações do usuário - então ele não pode pressionar dois botões ao mesmo tempo
INSERT
&SELECT FOR UPDATE
- bom, mas preciso preencher previamente a tabela com linha "pendente" para poluir o esquema da tabela com colunas desnecessárias.- Deseja implementar: locktable. Então, o que quero dizer é criar uma tabela com bloqueios do tipo consultivo. Terá duas colunas
namespace
eid
- chave primária composta. Cada vez que o usuário pressionar o botão nº 1, haveráINSERT
&FOR UPDATE
para namespace=button#1
e id={userid}
.
Eu sinto que existem algumas pedras escondidas nesta (3ª) abordagem - alguém sabe disso? Você poderia gentilmente listar esses problemas?
Tenho certeza de que será pior em termos de desempenho do que os bloqueios de aconselhamento, mas será muito pior? Além disso, não consegui encontrar implementações dessa abordagem - parece que tive uma ideia estúpida que ninguém usa por algum motivo importante.
Confira esta conversa interessante em 2009 sobre este problema: https://www.postgresql.org/message-id/ [email protegido]
Por INSERT
& FOR UPDATE
quero dizer:
INSERT INTO adv_locks VALUES ('btn1', '123') ON CONFLICT ON CONSTRAINT adv_locks_pk DO NOTHING;
SELECT 1 FROM adv_locks WHERE namespace='btn1' and id='123' FOR UPDATE;
Dessa forma sempre haverá uma linha em uma tabela com esta tabela que pode ser bloqueada. (Ainda não me importo muito com o tamanho da mesa)
Entendo que uma ordem dessa pode ser quebrada (quero dizer, a solicitação nº 1 cria uma linha e a solicitação nº 2 seleciona a linha mais rapidamente), mas não é um problema para mim.
A ideia de sincronizar threads de aplicativos por meio de bloqueios de linha regulares em uma tabela especial é ruim. A razão é que você precisa manter uma transação de banco de dados aberta pelo tempo que quiser manter o bloqueio, o que pode demorar bastante, visto que estamos falando de interações do usuário. Transações de longa execução são ruins, pois impedem o progresso do importante trabalho de limpeza do
VACUUM
, e você acabará com tabelas inchadas e talvez problemas de encapsulamento de ID de transação.Se você usar bloqueios consultivos, que não exigem que você mantenha uma transação aberta, sua tabela de bloqueios poderá evitar esse problema. Mas os bloqueios de aconselhamento estão vinculados a sessões de banco de dados, e um thread de aplicativo precisaria manter uma sessão de banco de dados enquanto espera que o usuário termine de pressionar o botão. Isso causará um problema com o pool de conexões, e você desejará ter um pool de conexões no modo de transação se quiser que seu aplicativo tenha um bom desempenho.
Você pode pensar que minhas preocupações são ridículas se o intervalo de tempo para a fechadura for tão curto quanto o apertar de um botão. É verdade que normalmente isso pode levar pouco tempo, mas você deve imaginar problemas como uma falha do aplicativo ou um problema de rede entre o pressionamento e a liberação de um botão.
Eu escolheria uma solução que insere ou atualiza uma linha em uma tabela sempre que um botão é pressionado e liberado. Se você usar um carimbo de data/hora, também poderá usá-lo para expirar um "bloqueio". se você usar atualizações HOT , isso pode funcionar bem.