Dans ApsaraDB for SelectDB, le partitionnement et le bucketing divisent les données en plages gérables et les répartissent sur les nœuds pour optimiser le stockage et le traitement.
Vue d'ensemble
Pour stocker et traiter efficacement de grands volumes de données, ApsaraDB for SelectDB découpe les données en partitions et les distribue dans le cluster pour un traitement parallèle.
Tous les modèles de données d'ApsaraDB for SelectDB prennent en charge les deux niveaux de partitionnement suivants :
-
Niveau unique : les données ne sont partitionnées que sur un seul niveau.
Si vous créez une table sans spécifier de partitions, ApsaraDB for SelectDB génère une partition par défaut transparente. Seul le bucketing est alors pris en charge.
-
Deux niveaux : les données sont partitionnées sur deux niveaux.
Le premier niveau correspond à la partition, qui prend en charge le partitionnement par plage (range) et par liste (list).
Le second niveau correspond au bucket, également appelé tablet, qui prend en charge le partitionnement par hachage (hash).
Partitionnement
Les partitions divisent les données en plages distinctes, ce qui revient à découper une table en plusieurs sous-tables pour en faciliter la gestion. Tenez compte des points suivants lors de l'utilisation des partitions :
Spécifiez une ou plusieurs colonnes comme clés de partition. Ces colonnes doivent obligatoirement être des colonnes clés (key columns).
Placez toujours les valeurs des clés de partition entre guillemets doubles (« " »), quel que soit le type de la colonne.
Le nombre de partitions créables est théoriquement illimité.
Si vous créez une table sans définir de partitions, le système en génère automatiquement une portant le même nom que la table et contenant l'intégralité des données. Cette partition invisible ne peut être ni supprimée ni modifiée.
Lors de la création d'une partition, assurez-vous que sa plage ne chevauche pas celle d'une autre partition existante.
Partitionnement par plage (Range)
Les colonnes temporelles servent fréquemment de clés pour le partitionnement par plage, car elles simplifient la gestion des données historiques et récentes. Pour les partitions par plage, définissez uniquement la limite supérieure via l'instruction VALUES LESS THAN (...). Le système utilise alors la limite supérieure de la partition précédente comme limite inférieure pour la partition courante, créant ainsi un intervalle fermé à gauche et ouvert à droite. Vous pouvez également définir explicitement les bornes inférieure et supérieure avec l'instruction VALUES [...], ce qui produit aussi un intervalle fermé à gauche et ouvert à droite.
Partitionnement sur une seule colonne
L'exemple ci-dessous illustre l'évolution des plages de partitions lors de l'ajout ou de la suppression de partitions avec l'instruction VALUES LESS THAN (...) :
-
Créez une table nommée test_table.
CREATE TABLE IF NOT EXISTS test_db.test_table ( `user_id` LARGEINT NOT NULL COMMENT "The user ID", `date` DATE NOT NULL COMMENT "The date on which data is imported to the table", `timestamp` DATETIME NOT NULL COMMENT "The time when data is imported to the table", `city` VARCHAR(20) COMMENT "The city in which the user resides", `age` SMALLINT COMMENT "The age of the user", `sex` TINYINT COMMENT "The gender of the user", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "The last time when the user paid a visit", `cost` BIGINT SUM DEFAULT "0" COMMENT "The amount of money that the user spends", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "The maximum dwell time of the user", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "The minimum dwell time of the user" )ENGINE=OLAP AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY RANGE(`date`) ( PARTITION `p201701` VALUES LESS THAN ("2017-02-01"), PARTITION `p201702` VALUES LESS THAN ("2017-03-01"), PARTITION `p201703` VALUES LESS THAN ("2017-04-01") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;Une fois la table
test_tablecréée, les trois partitions suivantes sont automatiquement générées :p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) -
Exécutez l'instruction
ALTER TABLE test_db.test_table ADD PARTITION p201705 VALUES LESS THAN ("2017-06-01");pour ajouter une partition nomméep201705. Le code exemple suivant montre les résultats du partitionnement :p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) p201705: [2017-04-01, 2017-06-01) -
Exécutez l'instruction
ALTER TABLE test_db.test_table DROP PARTITION p201703;pour supprimer la partitionp201703. Le code exemple suivant montre les résultats du partitionnement :p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)ImportantDans l'exemple précédent, après la suppression de la partition p201703, les plages des partitions p201702 et p201705 restent inchangées. Cependant, l'intervalle [2017-03-01,2017-04-01) situé entre ces deux plages devient vacant. Les données existantes dans cet intervalle sont également supprimées. Par conséquent, toute tentative d'importation de données se situant dans cette plage vacante échouera.
-
Supprimez la partition p201702. Le code exemple suivant montre les résultats du partitionnement :
p201701: [MIN_VALUE, 2017-02-01) p201705: [2017-04-01, 2017-06-01)La plage vacante devient alors [2017-02-01,2017-04-01).
-
Exécutez l'instruction
p201702newpour créer une nouvelle partition. Le code exemple suivant montre les résultats du partitionnement :p201701: [MIN_VALUE, 2017-02-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)La plage vacante devient alors [2017-03-01,2017-04-01).
-
Supprimez la partition p201701 et exécutez l'instruction
p201612pour créer une partition. Le code exemple suivant montre les résultats du partitionnement :p201612: [MIN_VALUE, 2017-01-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)Les plages vacantes deviennent [2017-01-01,2017-02-01) et [2017-03-01,2017-04-01).
Comme le montre cet exemple, la suppression de partitions laisse leurs plages voisines inchangées, mais peut créer des intervalles vacants. Lorsque vous utilisez l'instruction VALUES LESS THAN (...) pour ajouter une partition, sa limite inférieure doit être contiguë à la limite supérieure de la partition précédente.
Partitionnement sur plusieurs colonnes
Vous pouvez partitionner les données sur plusieurs colonnes. Exemple :
PARTITION BY RANGE(`date`, `id`)
(
PARTITION `p201701_1000` VALUES LESS THAN ("2017-02-01", "1000"),
PARTITION `p201702_2000` VALUES LESS THAN ("2017-03-01", "2000"),
PARTITION `p201703_all` VALUES LESS THAN ("2017-04-01")
)
Dans cet exemple, les colonnes date et id servent de clés de partition. La colonne date est de type DATE et la colonne id de type INT. Le code exemple suivant montre les résultats du partitionnement :
* p201701_1000: [(MIN_VALUE, MIN_VALUE), ("2017-02-01", "1000") )
* p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") )
* p201703_all: [("2017-03-01", "2000"), ("2017-04-01", MIN_VALUE))
Pour la dernière partition, seule la valeur de la colonne date est spécifiée. Par défaut, le système attribue la valeur MIN_VALUE à la colonne id. Lors de l'insertion de données, le système compare séquentiellement les valeurs aux clés de partition définies afin de déterminer la partition cible. Le code exemple suivant fournit un exemple :
* Data --> Partition
* 2017-01-01, 200 --> p201701_1000
* 2017-01-01, 2000 --> p201701_1000
* 2017-02-01, 100 --> p201701_1000
* 2017-02-01, 2000 --> p201702_2000
* 2017-02-15, 5000 --> p201702_2000
* 2017-03-01, 2000 --> p201703_all
* 2017-03-10, 1 --> p201703_all
* 2017-04-01, 1000 --> Failed to be imported.
* 2017-05-01, 1000 --> Failed to be imported.
Partitionnement par liste (List)
Le partitionnement par liste prend en charge les types de données suivants pour les colonnes clés : BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR et VARCHAR. Les données ne sont importées dans une partition que si elles correspondent exactement à l'une des valeurs énumérées de cette partition.
Utilisez l'instruction VALUES IN (...) pour définir les valeurs d'énumération contenues dans chaque partition.
Partitionnement sur une seule colonne
L'exemple suivant montre comment les partitions évoluent lors de l'ajout ou de la suppression de partitions avec l'instruction VALUES IN (...) :
-
Créez une table nommée test_table1.
CREATE TABLE IF NOT EXISTS test_db.example_list_tbl1 ( `user_id` LARGEINT NOT NULL COMMENT "The user ID", `date` DATE NOT NULL COMMENT "The date on which data is imported to the table", `timestamp` DATETIME NOT NULL COMMENT "The time when data is imported to the table", `city` VARCHAR(20) NOT NULL COMMENT "The city in which the user resides", `age` SMALLINT COMMENT "The age of the user", `sex` TINYINT COMMENT "The gender of the user", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "The last time when the user paid a visit", `cost` BIGINT SUM DEFAULT "0" COMMENT "The amount of money that the user spends", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "The maximum dwell time of the user", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "The minimum dwell time of the user" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY LIST(`city`) ( PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"), PARTITION `p_usa` VALUES IN ("New York", "San Francisco"), PARTITION `p_jp` VALUES IN ("Tokyo") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;Après la création de la table test_table1, les trois partitions suivantes sont automatiquement générées :
p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") -
Exécutez l'instruction
p_ukpour créer une partition. Le code exemple suivant montre les résultats du partitionnement :p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") p_uk: ("London") -
Supprimez la partition p_jp. Le code exemple suivant montre les résultats du partitionnement :
p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_uk: ("London")
Partitionnement sur plusieurs colonnes
Vous pouvez partitionner les données sur plusieurs colonnes. Exemple :
PARTITION BY LIST(`id`, `city`)
(
PARTITION `p1_city` VALUES IN (("1", "Beijing"), ("1", "Shanghai")),
PARTITION `p2_city` VALUES IN (("2", "Beijing"), ("2", "Shanghai")),
PARTITION `p3_city` VALUES IN (("3", "Beijing"), ("3", "Shanghai"))
)
Dans cet exemple, les colonnes id et city servent de clés de partition. La colonne id est de type INT et la colonne city de type VARCHAR. Le code exemple suivant montre les résultats du partitionnement :
* p1_city: [("1", "Beijing"), ("1", "Shanghai")]
* p2_city: [("2", "Beijing"), ("2", "Shanghai")]
* p3_city: [("3", "Beijing"), ("3", "Shanghai")]
Lors de l'insertion de données, le système compare séquentiellement les valeurs aux clés de partition définies afin de déterminer la partition cible. Le code exemple suivant fournit un exemple :
* Data ---> Partition
* 1, Beijing ---> p1_city
* 1, Shanghai ---> p1_city
* 2, Shanghai ---> p2_city
* 3, Beijing ---> p3_city
* 1, Tianjin ---> Failed to be imported.
* 4, Beijing ---> Failed to be imported.
Bucketing
Les données sont réparties dans les buckets selon les valeurs de hachage des colonnes de bucket spécifiées.
En présence de partitions, l'instruction DISTRIBUTED... définit les règles de répartition des données au sein de chaque partition. En l'absence de partitions, elle s'applique à l'ensemble des données de la table.
Plusieurs colonnes peuvent servir de colonnes de bucket. Pour les modèles Aggregate ou Unique, ces colonnes doivent être des colonnes clés. Pour le modèle Duplicate, elles peuvent être des colonnes clés ou des colonnes de valeur. Les colonnes de bucket peuvent être identiques ou différentes des clés de partition.
-
Choisissez les colonnes de bucket en trouvant un équilibre entre le débit de requête et la concurrence des requêtes.
Spécifier plusieurs colonnes de bucket permet une distribution plus uniforme des données. Toutefois, si une requête n'inclut pas de conditions d'égalité pour toutes ces colonnes, le système analyse tous les buckets. Cela augmente le débit et réduit la latence, ce qui convient aux scénarios à fort débit et faible concurrence.
Avec une seule colonne de bucket (ou très peu), une requête ponctuelle n'analyse qu'un seul bucket. Lorsque plusieurs requêtes ponctuelles s'exécutent simultanément, elles peuvent cibler des buckets différents, évitant ainsi toute interférence au niveau des E/S, surtout si les buckets résident sur des disques distincts. Cette configuration est idéale pour les scénarios de requêtes ponctuelles à haute concurrence.
Le nombre de buckets créables est théoriquement illimité.
Bonnes pratiques
Recommandations pour la configuration des partitions et des buckets**
Calculez le nombre total de buckets d'une table selon la formule suivante : Nombre total de buckets = Nombre de partitions × Nombre de buckets par partition.
Si la configuration du cluster reste stable, définissez un nombre de buckets par partition légèrement supérieur au nombre total de disques du cluster.
Visez un volume de 1 à 10 Go de données par bucket. Un volume trop faible réduit l'efficacité de l'agrégation et alourdit la gestion des métadonnées. À l'inverse, un volume excessif ralentit la migration et la reconstitution des réplicas, tout en augmentant le coût des tentatives pour les opérations au niveau du bucket (comme les modifications de schéma ou les rollups).
S'il est impossible de concilier la taille des données par bucket et le nombre de buckets, privilégiez le respect de la taille cible par bucket.
Lors de la création d'une table, chaque partition reçoit le même nombre de buckets. En revanche, l'instruction ADD PARTITION permet de spécifier un nombre de buckets différent pour une nouvelle partition créée dynamiquement. Utilisez cette fonctionnalité pour gérer la contraction ou l'expansion des données.
Le nombre de buckets d'une partition est immuable après sa création. Anticipez donc la mise à l'échelle future du cluster lors de la planification. Par exemple, si votre cluster comporte trois machines dotées d'un seul disque chacune et que vous fixez le nombre de buckets à 3 ou moins, l'ajout de machines supplémentaires n'améliorera pas la concurrence.
Le tableau suivant présente des recommandations de partitionnement et de bucketing pour un cluster composé de 10 backends disposant chacun d'un disque.
|
Taille de la table |
500 Mo |
5 Go |
50 Go |
500 Go |
5 To |
|
Partitions |
Aucune partition nécessaire. |
Aucune partition nécessaire. |
Aucune partition nécessaire. |
Chaque partition fait 50 Go. |
Chaque partition fait 50 Go. |
|
Buckets |
La table contient 4 à 8 buckets. |
La table contient 8 à 16 buckets. |
La table contient 32 buckets. |
Chaque partition contient 16 à 32 buckets. |
Chaque partition contient 16 à 32 buckets. |
Exécutez l'instruction SHOW DATA; pour consulter la taille d'une table.
Configuration et utilisation de la distribution aléatoire
Pour des données détaillées ne nécessitant ni agrégation ni mise à jour, créez une table basée sur le modèle Duplicate avec une distribution aléatoire. Exemple :
CREATE TABLE IF NOT EXISTS test.example_tbl
(
`timestamp` DATETIME NOT NULL COMMENT "The time when the log was generated",
`type` INT NOT NULL COMMENT "The type of the log",
`error_code` INT COMMENT "The error code",
`error_msg` VARCHAR(1024) COMMENT "The error message",
`op_id` BIGINT COMMENT "The owner ID",
`op_time` DATETIME COMMENT "The time when the error was handled"
)
DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY RANDOM BUCKETS 16;
Une table utilisant le modèle Duplicate key ne contient aucune colonne dont le type d'agrégation est REPLACE. Définissez le mode de bucketing sur RANDOM pour éviter les déséquilibres importants de données. Lors de l'importation, chaque tâche écrit les données dans un bucket aléatoire de la partition.
Avec le bucketing RANDOM, aucune colonne de bucket n'étant spécifiée, il est impossible de cibler des buckets précis par valeur. Le système analyse alors tous les buckets de la partition correspondante. Cette approche convient aux requêtes d'agrégation et d'analyse sur table complète, plutôt qu'aux requêtes ponctuelles à forte concurrence.
Pour les tables du modèle Duplicate utilisant la distribution aléatoire, activez le mode d'importation à bucket unique en définissant le paramètre load_to_single_tablet sur true (valeur par défaut : false). Dans ce mode, chaque tâche d'importation n'écrit que dans un seul bucket par partition, ce qui améliore la concurrence et le débit d'importation, réduit l'amplification des écritures due au compactage et préserve la stabilité du cluster.
Scénarios combinant partitions et buckets
Si une table possède des colonnes de dimension temporelle ou des colonnes de dimension à valeurs ordonnées, utilisez-les comme clés de partition. Déterminez la granularité du partitionnement en fonction de la fréquence d'importation et du volume de données à stocker par partition.
Pour supprimer les données historiques et ne conserver que celles des N derniers jours, utilisez le partitionnement composite afin d'éliminer les anciennes partitions. Vous pouvez également exécuter l'instruction DELETE pour purger les données d'une partition spécifique.
Afin d'éviter les déséquilibres de données, spécifiez un nombre de buckets adapté pour chaque partition. Par exemple, dans un scénario de partitionnement journalier où le volume de données varie considérablement d'un jour à l'autre, personnalisez le nombre de buckets par partition. Choisissez de préférence des colonnes de bucket faciles à identifier et garantissant une répartition uniforme des données.