Qual é o tipo de dados ideal para um campo MD5?

35

Estamos projetando um sistema que é conhecido por ter muita leitura (da ordem de dezenas de milhares de leituras por minuto).

  • Há uma tabela namesque serve como uma espécie de registro central. Cada linha possui um textcampo representatione um exclusivo keyque é um hash MD5 representation. 1 Atualmente, esta tabela possui dezenas de milhões de registros e espera-se que cresça bilhões ao longo da vida útil do aplicativo.
  • Existem dezenas de outras tabelas (de esquemas e contagens de registros altamente variadas) que fazem referência à namestabela. É garantido que qualquer registro em uma dessas tabelas tenha um name_key, que é funcionalmente uma chave estrangeira para a namestabela.

1: Aliás, como seria de esperar, os registros nesta tabela são imutáveis ​​depois de gravados.

Para qualquer tabela diferente da namestabela, a consulta mais comum seguirá esse padrão:

SELECT list, of, fields 
FROM table 
WHERE name_key IN (md5a, md5b, md5c...);

Gostaria de otimizar o desempenho de leitura. Suspeito que minha primeira parada seja minimizar o tamanho dos índices (embora eu não me importe em provar que estou errado lá).

A pergunta:
Quais são / são os tipos de dados ideais para as colunas keye name_key?
Existe uma razão para usar hex(32)mais bit(128)? BTREEou GIN?

bobocopy
fonte

Respostas:

41

O tipo de dados uuidé perfeitamente adequado para a tarefa. Ele só ocupa 16 bytes em oposição a 37 bytes de memória RAM para o varcharou textrepresentação. (Ou 33 bytes no disco, mas o número ímpar exigiria preenchimento em muitos casos para torná-lo efetivamente 40 bytes.) E o uuidtipo tem mais algumas vantagens.

Exemplo:

SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash

Detalhes e mais explicações:

Você pode considerar outras funções de hash (mais baratas) se não precisar do componente criptográfico do md5, mas eu usaria o md5 para o seu caso de uso (principalmente somente leitura).

Uma palavra de advertência : Para o seu caso ( immutable once written), uma PK funcionalmente dependente (pseudo-natural) é adequada. Mas o mesmo seria uma dor onde as atualizações textsão possíveis. Pense em corrigir um erro de digitação: o PK e todos os índices dependentes, colunas do FK dozens of other tablese outras referências também teriam que mudar. Inchaço de tabela e índice, problemas de bloqueio, atualizações lentas, referências perdidas, ...

Se textpuder mudar na operação normal, uma PK substituta seria uma escolha melhor. Sugiro uma bigserialcoluna (intervalo -9223372036854775808 to +9223372036854775807- são nove quintilhões duzentos e vinte e três quatrocentos trezentos e setenta e dois trilhões trinta e seis algo bilhões ) para valores distintos billions of rows. Em qualquer caso, pode ser uma boa ideia : 8 em vez de 16 bytes para dezenas de colunas e índices do FK!). Ou um UUID aleatório para cardinalidades muito maiores ou sistemas distribuídos. Você sempre pode armazenar o md5 (as uuid) adicionalmente para encontrar rapidamente linhas na tabela principal a partir do texto original. Relacionado:

Quanto à sua consulta :


Para abordar o comentário de @ Daniel : Se você preferir uma representação sem hífens, remova os hífens para exibição:

SELECT replace('90b7525e-84f6-4850-c2ef-b407fae3f271', '-', '')

Mas eu não me incomodaria. A representação padrão está correta. E o problema realmente não é a representação aqui.

Se outras partes devem ter uma abordagem diferente e jogar cordas sem hífens na mistura, isso também não é um problema. O Postgres aceita várias representações de texto razoáveis ​​como entrada para a uuid. A documentação :

O PostgreSQL também aceita as seguintes formas alternativas de entrada: uso de dígitos maiúsculos, o formato padrão entre parênteses, omitindo alguns ou todos os hífens, adicionando um hífen após qualquer grupo de quatro dígitos. Exemplos são:

A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}

Além disso, a md5()função retorna text, você usaria decode()para converter byteae a representação padrão disso é:

SELECT decode(md5('Store hash for long string, maybe for index?'), 'hex')

\220\267R^\204\366HP\302\357\264\007\372\343\362q

Você precisaria encode()novamente para obter a representação de texto original:

SELECT encode(my_md5_as_bytea, 'hex');

Para completar, os valores armazenados como byteaocupariam 20 bytes na RAM (e 17 bytes no disco, 24 com preenchimento ) devido à sobrecarga internavarlena , o que é particularmente desfavorável para o tamanho e o desempenho de índices simples.

Tudo funciona a favor de um uuidaqui.

Erwin Brandstetter
fonte
11
Isso é legítimo para "uuid"? Por favor, desculpe-me se eu for muito pedante, mas acho que o que estou vendo é que o tipo de dados "uuid" é orientado para o armazenamento de números com 16 octetos de comprimento em formato binário. Mas o termo "uuid" sugere um algoritmo de geração / hash específico, bem como a representação textual convencional em 5 blocos de caracteres hexadecimais separados por traço. Se esse nome de tipo sugere fortemente a geração de UUID / GUID, não é um pouco enganador, pelo menos para os programadores, usar esse tipo para armazenar um hash?
Andrew Wolfe
2
@ AndrewWolfe: Totalmente legítimo, IMO. Não se deixe levar pelo nome . É uma entidade de 16 bytes com um conjunto conveniente de conversão de tipos e lógica de entrada / saída fornecidas. O caso em questão até exige um "identificador exclusivo". Você também pode armazenar todos os tipos de dados de caracteres em textcolunas - mesmo que não seja um "texto".
Erwin Brandstetter
e se o hash MD5 for convertido para a base 64, como você o armazenará então
PirateApp
2
@PirateApp, descodificá-lo em primeiro lugar: SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
nyov 11/11
11
@nyov: uuidé um tipo de 16 bytes que não pode armazenar os resultados de qualquer algoritmo SHA que produza entre 160 e 512 bits. Não existe um tipo semelhante que se encaixe na distribuição padrão do Postgres. Você pode criar um ... Na falta disso, o padrão é byteacomo pg_crypto .
Erwin Brandstetter 12/11
2

Eu armazenaria o MD5 em uma coluna textou varchar. Não há diferença de desempenho entre os vários tipos de dados de caracteres. Você pode restringir o comprimento dos valores md5 usando varchar(xxx)para garantir que o valor md5 nunca exceda um determinado comprimento.

As grandes listas IN geralmente não são muito rápidas, é melhor fazer algo assim:

with md5vals (md5) as (
  values ('one'), ('two'), ('three')
)
select t.*
from the_table t
  join md5vals m on t.name_key  = m.md5;

Outra opção que às vezes se diz ser mais rápida é usar uma matriz:

select t.*
from the_table t
where name_key = ANY (array['one', 'two', 'three']);

Como você está apenas comparando a igualdade, um índice BTree regular deve ser bom. Ambas as consultas devem poder usar esse índice (especialmente se estiverem selecionando apenas uma pequena fração das linhas.

um cavalo sem nome
fonte
Algum motivo específico para não usar o bit (128) ou o hex (32)? É garantido que os valores se ajustem perfeitamente a esse campo, e eu gostaria de me proteger dos valores ruins que estão sendo atribuídos.
bobocopy
3
@obocopy: não há tipo de dados "hexadecimal" no Postgres. Eu nunca usei o bittipo, então não posso comentar sobre isso. Dado o seu número esperado de linhas, a sugestão de Erwin parece ser melhor por causa da economia de espaço que você começa com armazenar isso como UUID
a_horse_with_no_name
-1

Outra opção é usar 4 colunas INTEGER ou 2 BIGINT.

happy_marmoset
fonte
2
Em termos de tamanho de armazenamento, ambas as opções se encaixam, é claro, mas com que conveniência seria trabalhar? Talvez você possa expandir sua resposta para mostrar um exemplo ou explicar isso.
Andriy M