PostgreSQL

Chaves Artificiais no PostgreSQL: desempenho

Falamos sobre a criação de chaves artificiais, sobre como usar UUID ou sequencias, inclusive sobre a nova sintaxe adotada no PostgreSQL 10 utilizando as chamadas IDENTITY COLUMNs. Uma reação comum que eu vi por aí é achar que é mais simples usar UUID sempre, uma vez que você não tem risco de colisão e pode usar em sistemas distribuídos. Bom, a história não é bem assim. Existe um custo adicional ao se escolher UUIDs ou mesmo gerar hashs enormes e armazenar no banco de dados. Vou aqui explorar algumas possibilidades, entre utilizar uma SEQUENCE armazenada num campo INTEGER e índices do tipo BTREE e HASH (que melhorou muito no PostgreSQL 10). Depois vamos utilizar campos do tipo UUID, VARCHAR e CHAR, também com índices BTREE e HASH.

Primeiro vamos criar nossas tabelas:

Notem que não existem índices UNIQUE do tipo HASH, logo ele não pode ser utilizado em uma PRIMARY KEY. Neste caso adicionamos o índice após a criação da tabela.

INSERT

Agora vamos inserir um milhão de registros em cada uma das tabelas utilizando a função nextval() nas sequencias e a função uuid_generate_v4() para gerar o UUID:

Tomei alguns cuidados antes de fazer a carga:

  • Aumentei o valor do WORK_MEM;
  • Rodei um CHECKPOINT antes de cada teste;
  • Realizei todos os teste num mesmo ambiente controlado, sem nada rodando em paralelo.

Vejamos o tempo levado para a carga de cada tabela:

Aqui vemos uma clara vantagem em se utilizar sequências! Elas são muito mais rápidas durante o INSERT. Depois vemos que os dados armazenados em UUID são mais rápidos que os armazenados em CHAR ou VARCHAR. Note também que nas tabelas que utilizam UUID, o índice HASH foi mais veloz que o índice BTREE. Para números sequenciais isso se inverte.

Tamanho

Além disso, vemos que o tamanho das tabelas muda, uma vez que uma sequencia ocupa menos espaço:

Vejam que utilizar UUID é mais eficiente do que armazenar este tipo de dado em CHAR ou VARCHAR, daí o melhor desempenho na carga.

Agora vejamos como fica o tamanho dos índices:

Aqui nós vemos como o índice do tipo HASH tem vantagens em dados aleatórios sobre o BTREE que é melhor em dados sequenciais.

SELECT

Outro ponto de vista importante é referente às estatísticas coletadas pelo ANALYZE. Olhando a tabela pg_stats, vemos algumas coisas interessantes:

A primeira coisa que vemos é que o tamanho (avg_width) do campo é muito menor no caso de um INTEGER e muito pior no caso do VARCHAR e CHAR. depois notamos que a correlação (relação entre a distribuição dos dados e a ordem em que eles estão armazenados no disco) é favorável ao uso de sequencias, particularmente quando você quiser trazer vários registros baseados na ordenação.

Vejamos como fica o desempenho das nossas chaves artificiais em operações de leitura. Primeiro vamos pegar um registro apenas de cada tabela e medir o tempo:

Vemos que a diferença aqui é pequena, com leve vantagem para índices do tipo HASH e para o uso de sequências. Agora vamos utilizar uma ordenação para trazer os dados:

Aqui a correlação faz muita diferença e os índices do tipo HASH são uma péssima escolha. Então se você tem o hábito de ordenar os dados baseado na sua chave artificial, as sequências tem uma vantagem enorme, e os índices HASH uma boa desvantagem.

Conclusões

Espero que tenha ficado claro que o uso de UUID tem custo sim e deve ser avaliado com cuidado. Então vejamos algumas considerações finais:

  • Não fiz testes com UPDATE e DELETE aqui, pois considerei que não relevantes para no uso de chaves artificiais.
  • No geral ele ocupa mais espaço (ainda mais se você fizer a besteira de utilizar CHAR ou VARCHAR)
  • UUID e é cerca de 5x mais lento em operações de INSERT
  • UUID e sequências tem desempenho semelhante em consultas simples do tipo “id = xxxx’, mas em consultas complexas as sequences são mais eficientes devido a correlação entre a sequência e o armazenamento físico;
  • Índices do tipo HASH são ligeiramente mais eficientes com UUID, mas não valem à pena quando se utiliza sequences. No entanto, vale lembrar que índices do tipo hash não podem ser utilizados como PK ou UNIQUE.

Observação

Você também pode utilizar o tipo de dados binário bytea para armazenar números hexadecimais com a mesma eficiência que o campo do tipo UUID, gravando dois números hexadecimais por byte, enquanto o CHAR e VARCHAR utilizam pelo menos um byte por caractere. A diferença está na validação e formatação de entrada e saída que fica mais simples no UUID.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.