10 coisas que odeio no PostgreSqL

Nos últimos anos, o caso de amor da comunidade de desenvolvimento de software com o popular banco de dados relacional de código aberto atingiu um pouco de febre. Este tópico do Hacker News que cobre uma peça intitulada “PostgreSQL é o melhor bancode dados do mundo”, estourando nas costuras com sicophants bajulando elogios incondicionales, é um exemplo perfeito desse fenômeno.

Embora grande parte desse elogio seja certamente merecido, a falta de dissidência significativa me deixou um pouco incomodado. Nenhum software é perfeito, então exatamente quais são as imperfeições do PostgreSQL?

Estou com oPostgreSQL em produção desde 2003 com implantações que vão de pequenos (gigabytes) a modestos a muito grandes (~petabyte). Minha perspectiva é, em grande parte, de construir e executar sistemas que são pelo menos destinados a estar continuamente disponíveis. Desnecessário dizer, eu ganhei experiência em primeira mão com as idiossincrasias particulares do PostgreSQL através de alguns problemas dolorosos de produção ao longo dos anos.

#1: Desastroso XID Wraparound

Basta dizer que este pode morder com força. Há muitas histórias de paralisações de vários dias causados por este problema. No Go ahead e Google it você vai encontrar pobres almas escrevendo sobre a vez que eles pisaram nesta mina terrestre. Praticamente qualquer instalação pós-ano não trivial que não esteja equipada com um especialista de topo irá esbarrar nele eventualmente.

É provável que, em algum momento no futuro, XIDs serão transitórios para usar inteiros de 64 bits, mas até lá, estamos presos a ele. Acho que pelo menos podemos ser gratos por haver um processo que impede que isso aconteça como uma questão de curso, ao contrário de alguns softwares de avião.

#2: Failover provavelmente perderá dados

A configuração de replicação de streaming run-of-the-mill quase certamente perderá dados comprometidos se o mestre ativo falhar repentinamente. “Esse é o preço da replicação assíncrona”, alguns podem dizer, mas não precisa ser assim. PostgreSQL suporta replicação síncrona com quórum para durabilidade tolerante a falhas, mas tem um envelope de desempenho muito mais apertado que complica sua aplicação.

A espera não utiliza recursos do sistema, mas os bloqueios de transações continuam a ser mantidos até que a transferência seja confirmada. Como resultado, o uso incauto de replicação síncrona reduzirá o desempenho dos aplicativos de banco de dados devido ao aumento dos tempos de resposta e à maior contenção.

Esta replicação de quórum é útil em uma pitada, mas hesito em recomendá-la para casos de uso de uso geral. É semelhante à replicação isr de Kafka com acks=all e quórum min_isr, mas com toda a complexidade nuances de um banco de dados relacional transacional executando consultas arbitrárias. No momento, não estou ciente de uma aplicação bem sucedida de quórum para replicação altamente disponível e de alta durabilidade em escala não trivial. Se você tiver, estender a mão!

Em termos de bancos de dados relacionais, a replicação do grupo de Galera Clustertambém é imperfeita, mas mais próxima do ideal. Eles até incentivam a replicação geo-distribuída,o que provavelmente seria desastroso para uma configuração de replicação pós-ultragreSQL usando o compromisso de quórum.

#3: Replicação ineficiente que espalha a corrupção

A replicação de streaming é de longe o mecanismo de replicação mais utilizado nas implantações de produção. É uma forma de replicação física, o que significa que replica alterações nos próprios dados binários no disco.

Toda vez que uma página de banco de dados em disco (8KB) precisa ser modificada por uma operação de gravação, mesmo apenas um byte único, uma cópia de toda a página, editada com as alterações solicitadas, é escrita no registro de gravação (WAL). A replicação de streaming físico aproveita essa infraestrutura WAL existente como um registro de alterações que ela transmite para réplicas.

Atualização: Algumas pessoas apontaram que o PostgreSQL só precisa fazer esta gravação de página inteira uma vez por ponto de verificação wal. Isso é verdade, mas na maioria dos sistemas do mundo real a maioria das gravações acabará em uma página única entre os pontos de verificação, seguindo uma distribuição da lei de poder. Mais importante, porém: ao antecipar o comportamento do sistema, a abordagem certa é assumir o caso mais caro, particularmente se depender de um comportamento difícil de prever e altamente dinâmico da aplicação.

Com a replicação física, uma grande compilação de índice, por exemplo, cria uma enorme enxurrada de entradas WAL que podem facilmente gargalar o fluxo de replicação. O processo de reprodução de leitura-modificação na granularidade de página pode levar à corrupção de dados induzida por hardware no mestre muito mais facilmente propagando-se para réplicas, que eu pessoalmente testemunhei várias vezes na produção.

Isso contrasta com a replicação lógica, que só replica alterações lógicas de dados. Pelo menos teoricamente, uma grande construção de índice só resultaria em um único comando replicado em toda a rede. Embora o PostgreSQL tenha suportado a replicação lógica há algum tempo, a maioria das implantações usa replicação de streaming físico porque é mais robusta, mais amplamente suportada e muito mais fácil de usar.

#4: Lixo MVCC frequentemente doloroso

Como a maioria dos bancos de dados tradicionais, o PostgreSQL usa o controle de concurrency (MVCC) de várias versões para implementar transações simultâneas. No entanto, sua implementação particular frequentemente introduz dor operacional em torno das versões de linha de lixo e sua limpeza (VACUUM). De um modo geral, as operações criam novas cópias (ou “versões de linha”) de quaisquer linhas modificadas, deixando as versões antigas em disco até que possam ser limpas.UPDATE

Embora essa situação tenha melhorado constantemente ao longo dos anos, é um sistema complexo que é um pouco de uma caixa preta para quem se aproxima do problema pela primeira vez. Por exemplo, saber sobre Tuplas somente heap (HOT) e quando ele entra em ação pode ser fazer ou quebrar para cargas de trabalho pesadas de atualização no local, como manter uma coluna de contador consistente em uma fileira. A configuração padrão de autovacuum funciona na maioria das vezes, mas quando não funciona, bom senhor.

Em contraste, MySQL e Oracle usam redo e desfazer logs. Eles não precisam de um processo semelhante de coleta de lixo de fundo. A compensação que eles fazem é principalmente latência adicional para operações de compromisso transacional e reversão.

Pode ser que em algum momento em um futuro distante, zheap salve todos nós.

#5: Processo por conexão = Dor em Escala

O PostgreSQL prevê um processo para cada conexão,enquanto a maioria dos outros bancos de dados usa um modelo de concorrência de conexão mais eficiente. Isso torna um problema de ajuste difícil, pois há um limiar relativamente baixo no qual adicionar mais conexões degrada o desempenho (em torno de ~2x núcleos) e, eventualmente, outro limiar mais alto (difícil de estimar, altamente dependente de carga de trabalho) onde o desempenho despencará.

A receita padrão de usar um pooler de conexão certamente chuta a lata pela estrada, mas introduz uma complexidade arquitetônica adicional significativa. Em uma implantação particularmente grande, eu eventualmente tive que camada em um segundo nível pgbouncer. Um nível foi ranhado nos servidores de aplicativos e outro nos servidores do banco de dados. Ao todo, agregou conexões para cerca de 1 milhão de processos de clientes. Sintonizar era 40% arte escura, 40% força bruta e 10% pura sorte.

A escalabilidade do processo vem melhorando gradualmente a cada versão principal, mas, em última análise, há um limite um pouco difícil para o desempenho desta arquitetura em comparação com algo como thread-per-connection, que é usado no MySQL.

Para obter mais profundidade técnica, consulte https://brandur.org/postgres-connections.

#6: Índice de Chave Primária é um Porco Espacial

As tabelas no PostgreSQL têm um índice para a chave primária e armazenamento de linha separado chamado heap. Outras bases de dados as integram ou suportam “tabelas organizadas por índices”. Neste arranjo, o processo de pesquisa principal leva diretamente aos dados da linha sem uma busca secundária para obter a linha completa e a necessária utilização adicional da CPU e da I/O.

O comando CLUSTER no PostgreSQL reorganiza uma tabela de acordo com um índice para melhorar o desempenho, mas realmente não funciona para a maioria dos casos OLTP do mundo real. Ele reescreve toda a tabela sob uma fechadura exclusiva, bloqueando quaisquer leituras ou gravações. PostgreSQL não mantém o layout em cluster para novos dados, portanto, esta operação deve ser realizada periodicamente. Portanto, ele realmente só é útil se você pode tirar seu banco de dados offline por longos períodos de tempo regularmente.

Mas, mais criticamente, tabelas organizadas por índices economizam espaço, pois o índice não requer uma cópia separada dos dados da linha. Para mesas com linhas pequenas que são cobertas principalmente pela chave principal, como mesas de ação, isso pode facilmente cortar a pegada de armazenamento da mesa pela metade.

Considere a tabela a seguir que armazena “likes” sociais para objetos arbitrários:

CREATE TABLE likes (
object_type INTEGER NOT NULL,
object_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY,
user_id BIGINT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY(object_type, object_id, user_id)
);

O PostgreSQL manterá um índice para a chave primária que é separado do armazenamento da tabela base. Este índice conterá uma cópia completa do , , e colunas para cada linha. 20 de 28 bytes em cada linha (~70%) será duplicado. Se o PostgreSQL suportasse tabelas organizadas por índices, não consumiria todo esse espaço adicional.object_typeobject_iduser_id

#7: atualizações de versão principal podem exigir tempo de inatividade

Algumas atualizações de versão principais requerem horas de inatividade para converter os dados para um grande banco de dados. Usando o mecanismo típico de replicação de streaming, não é possível fazer isso graciosamente atualizando uma réplica e fazendo um failover. O formato binário no disco é incompatível entre as principais versões, portanto, o protocolo de fio entre mestre e réplica também é efetivamente incompatível.

Espero que a replicação lógica eventualmente substitua completamente a replicação de streaming, o que permitiria uma estratégia de atualização on-line e rolando. Quando trabalhei em uma implantação em larga escala horizontal, fizemos um investimento significativo em engenharia em infraestrutura personalizada para fazer essas atualizações sem tempo de inatividade usando um sistema de replicação adicional baseado em gatilho que também foi usado para migração de fragmentos.

#8: Configuração de replicação um pouco complicada

Para ser justo, a replicação fora da caixa do MySQL é muito mais complicada, mas comparada com algumas lojas noSQL como MongoDB e Redis ou alguns sistemas de replicação orientados a cluster, como a Replicação do Grupo MySQL e o Galera Cluster,de uma perspectiva de facilidade de uso e de evasão de bordas afiadas, configurar a replicação no PostgreSQL deixa muito a desejar. Embora a replicação lógica teoricamente forneça muito mais flexibilidade para soluções de terceiros para papel sobre essas lacunas, até agora há algumas ressalvas muito grandes para usá-lo no lugar da replicação de streaming.

#9: Ridículo não-planner-dicas dogma

As dicas do planejador permitem que as consultas direcionem o planejador de consultas a usar estratégias que ele não usaria por conta própria. No que parece ser uma forma do argumento compilador suficientemente inteligente, a equipe de desenvolvimento do PostgreSQL manteve a linha por anos em se recusar a apoiar dicas de planejador de consultas.

Entendo seu raciocínio, que em grande parte é sobre impedir que os usuários ataquem problemas usando dicas de consulta que devem ser corrigidas escrevendo consultas adequadas. No entanto, essa filosofia parece brutalmente paternalista quando você está assistindo um banco de dados de produção espiral em um colapso total sob uma mudança repentina e inesperada de plano de consulta.

Em muitas dessas situações, uma dica para o planejador pode mitigar o problema em minutos, comprando à equipe de engenharia as horas ou dias necessários para fazer uma correção adequada à consulta. Embora existam algumas soluções alternativas que envolvem desativar certas estratégias de planejador de consultas, elas são arriscadas e definitivamente não devem ser empregadas sob qualquer tipo de pressão temporal.

Aquela torre de marfim deve ser boa.

#10: Sem compactação de blocos

A compressão de página do InnoDB no MySQL geralmente corta a pegada de armazenamento pela metade e é praticamente “livre” de uma perspectiva de desempenho. O PostgreSQL compactará automaticamente grandes valores, mas isso não é útil para as formas mais comuns de armazenamento de dados em bancos de dados relacionais. Para a maioria dos casos de uso de RDBMS, uma linha é tipicamente algumas centenas de bytes ou menos, o que significa que a compressão só poderia realmente ser eficaz quando aplicada em várias linhas, ou em blocos.

A compactação de blocos é realmente bastante difícil de implementar para o tipo de estruturas de dados no coração do PostgreSQL, mas a estratégia de “furo” empregada pelo motor de armazenamento InnoDB da MySQL parece funcionar muito bem na prática, apesar de algumas desvantagens.

Atualização 2020-04-07: Mark Callaghan, da fama de “MySQL no Facebook”, questionou minha declaração aqui de que a compressão de tapa-buracos funciona “muito bem na prática”. Acontece que a maior instalação MySQL do mundo nunca usou compressão de furos, como eu pensava anteriormente. Eles usaram com sucesso uma versão ligeiramente modificada da geração mais antiga de compressão InnoDB com sucesso,no entanto, antes de migrar para o MyRocks há alguns anos.

Embora a compressão de furos pareça funcionar para algumas pessoas, existem algumas ressalvas que o tornam menos um home run. Se você está executando a versão de Percona do MySQL, MyRocks é uma aposta melhor. Se não, parece que a clássica compressão de tabela InnoDB é uma aposta mais segura para cargas de trabalho muito lidas pesadas no armazenamento flash. Mark não apontou para nenhum caso específico de grandes problemas de produção, mas aponta que ele “duvida que os sistemas de arquivos tenham sido projetados para furar por página e eu teria medo de falhas obscuras”.

A única configuração de compactação de blocos de uso geral amplamente utilizada no mundo PostgreSQL alavanca o ZFS,que parece funcionar muito bem para as pessoas. ZFS é uma realidade de qualidade de produção no Linux nos dias de hoje, mas definitivamente traz consigo algumas despesas administrativas que não existem para os sistemas de arquivos mais “fora da caixa” como XFS ou ext4.

Tudo o que disse…

Você provavelmente ainda deve usar PostgreSQL e não outra coisa para armazenar dados que você gostaria idealmente de, você sabe, manter por algum tempo. Em geral, eu recomendaria começar com PostgreSQL e depois tentar descobrir por que não vai funcionar para o seu caso de uso.

PostgreSQL é muito maduro, bem projetado, ricamente caracterizado, é geralmente livre de bordas afiadas, e é bastante performático para a grande maioria dos casos de uso. Também é livre de um patrocinador corporativo dominante,inclui documentação fantástica, e tem uma comunidade profissional e inclusiva.

A boa notícia é que a dor causada por muitos dos problemas trazidos neste post pode ser reduzida ou eliminada usando um serviço de banco de dados gerenciado como Heroku PostgreSQLCompor PostgreSQL, Amazon RDS para PostgreSQL, ou Google Cloud SQL para PostgreSQL. Se você pode usar um desses serviços, pelo amor de tudo o que é sagrado, por favor, faça!

Tenho orgulho de dizer que construí software em cima do PostgreSQL por quase duas décadas e continuo sendo um forte defensor, apesar de suas falhas. Dado o progresso que testemunhei ao longo dos anos por sua incrível equipe de desenvolvimento, posso dizer que a maioria, se não todas essas questões serão resolvidas no devido tempo.