web-dev-qa-db-fra.com

Qu'est-ce qui peut mal tourner en utilisant la même séquence sur plusieurs tables en postgres?

Nous envisageons d'utiliser une séquence partagée pour attribuer des identifiants aux clés primaires pour toutes les tables de notre base de données. Il y en a environ 100. Seul un couple est inséré fréquemment et régulièrement. Nous voulons exclure que ce soit "une idée terrible pour une raison évidente" avant de passer à la phase de l'essayer et de le tester à pleine charge.

Notre charge de pointe est de l'ordre de 1000 inserts par seconde, sur quelques tables.

Nos recherches jusqu'à présent indiquent que - la vitesse de génération de séquence ne devrait pas être un problème - la fragmentation de la séquence (lacunes) se produira, mais ne devrait pas être un problème - l'épuisement des identifiants ne sera pas un problème

Nous ne savons pas si nous manquons d'autres grandes choses. Nous serions reconnaissants pour les opinions des gens, en particulier de ceux qui l'ont déjà essayé et qui ont eu des expériences positives ou négatives.

Pour le contexte - nous avons deux motivations principales pour ce faire.

L'une des motivations pour ce faire est de pouvoir définir un tas de dictionnaires (nous les appelons étendues) et d'avoir des mots lisibles par l'homme attribués à ces identifiants, nous voulons donc nous assurer que les identifiants des différents tableaux ne se chevauchent jamais. Ainsi, dans une étendue, l'ID 12345 peut être affecté de la valeur "Vert" et dans une autre, il peut être affecté "Vert". (En fait, nous ne l'utilisons pas pour l'internationalisation, mais nous pourrions un jour).

L'autre motivation est de faciliter la mise en place de plusieurs déploiements sur le terrain et de savoir (en définissant de manière unique le couple de séquence de chaque déploiement des chiffres les plus significatifs) que nos déploiements ne chevaucheront pas les clés primaires. (Comme un GUID lite).

10
Burleigh Bear

Trois problèmes possibles qui me viennent à l'esprit sont:

  1. Avec n'importe quelle ressource partagée, vous créez un goulot d'étranglement potentiel. Mon instinct dit que pour votre charge de pointe, cela ne devrait pas être un problème, mais je vous suggère fortement de comparer une telle solution dans un environnement de production de taille similaire.

  2. Vous attribuez essentiellement un sens aux clés de substitution, ce qui va à l'encontre d'une partie de leur objectif dans la théorie RDB. Une clé de substitution, par sa nature, ne devrait pas avoir de signification au-delà d'être une clé pour identifier les tuples dans cette relation. Si les entités peuvent avoir un sens ensemble et ont donc besoin de clés sans collision, est-il exact qu'elles sont modélisées séparément ou que quelque chose a été omis dans les exigences et/ou la conception du modèle de données?

  3. Vous introduisez un point d'échec potentiel. Que se passe-t-il si un déploiement n'a pas son point de départ de séquence initial défini? Vous avez alors soit une erreur de blocage de déploiement ou les déploiements commencent au même endroit "cassant" votre fonctionnalité. De plus, que ferez-vous si, quelque part en aval, quelqu'un pense que c'est une bonne idée de gérer un déploiement (en production, peut-être qu'une entreprise locataire se défait d'une partie d'elle-même et a besoin de séparer les données). Que se passe-t-il si la graine est en quelque sorte réinitialisée par un mauvais déploiement de mise à niveau ou une autre migration?[0]

Si aucun de ces problèmes ne vous préoccupe, allez-y, l'idée ne va rien casser à l'OMI. Bien sûr, il peut y avoir de meilleures façons même si celui-ci ne se trompe pas en soi.


Lorsque vous dites "UUID-lite", vous indiquez que vous avez déjà pris en compte et actualisé les UUID. Est-ce le cas et, dans l'affirmative, y a-t-il des raisons particulières de décider qu'elles ne conviennent pas à ce projet?

Une des raisons possibles pour ne pas utiliser les UUID est la fragmentation des index, bien que l'importance de cela soit souvent largement surestimée.[1]. La réponse de SQL Server à cela est le "GUID séquentiel" qui est à peu près équivalent à ce que vous proposez si nous actualisons l'attribution de sens aux valeurs clés - peut-être que postgres a un équivalent à cela? Bien sûr, les index toujours croissants peuvent avoir leurs propres problèmes de performances (conflit de dernière page, statistiques d'index de plus en plus obsolètes) dans certaines charges de travail à volume élevé très spécifiques[2].

Un autre argument courant contre les UUID est la longueur de clé: pourquoi utiliser 16 octets par valeur alors que 4 ou 8 suffiront? Si l'unicité est vraiment une propriété utile, cela l'emporte généralement sur les problèmes de taille de clé. Si la taille de la clé est un problème mais que vous êtes heureux d'utiliser un INT 64 bits plutôt que d'avoir besoin de rester à l'intérieur de 32 bits, vous pouvez utiliser votre technique sans ajouter un problème potentiel de conflit de ressources partagées en faisant votre idée de clé de départ. par table[3] en utilisant une INT IDENTITY(<start>, 1) normale[4] définition de la colonne, bien que cela ajoute une complexité de déploiement (une petite quantité, mais certainement pas zéro).

La lisibilité humaine est parfois citée comme un problème, mais cela revient à attribuer un sens aux clés de substitution.

La compressibilité est une préoccupation moins courante, mais que vous pourriez rencontrer. Pour à peu près n'importe quel algorithme de compression, les UUID sont susceptibles de ressembler à des données aléatoires (donc incompressibles), sauf si vous utilisez quelque chose comme les UUID séquentiels de SQL Server. Cela peut être un problème pour un très grand ensemble de liens (ou autre bloc de données) qui contient de nombreux ID d'entité servis à une application sur un réseau lent , ou si vous avez besoin d'utiliser quelque chose comme les fonctionnalités de compression d'index de SQL Server, bien que ces deux questions ne soient essentiellement que reformuler la préoccupation de taille de clé d'une manière légèrement différente et les UUID séquentiels peuvent également aider ici.


[0] cela peut aussi se produire pour les colonnes d'identité normales, mais comme vous utilisez une fonctionnalité moins courante, vous augmentez les chances d'un DBA moins expérimenté après avoir raté le problème si cela se produit une fois que vous êtes en train de faire quelque chose de nouveau et d'excitant autre part!

[1] Je suis un gars de SQL Server, je soupçonne que le problème potentiel est le même dans les postgres mais pour autant que je sache, il peut avoir une disposition d'index différente qui peut atténuer l'effet.

[2] Bien que cela puisse être spécifique à SQL Server, en particulier le dernier des deux exemples que j'ai énumérés

[3] Les deux premiers octets: varient selon la base de données, les deux suivants: varient selon la table, les quatre autres: les bits d'incrémentation

[4] C'est la syntaxe MS SQL Server, la syntaxe postgres peut varier mais vous devez voir ce que je veux dire et être capable de traduire


tl; dr: si vous vous retrouvez à réinventer la roue, assurez-vous que toutes les conceptions existantes vraiment ne conviennent pas avant de commencer à examiner pourquoi un nouveau pourrait ou non être.

5
David Spillett

Nous envisageons d'utiliser une séquence partagée pour attribuer des identifiants aux clés primaires pour toutes les tables de notre base de données. Il y en a environ 100. Seul un couple est inséré fréquemment et régulièrement. Nous voulons exclure que ce soit "une idée terrible pour une raison évidente" avant de passer à la phase de l'essayer et de le tester à pleine charge.

C'est une horrible idée: l'exclure. Utilisez simplement un GUID/UUID. Pourquoi avez-vous écarté cette idée? Dans PostgreSQL, nous utilisons uuid-ossp ,

uuid_generate_v4() Cette fonction génère un UUID version 4, qui est entièrement dérivé de nombres aléatoires.

Comme ça,

CREATE EXTENSION uuid-ossp;
CREATE TABLE f ( f_id uuid DEFAULT uuid_generate_v4() );

Vous faites beaucoup d'hypothèses dans votre réponse pour qu'elle soit valide,

  • la vitesse "ne devrait pas être un problème"
  • les lacunes "ne devraient pas être un problème"
  • l'épuisement id ne se produira pas

Vous n'avez rien à assumer. Que se passe-t-il si vous obtenez un DOS sur l'ID créant un écart énorme et poussant le survol sur un fragment? Pourquoi ne pas simplement utiliser la solution de l'industrie pour ce problème? Il n'est pas clair qu'il y ait un seul inconvénient. C'est probablement tout gagner. Sauf pour quelques octets de stockage.

3
Evan Carroll

J'ai utilisé le modèle que vous avez proposé avec une table d'ID centrale supplémentaire à laquelle toutes les autres ID ont une clé étrangère. Il fonctionnait parfaitement dans un système de production majeur.

Je pense que la vraie raison de cela est que vos identifiants ont une portée au-delà de votre base de données. Par exemple, dans mon exemple, ces identifiants énuméraient des titres financiers et des sociétés uniques. Vous pourriez vous demander, pourquoi ne pas créer un ensemble d'ID pour les sociétés et un deuxième ensemble pour les titres, en tant que clés primaires d'auto-incrémentation sur chaque table? Parce que nous voulions que d'autres enregistrements de séries chronologiques se réfèrent à des titres ou à des sociétés. Ainsi, la table de séries chronologiques à clé étrangère à la table d'ID centrale.

Compte tenu de ce qui précède, un GUID/UUID fonctionnerait également très bien. Cependant, ces formats sont souvent de 128 bits, ce qui peut avoir un impact car ils sont utilisés dans presque tous les index, clés primaires et étrangères. clé dans la base de données, et l'atténuation de leur placement non séquentiel dans la plage totale d'ID peut être délicate, conduisant à des performances de sélection sous-optimales. Notre base de données était très orientée vers la sélection des performances.

Les GUID/UUID ont un avantage: ils sont beaucoup plus faciles à créer des processus de génération fédérés. Autrement dit, vous pouvez avoir plusieurs processus de génération/d'affectation d'ID dans votre entreprise sans coordination, en supposant simplement qu'ils ne se heurteront jamais. Si votre seul processus de génération d'ID se trouve dans votre base de données, cela ne pose pas de problème, mais il convient de le mentionner.

Notez que la génération d'UUID dépend de l'obtention de vos adresses MAC uniques, vous devrez donc faire attention à cela dans un environnement virtuel/conteneur.

0
ThatDataGuy

L'une des motivations pour ce faire est de pouvoir définir un tas de dictionnaires (nous les appelons étendues) et d'avoir des mots lisibles par l'homme attribués à ces identifiants, nous voulons donc nous assurer que les identifiants des différents tableaux ne se chevauchent jamais. Ainsi, dans une étendue, l'ID 12345 peut être affecté de la valeur "Vert" et dans une autre, il peut être affecté "Vert". (En fait, nous ne l'utilisons pas pour l'internationalisation, mais nous pourrions un jour).

À lui seul, je ne laisserais pas cela être la raison du choix d'un design original et fragile. Si vous suivez la route, il n'y aura aucun moyen de profiter des fonctionnalités de la base de données pour garantir l'intégrité référentielle, par exemple. Une manière normalisée traditionnelle de réaliser la même chose aurait des avantages au-delà du RI:

create table tab1(tab1_id serial primary key);
create table tab2(tab2_id serial primary key);
create table scope(scope_id serial primary key, scope_name text);
create table scope_tab1(scope_id integer references scope, tab1_id integer references tab1, val text, primary key(scope_id,tab1_id));
insert into scope(scope_name) values ('English'),('French');
insert into tab1(tab1_id) select generate_series(1,5);
insert into tab2(tab2_id) select generate_series(1,5);
insert into scope_tab1(scope_id,tab1_id,val) values (1,1,'Green'),(2,1,'Verde');
select tab1_id
     , (select val from scope_tab1 where scope_id=1 and tab1_id=tab1.tab1_id) val_s1
     , (select val from scope_tab1 where scope_id=2 and tab1_id=tab1.tab1_id) val_s2
from tab1;
 tab1_id | val_s1 | val_s2 
 ------: | : -- : ----- 
 1 | Vert | Verde 
 2 |  null  |  null  
 3 |  null  |  null  
 4 |  null  |  null  
 5 |  null  |  null 

dbfiddle --- (ici

L'autre motivation est de faciliter la mise en place de plusieurs déploiements sur le terrain et de savoir (en définissant de manière unique le couple de séquence de chaque déploiement des chiffres les plus significatifs) que nos déploiements ne chevaucheront pas les clés primaires. (Comme un GUID lite).

Je dirais, comme d'autres l'ont fait, que l'utilisation d'UUID est beaucoup mieux (c'est-à-dire beaucoup moins sujette aux erreurs) que d'inventer un nouveau UUID-lite.

Je ne pense toujours pas que ce soit votre meilleur pari cependant - vous ne partagez pas, il n'y a donc pas vraiment besoin d'avoir des ID non chevauchants entre les déploiements que je peux voir à partir des informations que vous avez fournies. Vraisemblablement, vous avez d'autres façons d'identifier un déploiement dans une base de données que de regarder les ID dans ces tables.