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?

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.


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.


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.