Quel est le type de données optimal pour un champ MD5?

35

Nous concevons un système dont la lecture est connue (de l'ordre de dizaines de milliers de lectures par minute).

  • Il y a une table namesqui sert comme une sorte de registre central. Chaque ligne a un textchamp representationet un unique keyqui est un hachage MD5 de cela representation. 1 Ce tableau contient actuellement des dizaines de millions d'enregistrements et devrait atteindre des milliards au cours de la durée de vie de l'application.
  • Il existe des dizaines d'autres tables (de schémas très variables et de nombres d'enregistrements) qui font référence à la namestable. Tout enregistrement donné dans l'une de ces tables est garanti d'avoir un name_key, qui est fonctionnellement une clé étrangère à la namestable.

1: Incidemment, comme on pouvait s'y attendre, les enregistrements de cette table sont immuables une fois écrits.

Pour toute table donnée autre que la namestable, la requête la plus courante suivra ce modèle:

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

J'aimerais optimiser les performances de lecture. Je soupçonne que mon premier arrêt devrait être de minimiser la taille des indices (bien que cela ne me dérangerait pas de me tromper).

La question:
Quels sont / sont les types de données optimaux pour les colonnes keyet name_key?
Y a-t-il une raison d'utiliser hex(32)plus bit(128)? BTREEou GIN?

bobocopy
la source

Réponses:

41

Le type de données uuidest parfaitement adapté à la tâche. Il n'occupe que 16 octets, contre 37 octets en RAM pour la représentation varcharou text. (Ou 33 octets sur le disque, mais le nombre impair nécessiterait un bourrage dans de nombreux cas pour le rendre efficacement à 40 octets.) Et le uuidtype présente quelques avantages supplémentaires.

Exemple:

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

Détails et plus d'explications:

Vous pourriez envisager d'autres fonctions de hachage (moins chères) si vous n'avez pas besoin du composant cryptographique de md5, mais j'utiliserais md5 pour votre cas d'utilisation (la plupart du temps en lecture seule).

Un mot d' avertissement : pour votre cas ( immutable once written), une PK dépendant de la fonctionnalité (pseudo-naturelle) convient. Mais la même chose serait une douleur où les mises à jour textsont possibles. Pensez à corriger une faute de frappe: le PK et tous les index dépendants, les colonnes FK dozens of other tableset autres références devraient également changer. Ballonnement des tables et des index, problèmes de verrouillage, mises à jour lentes, références perdues, ...

Si textpeut changer en fonctionnement normal, une PK de substitution serait un meilleur choix. Je suggère une bigserialcolonne (plage -9223372036854775808 to +9223372036854775807- neuf cent vingt-trois cent vingt-trois quadrillions trois cent soixante-douze trillions trente-six milliards ) de valeurs distinctes billions of rows. Peut-être une bonne idée dans tous les cas: 8 au lieu de 16 octets pour des dizaines de colonnes et d'index FK!). Ou un UUID aléatoire pour des cardinalités beaucoup plus grandes ou des systèmes distribués. Vous pouvez toujours stocker md5 (en tant que uuid) en plus pour trouver rapidement des lignes dans la table principale à partir du texte d'origine. En relation:

En ce qui concerne votre question :


Pour répondre au commentaire de @ Daniel : Si vous préférez une représentation sans traits d'union, supprimez-les pour affichage:

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

Mais je ne me dérangerais pas. La représentation par défaut est très bien. Et le problème n'est vraiment pas la représentation ici.

Si d'autres parties ont une approche différente et ajoutent des cordes sans trait d'union, ce n'est pas un problème non plus. Postgres accepte plusieurs représentations textuelles raisonnables comme entrée pour a uuid. La documentation :

PostgreSQL accepte également les autres formes de saisie suivantes: utilisation de chiffres en majuscules, format standard entouré d'accolades, suppression de tout ou partie des tirets, ajout d'un trait d'union après tout groupe de quatre chiffres. Les exemples sont:

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

De plus, les md5()déclarations de fonction text, vous pouvez utiliser decode()pour convertir en byteaet la représentation par défaut de c'est:

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

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

Il vous faudrait à encode()nouveau pour obtenir la représentation textuelle originale:

SELECT encode(my_md5_as_bytea, 'hex');

Pour couronner le tout, les valeurs stockées byteaoccuperaient 20 octets en RAM (et 17 octets sur disque, 24 avec remplissage ) en raison de la surcharge internevarlena , ce qui est particulièrement défavorable pour la taille et les performances des index simples.

Tout fonctionne en faveur d'un uuidici.

Erwin Brandstetter
la source
1
Est-ce légitime pour "uuid"? Veuillez m'excuser si je suis trop pédant, mais je pense que ce que je vois, c'est que le type de données "uuid" est orienté vers le stockage de nombres de 16 octets de long au format binaire. Mais le terme "uuid" suggère un algorithme de génération / hachage particulier ainsi que la représentation textuelle conventionnelle dans 5 blocs de caractères hexadécimaux séparés par des tirets. Si ce nom de type suggère fortement la génération d'UUID / GUID, n'est-il pas un peu trompeur, du moins pour les programmeurs, d'utiliser ce type pour stocker un hachage?
Andrew Wolfe
2
@ AndrewWolfe: Totalement légitime, OMI. Ne vous laissez pas emporter par le nom . C'est une entité de 16 octets avec un ensemble pratique de transtypages fournis et d'une logique d'entrée / sortie. Le cas d'espèce nécessite même en réalité un "identifiant unique". Vous pouvez également stocker toutes sortes de données de caractères dans des textcolonnes, même si ce n'est pas du tout un "texte".
Erwin Brandstetter
Et si le hash MD5 est converti en base 64, comment le
stockerez-
2
@PirateApp, décoder d' abord: SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
Nyov
1
@nyov: uuidest un type de 16 octets qui ne peut stocker les résultats d'aucun algorithme SHA produisant entre 160 et 512 bits. Aucun type similaire ne correspond à la distribution standard de Postgres. Vous pouvez en créer un ... À défaut bytea, comme le fait pg_crypto .
Erwin Brandstetter
2

Je voudrais stocker le MD5 dans un textou une varcharcolonne. Il n'y a pas de différence de performance entre les différents types de données de caractères. Vous voudrez peut-être limiter la longueur des valeurs md5 en varchar(xxx)vous assurant que la valeur md5 ne dépasse jamais une certaine longueur.

Les grandes listes IN ne sont généralement pas très rapides, il est préférable de faire quelque chose comme ceci:

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

Une autre option, parfois dite plus rapide, consiste à utiliser un tableau:

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

Comme vous ne faites que comparer pour l’égalité, un indice BTree normal devrait suffire. Les deux requêtes devraient pouvoir utiliser un tel index (surtout si elles ne sélectionnent qu'une petite fraction des lignes.

un cheval sans nom
la source
Une raison particulière de ne pas utiliser bit (128) ou hex (32)? Il est garanti que les valeurs s’intègrent parfaitement dans un tel champ, et je voudrais me protéger des mauvaises valeurs attribuées.
bobocopy
3
@bobocopy: il n'y a pas de type de données "hex" dans Postgres. Je n'ai jamais utilisé le bittype, je ne peux donc rien en dire. Compte tenu du nombre de lignes que vous attendez, la suggestion d'Erwin semble être meilleure, car vous économisez de l'espace en enregistrant ceci sous le nom UUID
a_horse_with_no_name
-1

Une autre option consiste à utiliser 4 colonnes INTEGER ou 2 colonnes BIGINT.

happy_marmoset
la source
2
En termes de taille de stockage, l'une ou l'autre option conviendrait, bien sûr, mais serait-il pratique de travailler avec? Peut-être pourriez-vous développer votre réponse pour montrer un exemple ou expliquer autrement.
Andriy M