Comment concevoir une base de données pour stocker une liste triée?

42

Je cherche à stocker une liste triée dans une base de données. Je souhaite effectuer efficacement les opérations suivantes.

  1. Insert (x) - Insère l'enregistrement x dans la table
  2. Supprimer (x) - Supprimer l'enregistrement x de la table
  3. Before (x, n) - Renvoie les enregistrements 'n' précédant l'enregistrement x dans la liste triée.
  4. After (x, n) - Renvoie les enregistrements 'n' qui succèdent à l'enregistrement x dans la liste triée.
  5. Premier (n) - Renvoie les premiers n enregistrements de la liste triée.
  6. Dernier (n) - Renvoie les derniers n enregistrements de la liste triée.
  7. Comparer (x, y) - Deux enregistrements x et y de la table, trouver si x> y.

La méthode simple à laquelle je pouvais penser est de stocker une sorte d'attribut 'rang' dans la table et d'interroger en triant sur cet attribut. Mais dans cette méthode, insérer / modifier un enregistrement avec un rang devient une opération coûteuse. Y a-t-il une meilleure méthode?

Plus précisément, je cherche à implémenter la table en utilisant SimpleDB d'Amazon. Mais une réponse générale à une base de données relationnelle devrait également être utile.

Mise à jour sur le profil de charge:

Comme je prévois cela pour une application Web, cela dépend du nombre d'utilisateurs qui l'utilisent.

S'il y a 100 000 utilisateurs actifs (super optimisme: P), alors mon estimation très approximative par jour serait

500 000 sélections, 100 000 insertions et suppressions, 500 000 mises à jour

Je m'attendrais à ce que la table atteigne 500k au total.

Je cherche à optimiser les opérations de mise à jour, d’insertion et de comparaison. Le classement des objets changera constamment et je dois tenir le tableau à jour.

chitti
la source
Élaborez un peu sur votre profil de charge prévu. Combien de sélections / insertions / mises à jour par jour? Pour quelles opérations voulez-vous optimiser le plus? De quelle taille pensez-vous que la table s'agrandit ou gagne au total?
Nick Chammas
Est-ce pour un tableau de classement des joueurs? Quoi qu'il en soit, j'ai mis à jour ma réponse ci-dessous avec des commentaires basés sur votre profil de charge projeté.
Nick Chammas le
non ce n'est pas un tableau de classement des joueurs.
Chitti
Quelle approche avez-vous fini par utiliser?
Nick Chammas
Je ne suis même pas sûr de ce qui est demandé ici ou de ce que vous n'avez pas besoin de faire parmi la liste de tâches que vous devez faire.
Evan Carroll

Réponses:

22

Si le classement n'est pas complètement arbitraire, mais qu'il peut être déduit d'une autre propriété (par exemple, nom, score du joueur, etc.), examinez attentivement la réponse de Joel .

S'il s'agit d' une propriété arbitraire de vos données, elle doit être stockée sous forme de colonne dans votre table d'enregistrements. En supposant que SimpleDB d'Amazon soit similaire au SGBDR classique, vous pouvez alors indexer cette colonne et satisfaire rapidement toutes vos requêtes ci-dessus avec la stratégie d'indexation appropriée. Ceci est normal pour un SGBDR.

Étant donné que vous vous attendez à une activité d'insertion et de mise à jour élevée, mais également à une activité de lecture relativement élevée, je vous recommande de procéder comme suit:

  • Cluster la table sur le rang, surtout si la grande majorité de vos requêtes sont contre rang. Si ce n'est pas le cas, ou si le choix d'une clé de cluster n'est pas disponible dans SimpleDB, créez simplement un index avec rang comme colonne de tête. Cela satisferait les requêtes 3 à 6.
  • Un index sur l’enregistrement d’abord, puis un rang (ou, dans le monde SQL Server, un INCLUDErang et un rang, ou simplement un enregistrement si vous avez mis en cluster sur un rang) satisferaient la requête 7.
  • Les opérations 1 et 2 peuvent être optimisées en espaçant vos données de manière appropriée (c.-à-d. En définissant le FILLFACTORdans SQL Server). Ceci est particulièrement important si vous vous regroupez sur un rang.
  • Au fur et à mesure que vous insérez ou mettez à jour des rangs, maintenez le plus possible l’écart entre les numéros de rangs afin de minimiser la possibilité que vous deviez reclasser un enregistrement existant pour permettre l’insertion ou la mise à jour de rangs. Par exemple, si vous classez vos enregistrements par pas de 1 000, vous laissez assez de place pour environ la moitié du nombre de modifications et d'insertions avec un minimum de chance, vous devrez redéfinir le classement d'un enregistrement qui n'est pas directement impliqué dans ces modifications.
  • Chaque nuit, re-classer tous les enregistrements pour réinitialiser les écarts de classement entre eux.
  • Vous pouvez ajuster la fréquence des reclassements en masse ainsi que la taille de l'écart entre les classements afin de l'adapter au nombre d'insertions ou de mises à jour prévu par rapport au nombre d'enregistrements existants. Donc, si vous avez 100 000 enregistrements et que vos insertions et mises à jour représentent 10% de ce total, laissez assez de place pour 10 000 nouveaux rangs et procédez à un nouveau classement tous les soirs.
  • Ré-classer 500 000 enregistrements est une opération coûteuse, mais une fois par jour ou par semaine en dehors des heures de bureau devrait convenir à une base de données comme celle-là. Ce reclassement de masse en dehors des heures de travail pour maintenir les écarts de classement vous évite de devoir reclasser de nombreux enregistrements pour chaque mise à jour ou insertion de classement pendant vos heures normales et de pointe.

Si vous vous attendez à plus de 100 000 lectures sur une table de taille supérieure à 100 000, je vous déconseille d'utiliser la méthode de la liste chaînée. Il ne sera pas adapté à ces tailles.

Nick Chammas
la source
Les rangs sont modifiables. Je m'attends à ce que les rangs changent constamment et que de nouveaux records soient insérés constamment. Je suis inquiet à propos du cas où j'insère un nouvel élément avec un rang, puis les rangs de tous les enregistrements situés en dessous du nouvel enregistrement dans l'ordre de tri doivent être modifiés. N'est-ce pas une opération coûteuse lorsque j'ai des milliers d'enregistrements dans ma base de données?
Chitti
@ Chitti - Ah, c'est une préoccupation. Vous pouvez espacer votre classement (par exemple 0, 1000, 2000, 3000, ...) et reclasser périodiquement tous les enregistrements à mesure que les écarts de classement se comblent. Cela ne sera cependant pas adapté si vous attendez beaucoup plus que quelques dizaines de milliers d'enregistrements.
Nick Chammas
1
@ Chitti - C'est un peu drôle, en fait. C’est exactement le problème que les moteurs de base de données rencontrent lors de l’indexation des données, car ils les commandent et les réorganisent au fur et à mesure que les données sont ajoutées ou modifiées. Si vous recherchez, FILLFACTORvous constaterez qu'il est essentiellement conçu pour créer cet espace supplémentaire pour les enregistrements d'un index, tout comme les espaces de classement que j'ai décrits créent un espace pour les modifications de classement et les insertions.
Nick Chammas
2
Merci pour la réponse mise à jour. Le «rang» est une propriété arbitraire de mes données. Je suis presque convaincu qu'une colonne d'index personnalisée est ce dont j'ai besoin. Consultez ce lien SO avec une question similaire. La première réponse fournit des recommandations sur la façon de gérer une telle colonne de classement.
Chitti
@ Chitti - La réponse acceptée à cette question SO est excellente. Cela suggère la même approche que celle que j'ai détaillée ici, avec la suggestion supplémentaire d'utiliser des décimales plutôt que des entiers pour augmenter considérablement votre flexibilité dans l'attribution et le changement de rangs. Super trouvaille.
Nick Chammas
13

J'utilise généralement la méthode "rang" que vous décrivez. Plutôt que de perdre du temps avec la mise à jour des lignes lorsque des éléments devaient être réorganisés, j'ai souvent réussi à supprimer tous les enregistrements de la liste et à réinsérer de nouveaux éléments dans le bon ordre. Cette méthode est clairement optimisée pour la récupération.

Une autre approche consisterait à modéliser les enregistrements sous forme de liste chaînée en utilisant une colonne de clé étrangère réflexive "prédécesseur" sur la table:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

Vous pouvez facilement récupérer une liste et ajouter et supprimer des éléments avec un léger surcoût, mais il sera difficile de sortir les enregistrements dans le bon ordre. Peut-être y a-t-il une façon intelligente de le faire en une seule requête, probablement avec beaucoup de jointures de table avec alias.

J'utilise souvent cette dernière approche lorsque je modélise une relation en arborescence (catégories, dossiers, ensembles et sous-ensembles). J'ai généralement eu une fonction récursive pour reconstruire l'arborescence complète dans mon application.

Bpanulla
la source
2
Le modèle de liste chaînée est soigné. Pour récupérer une telle hiérarchie dans l'ordre dans SQL Server, vous utiliseriez un CTE récursif .
Nick Chammas
Construire cette hiérarchie serait cependant assez coûteux pour une grande table. L'avantage est que les changements de rangs / insertions / etc. peuvent être effectués facilement. En fonction du profil de charge attendu de chitti, cela peut être la meilleure approche.
Nick Chammas
L'option de liste liée semble être la meilleure idée pour toutes les opérations sauf Comparer. Avez-vous une idée de la manière dont je mettrais en œuvre Compare sans avoir à tracer le chemin entre les deux éléments comparés?
Chitti
Si vous avez les identifiants des éléments, je pense que Compare () serait simple, à moins que je ne comprenne mal ce que vous entendiez par Compare (). Lorsque vous avez dit: "trouver si x> y" vouliez-vous dire "trouver si x précède y"? Je ne vois pas cela facile sans un index personnalisé ou une procédure stockée qui parcourrait la liste (ou cette fonctionnalité CTE intéressante mentionnée par @Nick).
bpanulla
5
Ce type de solution se rapproche également d’un modèle de données graphique ( en.wikipedia.org/wiki/Graph_theory ). Un système de stockage optimisé pour stocker les nœuds de graphe et les arêtes peut constituer une meilleure solution qu'un SGBDR. Les triples et quadri-magasins et les bases de données graphiques comme Neo4J sont très bons à cet égard.
bpanulla
6

Je pense que la chose à faire est de stocker la ou les propriétés qui sont utilisées pour calculer le rang , puis de construire un index sur elles. Plutôt que d'essayer de forcer la base de données à stocker physiquement les données dans un ordre hiérarchisé ou à l'aide d'une liste chaînée gérée manuellement, pourquoi ne pas laisser le moteur de base de données faire ce pour quoi il a été conçu?

Joel Brown
la source
2
Et si les «propriétés utilisées pour calculer le rang» sont arbitraires? Ex.: Un ensemble d'entrées de panier d'achat qui sont réorganisées en fonction des actions arbitraires de l'utilisateur.
Chitti
Quand vous dites que le rang est arbitraire, que voulez-vous dire? Vous devez utiliser un algorithme pour calculer le rang. Par exemple: "basé sur les entrées du panier" - Basé comment? Il doit y avoir quelque chose stocké dans la base de données qui est le pilote pour le calcul du rang. Il peut s'agir d'une combinaison de plusieurs éléments, mais ces éléments doivent en quelque sorte être stockés dans la table client ou dans des tables liées au client. Si cela se trouve dans les données, vous pouvez créer une fonction qui le calcule. Si vous pouvez le calculer, vous pouvez le stocker et l'indexer.
Joel Brown
Supposons qu'il soit nécessaire de maintenir l'ordre des articles dans un panier d'achat et que l'ordre puisse être modifié «arbitrairement» par l'utilisateur à l'aide d'une interface Web. Comment stockeriez-vous une telle liste d'articles dans une base de données et comment géreriez-vous l'ordre de tri?
Chitti
Si je vous ai bien compris, vous entendez par "changer arbitrairement" l'ordre des articles dans un panier, l'utilisateur pouvant faire glisser les articles de haut en bas dans une liste et les déposer où ils le souhaitent. Je suppose que cela me semble un peu artificiel. Pourquoi les utilisateurs feraient-ils cela? S'ils pouvaient le faire, le feraient-ils souvent? L’utilisation d’une simple séquence d’articles dans un chariot est-elle vraiment un problème de performances? Il me semble qu'un numéro d'ordre allant de un au nombre d'articles dans le panier + le FK à la commande vous donnerait l'index dont vous avez besoin. Il suffit de mettre à jour les éléments quand on est traîné.
Joel Brown
3
Le panier d'achat n'est qu'un exemple que j'ai donné pour montrer qu'il existe des cas où le «rang» peut être arbitraire. Peut-être que ce n'était pas un bon exemple. La file d'attente de DVD Netflix peut être un meilleur exemple. Juste pour les besoins de l’argumentation, imaginez une file d’attente Netflix avec 100 000 éléments pouvant être réorganisés de manière arbitraire par l’utilisateur et il le fait toutes les minutes. Comment pourriez-vous concevoir une base de données pour stocker cette liste ordonnée de films dans cette application hypothétique?
Chitti
1

Ce sont les limitations d'un système non-SGBDR tel que simpleDB. Les fonctionnalités dont vous avez besoin ne peuvent pas être implémentées du côté de la base de données dans simpleDB, elles doivent être implémentées du côté de la programmation / de l'application.

Pour un SGBDR SQL server, les fonctionnalités dont vous avez besoin sont rudimentaires pour l’index clusterisé.

  • Insérer (x) - Insérer l'enregistrement x dans le tableau> Insertion simple.
  • Supprimer (x) - Supprimer l'enregistrement x de la table> Suppression simple.
  • Before (x, n) - Renvoie les enregistrements 'n' précédant l'enregistrement x dans la liste triée. > Sélectionnez les n premiers résultats où x est inférieur à valeur et commande par clause.

  • After (x, n) - Renvoie les enregistrements 'n' qui succèdent à l'enregistrement x dans la liste triée. > Sélectionnez les n premiers résultats où x est supérieur à valeur et commande par clause.

  • Premier (n) - Renvoie les premiers n enregistrements de la liste triée. > Sélectionnez les n premiers résultats.

  • Dernier (n) - Renvoie les derniers n enregistrements de la liste triée. > Sélectionnez les n premiers résultats après ordre en desc.

  • Comparer (x, y) - Deux enregistrements x et y de la table, trouver si x> y. > Instruction TSQL IF.
StanleyJohns
la source
SimpleDB fournit des index automatiques, un tri et un langage de requête simple . Mon problème restera même si je choisis un SGBDR. Le problème vient du fait que le classement des données dans ma base de données change arbitrairement et qu'elles ne peuvent pas être capturées en tant que propriété unique (à moins que j'utilise une colonne de classement personnalisée) pouvant être indexée.
Chitti
0

Voici ce que j'avais l'habitude de re-classer ma table Postgres après chaque insertion:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

Pour mon cas d'utilisation, la performance n'est pas une préoccupation, mais il est important de pouvoir compter sur sa capacité à ne jamais casser ou à agir bizarrement.

marque
la source