Continuamos a publicar vídeos e transcrições dos melhores relatórios da conferência
PGConf.Russia 2019 .
Na primeira parte da palestra de Ivan Frolkov, tratou -se de nomes inconsistentes, restrições, sobre onde é melhor concentrar a lógica - no banco de dados ou no aplicativo. Nesta parte, você encontrará o tratamento de erros de análise, acesso simultâneo, operações não canceláveis, CTE e JSON.

Vou contar essa história. Nosso cliente diz: “O banco de dados está funcionando lentamente, e nosso aplicativo está comprometido em atender a população. Temos medo de que eles nos levem aqui para garfos. Aconteceu que eles tinham muitos processos
ociosos no estado da
transação . O aplicativo iniciou a transação, não faz nada, mas a transação não é concluída. Se você interagir com alguns serviços externos, então, em princípio, esta é uma situação normal. Outra coisa é que, se o seu estado
ocioso da transação durar muito tempo (já é suspeito por mais de um minuto), isso é ruim porque o PostgreSQL realmente não gosta de transações longas: o VACUUM não poderá limpar todas as linhas que pôde ver e ficar pendurado por um longo tempo. transação efetivamente bloqueia VACUUM. As tabelas começam a aumentar, os índices estão se tornando cada vez menos eficazes.

Nesse caso, as pessoas não escreveram solicitações corretamente e receberam produtos cartesianos - essas solicitações foram concluídas por vários dias. Bem, o usuário, ele pressionará o botão, aguardará o resultado e, se não houver resultado, pressione o botão novamente.
Mas isso não explicou por que eles têm tantos processos
ociosos na transação . E eles apareceram na seguinte situação: o aplicativo rastreia o banco de dados, inicia a transação, rastreia algum serviço externo, recebe um erro e, em seguida, tudo desmorona, imprimimos no log de
rastreamento da
pilha e nos acalmamos. A conexão permanece abandonada, suspensa e interferindo.
O que fazer sobre isso? Primeiro, você deve sempre lidar com erros. Se um erro chegar até você, não o ignore. É bom que o PostgreSQL perca a conexão: reverte a transação, nós sobrevivemos. Por isso vou parar. Bem, se houver um código que não tenha tempo para editar, ainda temos o
máximo de ociosidade na transação - você pode colocá-lo, e ele simplesmente eliminará as transações inativas.

Um caso típico de tratamento de erros é: EXCEÇÃO QUANDO OUTROS QUE NULOS. Uma vez discutimos com um colega sobre terminologia. Eu disse que se traduz como "queime tudo com uma chama azul" e ele quer dizer "tudo foi desperdiçado". Se algo ruim aconteceu conosco, mesmo que tudo tenha repreendido o registro, ainda é melhor do que um completo silêncio - como aqui.

Se você não sabe o que fazer com o erro, não o intercepte. Uma prática muito comum: eles pegaram um erro, registraram e continuaram como se nada tivesse acontecido. Se, novamente, você se envolver em transações monetárias e tiver um erro que ignorou, os resultados poderão ser imprevisíveis. Nos anos 90, eles poderiam, por exemplo, ser levados para a floresta no tronco. Agora os tempos se tornaram mais suaves, mas também não muito agradáveis.

Se fizermos a operação no cliente, geralmente retornamos o valor: tudo correu com êxito ou sem êxito. E nós processamos cada erro. Vi como as pessoas escreveram especialmente o código
plpgsql , onde detectaram um erro, escreveram no log que, dizem, sim, houve um erro e, muito rude, inseriram o texto da mensagem. Mas SQLSTATE não retornou. Isso sempre é feito; portanto, se eles esqueceram de verificar alguma coisa, começaram a ter problemas.
Todo mundo, por algum motivo, tem medo de exceções - tanto no
plpgsql quanto em outros idiomas. E se você não inventa algo próprio, mas usa os recursos padrão da linguagem, tudo geralmente funciona bem. Especialmente esse problema geralmente ocorre quando a conexão cai. Ele caiu, o
ocioso no processo de
transação , o banco de dados está sendo preenchido, o desempenho está caindo. A propósito, essa transação ainda pode deixar bloqueios, mas por algum motivo isso não é tão comum. Portanto, adicione
finalmente erros ao código de processamento e limpe a conexão e devolva-a ao servidor.

Além disso, se você tiver restrições bem definidas, poderá lançar uma exceção não no banco de dados, mas no aplicativo durante o processamento do erro. Na
primavera, há uma
tradução de exceção ,
em php , respectivamente,
set_exception_handler . Preste atenção às ferramentas fornecidas pela estrutura, elas apareceram lá por um motivo.
Então: não pegue o erro com o qual você não sabe o que fazer; nomeie os erros com cuidado e precisão; classificar erros.

Pessoalmente, classifico por esses critérios: a operação pode ser repetida (por exemplo, tivemos um impasse); a operação não pode ser repetida, ela já foi concluída; a operação não pode ser executada em princípio.
Paradoxalmente, do ponto de vista do aplicativo, as situações em que ocorre um impasse, quando a conexão é perdida e quando o dinheiro está acabando para pagar são as mesmas situações: o manipulador de erros tentará executar a operação novamente depois de um tempo.

Por outro lado, o que eles escrevem no aplicativo, em geral, não é da minha conta: estou envolvido na base. Apenas peço que você lide com os erros com cuidado, caso contrário: ocioso na transação, linhas bloqueadas, bancos de dados inchados e assim por diante.
A maioria dos desenvolvedores acredita que trabalha apenas com o banco de dados e seu aplicativo executa operações estritamente sequenciais. E isso é uma vantagem para todos os DBMSs relacionais porque, por incrível que pareça, tudo funciona, como regra, muito bem, mesmo com o nível de isolamento padrão READ COMMITTED e não SERIALIZABLE. Ao mesmo tempo, as situações acontecem quando as atualizações são perdidas: uma carrega o formulário, a outra carrega o mesmo formulário, uma escreveu e salvou, o outro salvou o antigo - as alterações foram apagadas. O primeiro veio a jurar: "como escrevi tanto, e tudo está perdido".

Da minha experiência: uma vez por semana às sextas-feiras, dois gerentes faziam pagamentos. Eles deveriam
estavam mudando a cada duas vezes, mas, no entanto, uma vez subiram ao mesmo tempo e fizeram dois pagamentos por pessoa. Se você tiver pelo menos alguma chance de um erro de acesso competitivo, isso acontecerá mais cedo ou mais tarde. A questão é quando.
Além disso, chamo sua atenção para as limitações. Eu tenho visto repetidamente como eles tentaram fornecer exclusividade com gatilhos. Você não fornecerá exclusividade na tabela com gatilhos. Você precisará bloquear a tabela inteira ou fazer outros gestos complexos. Você tropeçará nisso mais cedo ou mais tarde.

Algumas vezes me deparei com uma coisa completamente assustadora: um serviço da web externo é chamado a partir do banco de dados. Houve algumas operações que alteram as entidades externas. Isso é ruim porque uma transação pode ser revertida no banco de dados, mas as operações no serviço remoto não serão rejeitadas.
Um ponto ainda mais sutil é o impasse. Vamos imaginar: processamos uma transação, chamamos um serviço da Web externo, mudamos alguma coisa, depois disso obtemos um impasse e revertemos, depois tentamos executar a operação novamente, chamamos novamente, em boas circunstâncias, o impasse ocorre novamente, novamente reverter - pode
acontecer muitas vezes (me deparei com algumas centenas de repetições). E agora você processa esses impasses mais ou menos corretamente, repita as operações e repentinamente descobre que está pagando uma quantia dupla a alguém dentro de dois meses.

Encontrei-me com serviços de pagamento que tinham uma API ruim: "pague tal e tal quantia a tal e tal usuário"; a função retorna o resultado - pago / não pago. Em primeiro lugar, há um problema no caso de repetição e, em segundo lugar, não está claro o que fazer se a conexão for interrompida. Por alguma razão, muito poucas pessoas também se preocupam com esse assunto.

Um exemplo no slide: essa operação deve ser realizada em duas etapas: como se fosse um aviso - "faremos algo agora"; a operação em si.

Se interrompermos repentinamente - você nunca sabe, desligamos a energia -, podemos executar novamente a operação. Se morrermos no segundo estágio, no mundo inteiro, na segunda vez, não o faremos, e isso pode ser desmontado manualmente. De fato, a grande maioria dessas operações normalmente funciona pela primeira vez, mas essas medidas não são fabricações teóricas. Tudo pode funcionar normalmente por meses e, de repente, o administrador começa a ficar mais inteligente com a rede, o serviço começa a piscar ativamente - e os problemas começam.

Existem 4 tipos de operações não canceláveis no slide. O último são operações não idempotentes. Este é um caso muito triste. No começo, falei sobre um camarada que fazia tudo o que era necessário para garantir a idempotência de suas operações.

Na conferência, as pessoas falarão sobre expressões comuns de tabela, sobre como é bom. Infelizmente, as CTEs do PostgreSQL não são gratuitas: elas requerem work_mem para elas mesmas. Se você tem uma amostra pequena, então, em geral, está tudo bem. E se você de repente tiver grande, seus problemas começam. As pessoas costumam usar o CTE como um tipo de minivisualização - para que você possa estruturar o aplicativo de alguma forma. CTE é muito procurado.


Você pode fazer visualizações temporárias, mas, infelizmente, cada uma delas adota uma linha em pg_class e, se for usada de maneira muito ativa, pode haver problemas com o aumento de diretório.
Nesse caso, você pode aconselhar fazer uma exibição parametrizada ou formar dinamicamente uma consulta, mas, infelizmente, no PostgreSQL por dentro, isso não é muito legal.

Geralmente, o JSON é mencionado em tons excelentes, mas há uma tendência no aplicativo no JSON de fazer push de qualquer coisa. Em princípio, tudo funciona bem. Por outro lado, os dados são recuperados do JSON, embora rapidamente, mas não tão rápido quanto nas colunas. Pior ainda, se você tiver um JSON grande, e ele for emitido no TOAST. Para obter o JSON de lá, você precisa buscá-lo no TOAST.
Se todas as colunas estiverem em JSON, um índice funcional é construído sobre elas, você ainda precisará tirá-lo de lá. Fica ainda pior com um grande volume, quando o banco de dados é grande, quando você faz uma
verificação de índice de bitmap . Depois, temos links não para strings, mas para toda a página e, para entender o que levar da página, o PostgreSQL fará o
Recheck , ou seja, ele levanta uma linha do TOAST e verifica se esse valor está lá ou não, e, portanto, já pula ou não pula. Se com pequenas colunas isso funcionar bem, então com JSON este é um grande problema. Não há necessidade de se deixar levar pelos JSONs.
- Como verificar quando vários usuários trabalham com uma string? Que opções existem?- Primeiro, você pode subtrair os valores de todas as colunas e verifique se elas não foram alteradas antes de mostrar a linha no formulário. A segunda opção, mais conveniente: calcular o hash
colunas, especialmente porque as colunas podem ser grandes e grossas. E o hash não é tão grande.
- Você diz que as restrições devem ser chamadas de bons nomes para que o usuário possa entender o que está acontecendo. Mas há um limite de 60 caracteres por nome de restrição. Isso geralmente não é suficiente. Como lidar com isso?- Eu acho que lutar por autocontrole. No PostgreSQL, esse é um tipo especial de comprimento 64. Em princípio, você pode recompilar para um comprimento maior, mas isso não é muito bom.
- No relatório, você nos intrigou com o fato de que precisamos fazer algo com os arquivos. Qual mecanismo é considerado o mais correto para arquivamento desatualizado?- Como eu disse no começo, com a devida diligência, tudo funciona. Qual método é mais conveniente para você, então use-o.
Momento: a parte 2 do relatório começa às 25:16- Existe um certo procedimento que vários usuários chamam em paralelo. Como limitar a execução paralela deste procedimento, ou seja, criar todos os
usuários na fila para que, até que um procedimento seja concluído, o próximo não possa ser iniciado?- Precisamente o procedimento? Ou é transação suficiente?
- É o procedimento que é chamado em alguma transação.- Você pode colocar um cadeado no objeto. Seria difícil se você tivesse uma condição, digamos, não mais que 3 ao mesmo tempo. Mas isso é realizável. Eu geralmente uso bloqueios transacionais, mas os não transacionais também são possíveis.
- Eu ainda gostaria de retornar novamente aos dados de arquivo. Você falou sobre
arquivar opções de armazenamento para que os dados do aplicativo também estejam disponíveis. Ocorreu-me simplesmente criar um banco de dados de arquivo separado. Que outras opções existem?- Sim, você pode criar um banco de dados de arquivo morto. Você pode escrever uma função e envolvê-la em uma exibição. Em uma função, você pode fazer o que for preciso: você pode acessar o banco de dados de arquivamento, pegar alguns arquivos do disco, acessar um serviço da Web externo, combinar tudo isso e gerar dados aleatórios - escolha limitado apenas pela imaginação.
- Para a pergunta sobre dados de arquivamento: você pode usar partições - novos chips da 11ª versão, quando fazemos a tabela inteira particionada, e então apenas desanexamos a partição e a deixamos como um arquivo morto. Também pode ser acessado."Claro, por que não." Dou lugar ao próximo orador.