Je conçois une base de données (sur PostgreSQL 9.6) qui stockera les données d'une application distribuée. En raison de la nature distribuée de l'application, je ne peux pas utiliser d'entiers à incrémentation automatique (SERIAL
) comme clé primaire en raison de conditions de concurrence potentielles.
La solution naturelle consiste à utiliser un UUID ou un identifiant globalement unique. Postgres est livré avec un type UUID
intégré , ce qui est parfait.
Le problème que j'ai avec UUID est lié au débogage: c'est une chaîne non conviviale. L'identifiant ff53e96d-5fd7-4450-bc99-111b91875ec5
Ne me dit rien, tandis que ACC-f8kJd9xKCd
, Bien qu'il ne soit pas garanti qu'il soit unique, me dit que j'ai affaire à un objet ACC
.
Du point de vue de la programmation, il est courant de déboguer des requêtes d'application concernant plusieurs objets différents. Supposons que le programmeur recherche à tort un objet ACC
(compte) dans la table ORD
(ordre). Avec un identifiant lisible par l'homme, le programmeur identifie instantanément le problème, tout en utilisant des UUID, il passait un peu de temps à découvrir ce qui n'allait pas.
Je n'ai pas besoin de l'unicité "garantie" des UUID; J'ai ai besoin d'espace pour générer des clés sans conflits, mais l'UUID est exagéré. De plus, dans le pire des cas, ce ne serait pas la fin du monde si une collision se produisait (la base de données la rejette et l'application peut récupérer). Ainsi, les compromis envisagés, un identifiant plus petit mais convivial serait la solution idéale pour mon cas d'utilisation.
L'identifiant que j'ai trouvé a le format suivant: {domain}-{string}
, Où {domain}
Est remplacé par le domaine d'objet (compte, commande, produit) et {string}
Est une chaîne générée aléatoirement . Dans certains cas, il peut même être judicieux d'insérer un {sub-domain}
Avant la chaîne aléatoire. Ignorons la longueur de {domain}
Et {string}
Dans le but de garantir l'unicité.
Le format peut avoir une taille fixe s'il améliore les performances d'indexation/interrogation.
Sachant que:
ACC-f8kJd9xKCd
.Concernant les performances, quelle est la meilleure façon de stocker cette clé?
Voici quatre solutions possibles, mais comme j'ai peu d'expérience avec les bases de données, je ne sais pas laquelle (le cas échéant) est la meilleure.
VARCHAR
)(Postgres ne fait aucune différence entre CHAR(n)
et VARCHAR(n)
, donc j'ignore CHAR
).
Après quelques recherches, j'ai découvert que la comparaison de chaînes avec VARCHAR
, spécialement sur les opérations de jointure, est plus lente que l'utilisation de INTEGER
. C'est logique, mais est-ce quelque chose dont je dois m'inquiéter à cette échelle?
bytea
)Contrairement à Postgres, MySQL n'a pas de type natif UUID
. Il existe plusieurs articles expliquant comment stocker un UUID à l'aide d'un champ BINARY
de 16 octets, au lieu d'un champ VARCHAR
de 36 octets. Ces messages m'ont donné l'idée de stocker la clé au format binaire (bytea
sur Postgres).
Cela économise de la taille, mais je suis plus préoccupé par les performances. J'ai eu peu de chance de trouver une explication sur laquelle la comparaison est plus rapide: binaire ou chaîne. Je pense que les comparaisons binaires sont plus rapides. S'ils le sont, alors bytea
est probablement meilleur que VARCHAR
, même si le programmeur doit maintenant encoder/décoder les données à chaque fois.
Je peux me tromper, mais je pense que bytea
et VARCHAR
compareront (égalité) octet par octet (ou caractère par caractère). Existe-t-il un moyen de "sauter" cette comparaison étape par étape et de comparer simplement "le tout"? (Je ne pense pas, mais cela ne coûte pas de vérifier).
Je pense que le stockage en tant que bytea
est la meilleure solution, mais je me demande s'il existe d'autres alternatives que j'ignore. De plus, la même préoccupation que j'ai exprimée à propos de la solution 1 est vraie: les frais généraux sur les comparaisons sont-ils suffisants pour que je me préoccupe?
J'ai trouvé deux solutions très "créatives" qui pourraient fonctionner, je ne sais pas dans quelle mesure (c'est-à-dire si j'aurais du mal à les mettre à l'échelle sur plus de quelques milliers de lignes dans un tableau).
UUID
mais avec une "étiquette" attachéeLa principale raison de ne pas utiliser les UUID est que les programmeurs puissent mieux déboguer l'application. Mais que faire si nous pouvons utiliser les deux: la base de données stocke toutes les clés en tant que UUID
s uniquement, mais elle encapsule l'objet avant/après les requêtes.
Par exemple, le programmeur demande ACC-{UUID}
, La base de données ignore la partie ACC-
, Récupère les résultats et les renvoie tous sous la forme {domain}-{UUID}
.
Peut-être que cela serait possible avec un certain piratage avec des procédures ou des fonctions stockées, mais certaines questions viennent à l'esprit:
Je n'ai jamais utilisé de procédures ou de fonctions stockées auparavant, donc je ne sais pas si c'est même possible. Quelqu'un peut-il faire la lumière? Si je peux ajouter une couche transparente entre le programmeur et les données stockées, cela semble une solution parfaite.
cidr
Oui, vous l'avez bien lu. Il s'avère que le format d'adresse IPv6 résout parfaitement mon problème .
bytea
.contains
, selon la façon dont les domaines et leur hiérarchie sont représentés.Par exemple, supposons que j'utilise le code 0000
Pour représenter le domaine "produits". La clé 0000:0db8:85a3:0000:0000:8a2e:0370:7334
Représenterait le produit 0db8:85a3:0000:0000:8a2e:0370:7334
.
La question principale ici est: par rapport à bytea
, y a-t-il un avantage ou un inconvénient principal à utiliser le type de données cidr
?
ltree
Si IPV6 fonctionne, tant mieux. Il ne prend pas en charge "ACC". ltree
fait.
Un chemin d'étiquette est une séquence de zéro ou plusieurs étiquettes séparées par des points, par exemple L1.L2.L3, représentant un chemin de la racine d'une arborescence hiérarchique vers un nœud particulier. La longueur d'un chemin d'étiquette doit être inférieure à 65 Ko, mais le garder sous 2 Ko est préférable. En pratique, ce n'est pas une limitation majeure; par exemple, le chemin d'étiquette le plus long dans le catalogue DMOZ ( http://www.dmoz.org ) est d'environ 240 octets.
Vous l'utiliseriez comme ça,
CREATE EXTENSION ltree;
SELECT replace('ACC-f8kJd9xKCd', '-', '.')::ltree;
Nous créons des exemples de données.
SELECT x, (
CASE WHEN x%7=0 THEN 'ACC'
WHEN x%3=0 THEN 'XYZ'
ELSE 'COM'
END ||'.'|| md5(x::text)
)::ltree
FROM generate_series(1,10000) AS t(x);
CREATE INDEX ON foo USING Gist (ltree);
ANALYZE foo;
x | ltree
-----+--------------------------------------
1 | COM.c4ca4238a0b923820dcc509a6f75849b
2 | COM.c81e728d9d4c2f636f067f89cc14862c
3 | XYZ.eccbc87e4b5ce2fe28308fd9f2a7baf3
4 | COM.a87ff679a2f3e71d9181a67b7542122c
5 | COM.e4da3b7fbbce2345d7772b0674a318d5
6 | XYZ.1679091c5a880faf6fb5e6087eb1b2dc
7 | ACC.8f14e45fceea167a5a36dedd4bea2543
8 | COM.c9f0f895fb98ab9159f51fd0297e236d
Et l'alto ..
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on foo (cost=103.23..234.91 rows=1414 width=57) (actual time=0.422..0.908 rows=1428 loops=1)
Recheck Cond: ('ACC'::ltree @> ltree)
Heap Blocks: exact=114
-> Bitmap Index Scan on foo_ltree_idx (cost=0.00..102.88 rows=1414 width=0) (actual time=0.389..0.389 rows=1428 loops=1)
Index Cond: ('ACC'::ltree @> ltree)
Planning time: 0.133 ms
Execution time: 1.033 ms
(7 rows)
Voir les documents pour plus d'informations et opérateurs
Si vous créez les identifiants des produits, je préfère. Si vous avez besoin de quelque chose pour les créer, j'utiliserais UUID.
En ce qui concerne la comparaison des performances avec bytea. la comparaison du réseau se fait en 3 étapes: d'abord sur les bits communs de la partie réseau, puis sur la longueur de la partie réseau, puis sur l'ensemble de l'adresse non masquée. voir: network_cmp_internal
il devrait donc être un peu plus lent que le bytea qui va directement à memcmp. J'ai exécuté un test simple sur une table avec 10 millions de lignes en recherchant une seule:
Je ne peux pas dire qu'il y a beaucoup de différence entre le bytea et le cidr (bien que l'écart soit resté cohérent) Juste l'instruction supplémentaire if
- devinez que ce n'est pas trop mal pour des tuples de 10m.
J'espère que cela aide - j'aimerais savoir ce que vous avez fini par choisir.