Nous concevons un système connu pour être lourd en lecture (de l'ordre de dizaines de milliers de lectures par minute).
names
qui sert de sorte de registre central. Chaque ligne a un champ text
representation
et un key
unique qui est un hachage MD5 de ce representation
.1 Ce tableau compte actuellement des dizaines de millions d'enregistrements et devrait atteindre des milliards au cours de la durée de vie de l'application.names
. Tout enregistrement donné dans l'une de ces tables est garanti d'avoir un name_key
, Qui est fonctionnellement une clé étrangère de la table names
.1: Incidemment, comme vous pouvez vous y attendre, les enregistrements de ce tableau sont immuables une fois écrits.
Pour toute table donnée autre que la table names
, la requête la plus courante suivra ce modèle:
SELECT list, of, fields
FROM table
WHERE name_key IN (md5a, md5b, md5c...);
Je souhaite optimiser les performances de lecture. Je soupçonne que mon premier arrêt devrait être de minimiser la taille des indices (même si cela ne me dérangerait pas de me tromper).
La question:
Quels sont/sont les types de données optimaux pour les colonnes key
et name_key
?
Y a-t-il une raison d'utiliser hex(32)
sur bit(128)
? BTREE
ou GIN
?
Le type de données uuid
est parfaitement adapté à la tâche. Il n'occupe que 16 octets contre 37 octets en RAM pour la représentation varchar
ou text
. (Ou 33 octets sur le disque, mais le nombre impair serait nécessitent un remplissage dans de nombreux cas pour qu'il soit efficace 40 octets.) Et le type uuid
a quelques autres avantages.
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'irais avec md5 pour votre cas d'utilisation (principalement en lecture seule).
Un mot d'avertissement : Pour votre cas (immutable once written
) A fonctionnellement dépendant (pseudo-naturel ) PK est très bien. Mais la même chose serait un pain où les mises à jour sur text
sont possibles. Pensez à corriger une faute de frappe: le PK et tous les index dépendants, les colonnes FK dans dozens of other tables
Et d'autres références devraient également changer. Bloat de table et d'index, problèmes de verrouillage, mises à jour lentes, références perdues, ...
Si text
peut changer en fonctionnement normal, un PK de substitution serait un meilleur choix. Je suggère une colonne bigserial
(plage -9223372036854775808 to +9223372036854775807
- c'est neuf quintillion deux cent vingt-trois quadrillions trois cent soixante-douze mille milliards trente-six quelque chose de milliards ) des valeurs distinctes pour billions of rows
. Cela pourrait être une bonne idée dans n'importe quel cas: 8 au lieu de 16 octets pour des dizaines de colonnes et d'index FK!) . Ou un ID aléatoire pour beaucoup de plus grandes cardinalités ou systèmes distribués. Vous pouvez toujours stocker ledit md5 (comme uuid
) en plus pour trouver rapidement des lignes dans la table principale à partir du texte d'origine. En relation:
Quant à votre requête:
Pour répondre @ Daniel's comment : Si vous préférez une représentation sans tirets, supprimez les tirets pour les afficher:
SELECT replace('90b7525e-84f6-4850-c2ef-b407fae3f271', '-', '')
Mais ça ne me dérangerait 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 devraient avoir une approche différente et lancer des chaînes sans tirets dans le mélange, ce n'est pas un problème non plus. Postgres accepte plusieurs représentations de texte raisonnables en entrée pour un uuid
. La documentation :
PostgreSQL accepte également les formes alternatives suivantes pour la saisie: utilisation de chiffres en majuscules, le format standard entouré d'accolades, omettant certains ou tous les tirets, ajoutant un trait d'union après n'importe quel groupe de quatre chiffres. Voici des exemples:
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, la fonction md5()
renvoie text
, vous utiliseriez decode()
pour convertir en bytea
et la représentation par défaut de ça est:
SELECT decode(md5('Store hash for long string, maybe for index?'), 'hex')
\220\267R^\204\366HP\302\357\264\007\372\343\362q
Vous devrez de nouveau encode()
pour obtenir la représentation originale du texte:
SELECT encode(my_md5_as_bytea, 'hex');
Pour couronner le tout, les valeurs stockées sous la forme bytea
occuperaient 20 octets dans RAM (et 17 octets sur le disque, 24 avec remplissage ) en raison de la interne varlena
surcharge , ce qui est particulièrement défavorable pour la taille et les performances des index simples.
Tout fonctionne en faveur d'un uuid
ici.
Je voudrais stocker le MD5 dans une colonne text
ou varchar
. Il n'y a aucune différence de performances entre les différents types de données de caractères. Vous souhaiterez peut-être limiter la longueur des valeurs md5 en utilisant varchar(xxx)
pour vous assurer 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 qui est parfois considérée comme plus rapide consiste à utiliser un tableau:
select t.*
from the_table t
where name_key = ANY (array['one', 'two', 'three']);
Comme vous comparez juste pour l'égalité, un indice BTree normal devrait être bien. Les deux requêtes devraient pouvoir utiliser un tel index (surtout si elles ne sélectionnent qu'une petite fraction des lignes.
Une autre option consiste à utiliser 4 colonnes INTEGER ou 2 BIGINT.