Contrairement aux bases de données relationnelles traditionnelles, les systèmes NoSQL ont souvent des architectures et des mécanismes de requête différents, ce qui influe sur la façon dont le tri est effectué. Une opération de tri mal planifiée peut causer une latence élevée, une consommation accrue de mémoire et un débit dégradé. Pour construire des applications rapides et évolutives, les développeurs doivent comprendre le moteur de stockage sous-jacent, les capacités d'indexation et de tri des primitives disponibles dans leur base de données NoSQL choisie.

Cet article explore les concepts fondamentaux du tri dans les bases de données NoSQL, décrit les stratégies pratiques pour un tri efficace et fournit des conseils pratiques pour optimiser les performances dans les scénarios réels.

Comprendre les modèles de données noSQL et les répercussions de tri

Les bases de données NoSQL sont de plusieurs types : document, valeur clé, colonne-famille et graphique. Chaque modèle stocke les données différemment, et ces différences influent de façon spectaculaire sur la façon dont le tri peut être mis en œuvre efficacement.

Bases de données documentaires

Les bases de données de documents comme MongoDB et Couchbase stockent des données comme des documents JSON, généralement dans les collections. Elles supportent des requêtes riches avec tri, filtrage et agrégation. Le tri dans les bases de données de documents est souvent effectué sur des champs dans les documents. Parce que les documents peuvent avoir des structures imbriquées, le tri sur des sous-champs (p. ex. order.items.price) nécessite une conception d'index soignée.

Magasins de valeurs clés

Les magasins à valeur clé tels que Redis, Amazon DynamoDB (en mode à valeur clé) et Riak sont optimisés pour les simples recherches par clé primaire. Le tri des valeurs n'est pas natif; au contraire, les utilisateurs comptent souvent sur des structures de données triées (par exemple, les ensembles de valeurs triées Redis) ou le tri au niveau de l'application.

Bases de données sur la famille des colonnes

Les bases de données de la famille des colonnes comme Apache Cassandra et HBase stockent les données en lignes avec de nombreuses colonnes, regroupées en familles de colonnes. Le tri est étroitement couplé avec la touche de ligne et les colonnes de regroupement. Cassandra, par exemple, stocke les données sur le disque dans l'ordre défini par le PRIMARY KEY[ (clé de partition + colonnes de regroupement).

Bases de données graphiques

Les bases de données graphiques comme Neo4j ou Amazon Neptune stockent des nœuds et des relations. Le tri se fait généralement sur des propriétés de nœuds ou des propriétés de relations. Les requêtes de graphes traversent souvent des sous-graphes localisés, donc le tri des frais généraux est généralement minimal.

Stratégies de tri efficace

Le tri efficace dans NoSQL dépend de l'alignement de votre approche sur les forces de la base de données. Les stratégies suivantes s'appliquent à différents types de NoSQL, avec des détails spécifiques de mise en œuvre pour chaque système.

Indice de levier

Les index sont le moyen le plus efficace d'accélérer le tri. Lorsqu'une requête comprend une clause sort, la base de données peut lire les données directement dans l'ordre des index triés, évitant ainsi un scan complet et un tri in-memory. La plupart des bases de données NoSQL prennent en charge les index secondaires, bien que leur comportement varie.

  • MongoDB:[ Créer des index composés qui correspondent à la fois au filtre et aux champs de tri. Par exemple, db.collection.createIndex({ status: 1, createdÀ: -1 }) supporte le filtrage par status et le tri par créatedAt en descendant. MongoDB peut utiliser l'index pour le tri tant que le champ de tri fait partie de l'index et que le filtre est un préfixe de l'index.
  • Cassandra: Le tri est implicite par des colonnes de regroupement. Si vous devez trier par une colonne différente, vous devez modéliser les données différemment (par exemple, créer une table séparée avec l'ordre de regroupement souhaité) ou dénormaliser.
  • DynamoDB: Utilisez un indice secondaire local (LSI) ou un indice secondaire global (GSI) avec une clé de tri. Les requêtes peuvent alors spécifier ScanIndexForward pour contrôler l'ordre descendant/ascendant.

Les index sont à un coût : ils nécessitent un stockage et peuvent ralentir les écritures. Choisissez les index avec sagesse, en priorisant les requêtes de tri les plus courantes.

Utiliser les fonctions de tri intégrées

Exploitez les capacités de tri natives de votre base de données. La plupart des langages de requête NoSQL supportent une clause sort[ ou order by[. L'utilisation de ces langages est presque toujours plus rapide que le tri en code d'application car la base de données peut tirer parti des index et effectuer l'opération proche des données.

Par exemple, la méthode MongoDB=sort(), la méthode Couchbase=ORDER BY dans N1QL, et Cassandra="s commande implicite en cluster des colonnes. Même lorsqu'une requête n'utilise pas un index, les routines internes de tri de la base de données="s sont généralement plus efficaces qu'une implémentation d'application naïve.

Trier au niveau de la demande lorsque cela est approprié

Le tri au niveau de l'application doit être un repli, et non un défaut. Cependant, il y a des scénarios où il est logique:

  • L'ensemble de données est déjà petit (par exemple, les résultats paginés d'une requête filtrée).
  • La logique de tri est trop complexe pour la base de données (par exemple, algorithmes de classement personnalisés).
  • La base de données ne dispose pas d'un support de tri natif (p. ex., de nombreux magasins à valeur clé).

Lors du tri dans l'application, récupérer uniquement les données dont vous avez besoin (utiliser limite et projection) et trier en mémoire. Évitez de tirer des collections entières en mémoire juste pour les réorganiser.

Optimiser le schéma de données pour le tri

La conception du schéma a un impact profond sur les performances de tri. Les techniques comprennent:

  • Pré-triage: Écrire les données dans l'ordre désiré. Par exemple, dans Cassandra, choisir les colonnes de regroupement qui correspondent aux exigences communes de tri. Dans MongoDB, vous pouvez utiliser des collections captées ou des horodatages de stockage qui commandent naturellement l'insertion.
  • Dénormalisation:[ Dupliquer les données de façon à les stocker dans l'ordre nécessaire pour une requête spécifique. Cela trade le stockage et écrit les frais généraux pour la vitesse de lecture.
  • Utilisation de tableaux ou de documents intégrés:[ Dans les bases de données de documents, entreposez les sous-réseaux triés (p. ex., les ID de commentaires triés) pour éviter de trier au moment de la lecture.

L'optimisation du schéma doit toujours tenir compte des modèles d'écriture et de la cohérence des données.

Tri des gros ensembles de données : Techniques avancées

Lorsque les ensembles de données dépassent une capacité de nœuds ou dépassent les limites de mémoire, le tri nécessite des stratégies réparties.

Limiter les ensembles de résultats et utiliser la pagination

Limitez toujours le nombre de documents retournés. La plupart des bases de données NoSQL prennent en charge LIMIT[] ou pageSize[ paramètres. Combinées avec des index, cela permet à la base de données de trier uniquement les résultats N supérieurs, évitant une sorte complète de documents correspondants.

Leverage Sharding pour le tri parallèle

Chaque shard peut trier indépendamment sa partie des données, et un coordonnateur fusionne les résultats triés. C'est la base de la stratégie ]sort-merge utilisée dans des systèmes comme MongoDB (avec des clusters durs) et Apache Cassandra (en utilisant le nœud coordinateur).

  • Dans MongoDB, l'opération sort() sur une collection soudée exige que le champ de tri soit inclus dans la clé shard ou que la requête soit acheminée vers un seul shard. Autrement, le routeur (mongos) doit rassembler tous les documents correspondants de chaque shard et les trier en mémoire, qui peut être lent et intensif en mémoire.
  • Dans Cassandra, le tri entre les partitions n'est pas pris en charge dans une seule requête. Vous devez récupérer les données de chaque partition et fusionner au niveau de l'application, ou redessiner le schéma pour éviter le tri entre les partitions.

Lors de l'utilisation de sharding, concevez votre clé durs pour minimiser les opérations de collecte de données pour les requêtes de tri communes.

Employer des pipelines MapReduce ou Agrégation

Les exigences complexes de tri peuvent être traitées par des pipelines MapReduce ou agrégation, qui distribuent les travaux dans l'ensemble du groupe.

  • MongoDB=1] comprend un [phase de triage, qui peut être placée au début du pipeline pour réduire le volume de documents passés aux étapes suivantes. Si un phase de triage suit une phase de triage, assurez-vous que l'indice supporte les deux.
  • Apache Hadoop MapReduce trie les données implicitement pendant la phase de shuffle – les clés sont triées avant d'être passées aux réducteurs. Ceci est utile pour le traitement en vrac mais pas pour les requêtes en temps réel.
  • Apache Spark peut lire à partir de sources NoSQL (par exemple Cassandra via le connecteur Spark) et trier d'énormes ensembles de données entre nœuds en utilisant sa propre gestion de mémoire et partitionnement.

Pour les demandes de renseignements opérationnelles (temps de réponse de la sous-seconde), les pipelines d'agrégation sont préférés à MapReduce, qui est généralement plus lent et plus lourd en ressources.

Meilleures pratiques pour différents systèmes NoSQL

La mise en œuvre d'un tri efficace nécessite des connaissances spécifiques à la base de données. Voici des recommandations concrètes pour les moteurs NoSQL les plus populaires.

MangoDB

  • Toujours indexer les champs sur lesquels vous triez. Utilisez des index composés qui couvrent les filtres de requête et triez l'ordre.
  • Évitez de trier sur des champs à forte cardinalité qui ne font pas partie d'un index composé – la base de données peut revenir à un type in-memory, qui est plafonné par la limite mémoire (32 Mo par défaut).
  • Utiliser le pipeline d'agrégation .$sort après les étapes $match pour minimiser le flux de données.
  • Pour les données de séries chronologiques, utilisez le modèle createIndex({ timestamp: -1 }) – les index décroissants sont idéaux pour les requêtes --les plus récentes.

Cassandra

  • Modélisez vos tables de façon à ce que les colonnes de regroupement correspondent à l'ordre de tri dont vous avez besoin. Vous pouvez avoir plusieurs tables avec des ordres de regroupement différents pour les mêmes données (dénormalisation).
  • Ne pas compter sur ORDER BY – cela permet seulement de réorganiser dans la direction de cluster existante. Vous ne pouvez pas ajouter de nouvelles colonnes pour le tri au moment de la requête.
  • Utilisez des vues matérialisées parcimonieusement : elles créent des tables supplémentaires qui sont automatiquement maintenues, mais elles ajoutent des frais généraux d'écriture et ont des limites connues.
  • Gardez les partitions petites (moins de 100 000 lignes par partition) pour éviter de trier la latence à l'intérieur d'une partition.

DynamoDB

  • Utilisez une clé primaire composite avec une clé de tri (clé de tri) pour l'attribut que vous devez trier. Les requêtes peuvent alors retourner les résultats dans l'ordre ascendant ou descendant.
  • Pour le tri des attributs non clés, créez un GSI avec cet attribut comme clé de tri. Soyez conscient que les GSI sont finalement cohérents et consomment une capacité supplémentaire.
  • Utiliser ScanIndexForward défini à false pour l'ordre décroissant – il est efficace et utilise l'indice.
  • Évitez de trier sur les grands ensembles de résultats ; DynamoDB limite les résultats de la requête à 1 Mo par demande. Implémenter la pagination avec LastEvaluatedKey.

Réviser

  • Les ensembles triés (ZADD, ZRANGE[) sont le mécanisme principal de tri. Ils maintiennent un ordre trié par partition, idéal pour les classements, les séries chronologiques ou tout ordre numérique.
  • Pour les valeurs de chaîne, utilisez la commande SORT, mais elle bloque le serveur et ne doit pas être utilisée sur les grandes listes.
  • Si vous avez besoin de trier des objets complexes, rangez-les comme des hachages avec un ensemble trié d'ID, puis récupérez les objets par ID dans l'ordre trié.

Base de la pochette

  • N1QL prend en charge ORDER BY. Utilisez des index couvrant (index qui incluent tous les champs dans la requête) pour éviter la récupération de documents.
  • Pour l'analyse ad-hoc, utilisez le service d'analyse (un superset de N1QL) qui peut utiliser l'architecture MPP pour le tri des gros ensembles de données.

Les obstacles à éviter

Même les développeurs expérimentés peuvent tomber dans des pièges qui dégradent les performances de tri.

  • Désormais sans index sur une grande collection. Cela force un genre in-memory, qui peut échouer (MongoDB lance une erreur) ou causer une forte latence et une pression mémoire.
  • Utilisation ORDER BY avec une colonne aléatoire dans Cassandra. Cassandra ne prend en charge l'ordre que par le regroupement des colonnes dans l'ordre déclaré.
  • Faire le tri de tous les documents correspondants au niveau de l'application. Toujours filtrer agressivement et utiliser la pagination pour ramener le résultat défini à une taille gérable.
  • Le fait de trier un champ avec une faible sélectivité Un index sur un champ avec une faible cardinalité (p. ex. un booléen) offre peu d'avantage de tri parce que de nombreux documents partagent la même valeur, ce qui entraîne un tri secondaire ou aléatoire des E/S.
  • Ignorer les limites de mémoire. Les bases de données ont souvent des limites difficiles sur la quantité de mémoire autorisée pour le tri. Surveillez ces limites et soit cassez les requêtes en petits lots ou redessiner le schéma.

Conclusion

Le tri efficace des données dans les bases de données NoSQL dépend de la compréhension du modèle de données spécifique et de l'utilisation de techniques d'indexation, de conception de schémas et de traitement appropriées.Il n'existe pas de solution unique pour tous : une stratégie de tri qui fonctionne parfaitement dans MongoDB peut être impossible à Cassandra, et ce qui est trivial dans Redis peut être extrêmement coûteux dans DynamoDB.

Commencez par analyser vos modèles d'accès : quels champs seront triés le plus souvent et quelles sont les tailles de résultats attendus ? De là, concevez votre schéma et index pour soutenir ces modèles nativement. Lorsque les requêtes dépassent les capacités d'un seul noeud, envisagez de diluer, de regrouper des pipelines ou de décharger le tri vers un moteur d'analyse dédié.

Pour plus de détails, consulter la documentation MongoDB trier, Cassandra cluster column order, et le DynamoDB trier guide de conception de clé.