Estou desenvolvendo um software que lança vários clientes simultâneos para se conectar a um banco de dados PostgreSQL (12). Quando cada cliente é iniciado, a primeira coisa que ele faz ao se conectar ao PostgreSQL é executar o script de criação do esquema.
Este script foi escrito de forma idempotente - pelo menos em princípio - para que os vários clientes não tropecem em si mesmos. Em geral, isso funciona bem. No entanto, o PostgreSQL às vezes detecta deadlocks e falha de cliente(s) afetado(s). Examinando o registro, acredito que ocorram nessa sequência:
- Cliente A: Iniciar a transação de criação do esquema
- Cliente A: Concluir a transação de criação do esquema
- Cliente B: Iniciar a transação de criação do esquema
- Cliente A: Nova transação que usa esquema (selecione na visualização)
- Cliente A e B agora em deadlock
Os logs não estão 100% claros e não consigo reproduzir isso de forma determinística, mas parece ser o que está acontecendo: o cliente A está tentando a SELECT
partir de uma visão definida pelo esquema, mas está travando porque o cliente B está tentando recriar isso view ( CREATE OR REPLACE VIEW
) no script de esquema.
É possível garantir que o script de criação de esquema seja executado exclusivamente? Ou existe alguma outra solução (por exemplo, em vez de CREATE OR REPLACE VIEW
, eu apenas CREATE VIEW
uma vez que determinei que ainda não existe)?
Dos comentários à minha pergunta, o problema pode ser resumido, por @DanielVérité, como "DDL simultâneo é ruim; DDL e DML simultâneos são muito ruins". Eu não estava ciente disso - mesmo com o cuidado que tive para tornar o DDL idempotente - mas é bom saber. Então, isso me deixa com duas soluções amplas:
Não faça o DDL simultaneamente (por exemplo, tenha um único cliente instanciando o esquema).
Esta é provavelmente a solução "mais fácil" do ponto de vista do desenvolvimento de aplicativos - e eu vejo isso como meu plano de backup - mas tem consequências em como o aplicativo é arquitetado. Eu só quero fazer esse caminho se for preciso.
Use bloqueios consultivos, por sugestão de @a_horse_with_no_name.
Isso requer uma pequena modificação apenas na biblioteca de banco de dados do meu aplicativo, portanto, não há grandes alterações de arquitetura ou alterações no esquema. A desvantagem é que eu tenho que adquirir e liberar o bloqueio em todas as transações (para ter certeza de que já não está bloqueado pela seção DDL do código), o que necessariamente serializa todas as transações entre os clientes.
Estou experimentando a opção 2 e parece estar funcionando. Eu preciso fazer mais algumas tentativas para me convencer de que o impasse acabou e qual o impacto no desempenho da serialização (anecdoticamente, até agora, é insignificante). Volto com os resultados quando tiver...
Como você está tentando criar um esquema, também conhecido como bloqueios consultivos de comandos DDL, acho que não funcionará
A melhor solução que posso pensar é criar uma tabela que rastreie a criação do esquema ou consulte o pg_catalog diretamente e, em seguida, envolva essa chamada em um procedimento
com um procedimento você pode iniciar e confirmar transações
Aqui está o meu processo de pensamento, isso não foi testado
Em seguida basta chamar este procedimento