LindormTable est un moteur de données distribué dans lequel les données sont réparties en fonction des clés primaires. Si la clé primaire d'une table contient plusieurs colonnes, LindormTable utilise les colonnes de gauche à droite pour interroger les données. Une clé primaire mal conçue concentre les lectures et les écritures sur un petit ensemble de partitions, créant des points chauds qui dégradent les performances. Cette rubrique présente les principes de conception de clés primaires efficaces et fournit des exemples pour les charges de travail courantes.
Concepts clés
Requêtes GET par rapport aux requêtes SCAN
La conception de la clé primaire détermine les méthodes de requête disponibles.
| Méthode de requête | Fonctionnement | Exigence |
|---|---|---|
| GET | Recherche ponctuelle par clé primaire | Toutes les colonnes de la clé primaire doivent être spécifiées avec des valeurs explicites |
| SCAN | Analyse de plage sur la clé primaire | La plage doit être spécifiée sur la première colonne de la clé primaire ; les analyses complètes de la table sont rejetées par défaut |
Exemples de requêtes :
-- GET: retrieve a single order
SELECT * FROM table WHERE userid='abc' AND orderid=123
-- SCAN: retrieve a range of orders
SELECT * FROM table WHERE userid='abc' AND 123<orderid<456
-- SCAN with reverse order: retrieve most recent orders first
SELECT * FROM table WHERE userid='abc' AND 123<orderid<456 ORDER BY orderid DESC
Pour les requêtes qui ne peuvent pas être exprimées avec GET ou SCAN, utilisez une table d'index ou un index secondaire. Pour plus d'informations, consultez SELECT.
Principes de conception
Unicité
Différentes versions de ligne partagent la même clé primaire. Par défaut, une requête GET renvoie uniquement la dernière version. Concevez des clés primaires uniques, sauf si vous utilisez intentionnellement le versioning multiple.
Une clé primaire peut être une seule colonne ou une composition de plusieurs colonnes :
[userid]— un enregistrement par utilisateur[userid][orderid]— plusieurs enregistrements par utilisateur, un par commande
Nombre de colonnes et longueur des valeurs
Gardez les clés primaires compactes afin de réduire les coûts de stockage et d'améliorer les performances d'écriture.
Nombre de colonnes : Utilisez 1 à 3 colonnes de clé primaire.
Longueur des valeurs : Privilégiez les valeurs de longueur fixe, telles que les entiers longs. Pour les valeurs de longueur variable, maintenez chaque colonne sous 2 Ko.
Distribution de la première colonne
Les données stockées dans Lindorm sont distribuées en fonction des clés primaires. Si la clé primaire d'une table contient plusieurs colonnes, les données sont distribuées selon les colonnes de gauche à droite. Lorsque les valeurs de la première colonne de la clé primaire sont asymétriques ou augmentent de manière monotone, les écritures s'accumulent sur une seule partition.
Le tableau suivant indique si les types courants de première colonne produisent une distribution uniforme ou asymétrique :
| Première colonne de clé primaire | Distribution | Adapté ? |
|---|---|---|
| ID utilisateur (cardinalité élevée, aléatoire) | Uniforme | Oui |
| ID d'appareil (nombreux appareils, accès équilibré) | Uniforme | Oui |
| Horodatage ou valeur auto-incrémentée | Séquentiel — toutes les écritures vont vers une seule partition | Non |
| Type de commande ou code d'état (peu de valeurs possibles) | Asymétrique — la plupart des écritures vont vers quelques partitions | Non |
| Colonne avec un préfixe partagé | Asymétrique — les valeurs similaires atterrissent dans la même partition | Non |
Si vous devez utiliser un horodatage ou une colonne auto-incrémentée comme première colonne de clé primaire, appliquez un préfixe de hachage pour distribuer les écritures entre les partitions. Consultez la section Éviter les points chauds.
Éviter les points chauds
Lorsque la première colonne de la clé primaire augmente de manière monotone ou présente une faible cardinalité, utilisez l'une des techniques suivantes pour distribuer les écritures de manière uniforme. Chaque technique présente un compromis différent pour les lectures.
Préfixe de hachage
Préfixez la clé originale avec son hachage afin de disperser les écritures entre les partitions. Créez une colonne dérivée pk1 en utilisant :
pk1 = hash(pk).substring(0, 4) + pk
Les quatre premiers caractères du hachage agissent comme un préfixe aléatoire. Les écritures sont réparties uniformément ; les lectures utilisent directement pk1.
Clé primaire : [pk1][...]
Compromis : Pour interroger par la clé pk d'origine, calculez le préfixe de hachage au moment de la requête pour construire pk1.
Préfixe MD5
Une variante courante du préfixe de hachage utilisant MD5 :
Clé primaire : [md5(userid).subStr(0,4)][userId][orderid]
Index inversé
Inversez la représentation sous forme de chaîne de la clé pour briser les préfixes séquentiels :
Clé primaire : [reverse(userid)][orderid]
Compromis : Les analyses de plage sur userid deviennent moins naturelles. Cette méthode fonctionne mieux lorsque les recherches par correspondance exacte dominent.
Bucket modulo
Divisez les écritures en un nombre fixe de buckets à l'aide de l'arithmétique modulo. Attribuez chaque ligne à un bucket en fonction d'une valeur augmentant de manière monotone :
long bucket = timestamp % numBuckets
Clé primaire : [bucket][timestamp][hostname][log-event]
Compromis : Pour interroger toutes les données d'une plage horaire, exécutez une requête par bucket et fusionnez les résultats.
Salt aléatoire
Ajoutez un nombre aléatoire pour répartir les écritures entre les partitions :
Clé primaire : [userId][orderid][random(100)]
Compromis : Les lectures pour un seul [userId][orderid] nécessitent une analyse sur plusieurs valeurs de salt. N'utilisez ce modèle que lorsque la distribution des écritures est plus critique que la simplicité des lectures.
Simplifier les clés primaires
La réduction de la taille de la clé accélère les écritures et diminue les coûts de stockage. Deux approches courantes :
Remplacer STRING par LONG ou INT. Exemple :
'2015122410'→Long(2015122410).Remplacer les noms longs par des codes courts. Exemple :
'taobao'→'tb'.
Conceptions courantes
Données de journalisation et séries temporelles
| Objectif de la requête | Conception de la clé primaire |
|---|---|
| Interroger tous les enregistrements d'une métrique sur une plage horaire | [hostname][log-event][timestamp] |
| Récupérer les enregistrements les plus récents d'une métrique | [hostname][log-event][timestamp DESC] |
| Interroger des données temporelles avec un volume d'écriture élevé (horodatage sensible) | long bucket = timestamp % numBuckets;[bucket][timestamp][hostname][log-event] |
Pour les requêtes « enregistrements les plus récents », définissez timestamp DESC dans la clé primaire afin que les nouveaux enregistrements soient triés en haut. Une opération SCAN renvoie alors les derniers enregistrements sans tri supplémentaire.
Pour les séries temporelles à fort volume où une seule colonne d'horodatage créerait une partition sensible, le modèle de bucket modulo distribue les écritures. Pour lire tous les enregistrements d'une plage horaire, interrogez chaque bucket séparément et fusionnez les résultats.
Données transactionnelles
Les charges de travail transactionnelles nécessitent généralement plusieurs modèles d'accès — par vendeur, par acheteur et par ID de commande. Concevez des tables distinctes pour chaque modèle d'accès :
| Modèle d'accès | Table | Conception de la clé primaire |
|---|---|---|
| Enregistrements de transactions du vendeur par date | Table Vendeur | [seller_id][timestamp][order_number] |
| Enregistrements de transactions de l'acheteur par date | Table Acheteur | [buyer_id][timestamp][order_number] |
| Recherche de commande par ID de commande | Table Commande | [order_number] |
Joignez les trois tables pour effectuer des requêtes lorsque vous devez accéder aux données via les trois modèles d'accès.