Comme dans de nombreuses bases de données, divise les données en partitions, puis une partition est ensuite divisée en compartiments. Les partitions sont généralement définies par le temps ou d'autres valeurs continues. Cela permet aux moteurs de requêtes de localiser rapidement les données cibles lors des requêtes en éliminant les plages de données non pertinentes.
Le bucketing , quant à lui, distribue les données en fonction des valeurs de hachage d'une ou plusieurs colonnes, ce qui évite la distorsion des données.
: les utilisateurs spécifient les partitions dans l'instruction de création de table ou les modifient ultérieurement via des instructions DDL.
: le système maintient automatiquement les partitions dans une plage prédéfinie en fonction du temps d'ingestion des données.
Dans Apache Doris 2.1.0, nous avons introduit . Il prend en charge le partitionnement des données par RANGE ou par LIST et améliore encore la flexibilité en plus du partitionnement automatique.
Dans Doris, la table de données est divisée en partitions puis en buckets de manière hiérarchique. Les données contenues dans le même compartiment forment ensuite une tablette de données, qui constitue l'unité de stockage physique minimale dans Doris pour la réplication des données, la planification des données inter-clusters et l'équilibrage de charge.
CREATE TABLE IF NOT EXISTS example_range_tbl ( `user_id` LARGEINT NOT NULL COMMENT "User ID", `date` DATE NOT NULL COMMENT "Data import date", `timestamp` DATETIME NOT NULL COMMENT "Data import timestamp", `city` VARCHAR(20) COMMENT "Location of user", `age` SMALLINT COMMENT "Age of user", `sex` TINYINT COMMENT "Sex of user", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "Last visit date of user", `cost` BIGINT SUM DEFAULT "0" COMMENT "User consumption", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "Maximum dwell time of user", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "Minimum dwell time of 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"), PARTITION `p2018` VALUES [("2018-01-01"), ("2019-01-01")) ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 PROPERTIES ( "replication_num" = "1" );
La table est partitionnée par date
d'importation des données et 4 partitions ont été pré-créées. Au sein de chaque partition, les données sont ensuite divisées en 16 compartiments en fonction de la valeur de hachage du user_id
.
Avec cette conception de partitionnement et de compartimentage, lors de l'interrogation de données à partir de 2018, le système n'a besoin que d'analyser la partition p2018
. Voici à quoi ressemble la requête SQL :
mysql> desc select count() from example_range_tbl where date >= '20180101'; +--------------------------------------------------------------------------------------+ | Explain String(Nereids Planner) | +--------------------------------------------------------------------------------------+ | PLAN FRAGMENT 0 | | OUTPUT EXPRS: | | count(*)[#11] | | PARTITION: UNPARTITIONED | | | | ...... | | | | 0:VOlapScanNode(193) | | TABLE: test.example_range_tbl(example_range_tbl), PREAGGREGATION: OFF. | | PREDICATES: (date[#1] >= '2018-01-01') | | partitions=1/4 (p2018), tablets=16/16, tabletList=561490,561492,561494 ... | | cardinality=0, avgRowSize=0.0, numNodes=1 | | pushAggOp=NONE | | | +--------------------------------------------------------------------------------------+
Si les données sont inégalement réparties sur les partitions, le mécanisme de regroupement basé sur le hachage peut diviser davantage les données en fonction de l' user_id
. Cela permet d'éviter un déséquilibre de charge sur certaines machines lors des requêtes et du stockage.
CREATE TABLE `DAILY_TRADE_VALUE` ( `TRADE_DATE` datev2 NOT NULL COMMENT 'Trade date', `TRADE_ID` varchar(40) NOT NULL COMMENT 'Trade ID', ...... ) UNIQUE KEY(`TRADE_DATE`, `TRADE_ID`) PARTITION BY RANGE(`TRADE_DATE`) ( PARTITION p_200001 VALUES [('2000-01-01'), ('2000-02-01')), PARTITION p_200002 VALUES [('2000-02-01'), ('2000-03-01')), PARTITION p_200003 VALUES [('2000-03-01'), ('2000-04-01')), PARTITION p_200004 VALUES [('2000-04-01'), ('2000-05-01')), PARTITION p_200005 VALUES [('2000-05-01'), ('2000-06-01')), PARTITION p_200006 VALUES [('2000-06-01'), ('2000-07-01')), PARTITION p_200007 VALUES [('2000-07-01'), ('2000-08-01')), PARTITION p_200008 VALUES [('2000-08-01'), ('2000-09-01')), PARTITION p_200009 VALUES [('2000-09-01'), ('2000-10-01')), PARTITION p_200010 VALUES [('2000-10-01'), ('2000-11-01')), PARTITION p_200011 VALUES [('2000-11-01'), ('2000-12-01')), PARTITION p_200012 VALUES [('2000-12-01'), ('2001-01-01')), PARTITION p_200101 VALUES [('2001-01-01'), ('2001-02-01')), ...... ) DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 PROPERTIES ( ...... );
Ceci est un exemple d'instruction CREATE TABLE pour une table partitionnée par jour. Les paramètres start
et end
sont respectivement définis sur -7
et 3
, ce qui signifie que les partitions de données pour les 3 prochains jours seront pré-créées et que les partitions historiques datant de plus de 7 jours seront récupérées.
CREATE TABLE `DAILY_TRADE_VALUE` ( `TRADE_DATE` datev2 NOT NULL COMMENT 'Trade date', `TRADE_ID` varchar(40) NOT NULL COMMENT 'Trade ID', ...... ) UNIQUE KEY(`TRADE_DATE`, `TRADE_ID`) PARTITION BY RANGE(`TRADE_DATE`) () DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 PROPERTIES ( "dynamic_partition.enable" = "true", "dynamic_partition.time_unit" = "DAY", "dynamic_partition.start" = "-7", "dynamic_partition.end" = "3", "dynamic_partition.prefix" = "p", "dynamic_partition.buckets" = "10" );
Au fil du temps, la table conservera toujours les partitions dans la plage [current date - 7, current date + 3]
. La partition dynamique est particulièrement utile pour les scénarios d'ingestion de données en temps réel, par exemple lorsque la couche ODS (Operational Data Store) reçoit directement des données de sources externes comme Kafka.
Les paramètres start
et end
définissent une plage fixe pour les partitions, permettant à l'utilisateur de gérer les partitions uniquement dans cette plage. Cependant, si l'utilisateur doit inclure davantage de données historiques, il devra composer la valeur start
, ce qui pourrait entraîner une surcharge inutile des métadonnées dans le cluster.
Le premier est synonyme d’automatisation et le second de flexibilité. L’essence de la réalisation des deux consiste à associer la création de partition aux données réelles.
AUTO PARTITION BY RANGE (FUNC_CALL_EXPR) () FUNC_CALL_EXPR ::= DATE_TRUNC ( <partition_column>, '<interval>' )
La <partition_column>
ci-dessus est la colonne de partition (c'est-à-dire la colonne sur laquelle le partitionnement est basé). <interval>
spécifie l'unité de partition, qui est la largeur souhaitée de chaque partition.
Par exemple, si la colonne de partition est k0
et que vous souhaitez partitionner par mois, l'instruction de partition serait AUTO PARTITION BY RANGE (DATE_TRUNC(k0, 'month'))
. Pour toutes les données importées, le système appellera DATE_TRUNC(k0, 'month')
pour calculer le point final gauche de la partition, puis le point final droit en ajoutant un interval
.
Maintenant, nous pouvons appliquer la partition automatique à la table DAILY_TRADE_VALUE
introduite dans la section précédente sur la partition dynamique.
CREATE TABLE DAILY_TRADE_VALUE ( `TRADE_DATE` DATEV2 NOT NULL COMMENT 'Trade Date', `TRADE_ID` VARCHAR(40) NOT NULL COMMENT 'Trade ID', ...... ) AUTO PARTITION BY RANGE (DATE_TRUNC(`TRADE_DATE`, 'month')) () DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 PROPERTIES ( ...... );
mysql> show partitions from DAILY_TRADE_VALUE; Empty set (0.10 sec) mysql> insert into DAILY_TRADE_VALUE values ('2015-01-01', 1), ('2020-01-01', 2), ('2024-03-05', 10000), ('2024-03-06', 10001); Query OK, 4 rows affected (0.24 sec) {'label':'label_2a7353a3f991400e_ae731988fa2bc568', 'status':'VISIBLE', 'txnId':'85097'} mysql> show partitions from DAILY_TRADE_VALUE; +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 588395 | p200 | 2 | 2024-06-01 19:02:40 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2015-01-01]; ..types: [DATEV2]; keys: [2015-02-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 588437 | p20200101000000 | 2 | 2024-06-01 19:02:40 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2020-01-01]; ..types: [DATEV2]; keys: [2020-02-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 588416 | p20240301000000 | 2 | 2024-06-01 19:02:40 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2024-03-01]; ..types: [DATEV2]; keys: [2024-04-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.09 sec)
La partition automatique par LIST consiste à partager des données en fonction de dimensions non temporelles, telles que region
et department
. Il comble cette lacune pour la partition dynamique, qui ne prend pas en charge le partitionnement des données par LIST.
AUTO PARTITION BY LIST (`partition_col`) ()
Ceci est un exemple de partition automatique par LISTE utilisant city
comme colonne de partition :
mysql> CREATE TABLE `str_table` ( -> `city` VARCHAR NOT NULL, -> ...... -> ) -> DUPLICATE KEY(`city`) -> AUTO PARTITION BY LIST (`city`) -> () -> DISTRIBUTED BY HASH(`city`) BUCKETS 10 -> PROPERTIES ( -> ...... -> ); Query OK, 0 rows affected (0.09 sec) mysql> insert into str_table values ("Denver"), ("Boston"), ("Los_Angeles"); Query OK, 3 rows affected (0.25 sec) mysql> show partitions from str_table; +-------------+-----------------+----------------+---------------------+--------+--------------+-------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+-----------------+----------------+---------------------+--------+--------------+-------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 589685 | pDenver7 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Denver]; ] | city | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 589643 | pLos5fAngeles11 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Los_Angeles]; ] | city | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 589664 | pBoston8 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Boston]; ] | city | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+-----------------+----------------+---------------------+--------+--------------+-------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.10 sec)
Ajuster manuellement les partitions historiques
Pour les tables qui reçoivent à la fois des données en temps réel et des mises à jour historiques occasionnelles, étant donné que la partition automatique ne récupère pas automatiquement les partitions historiques, nous recommandons deux options :
Utilisez la partition automatique et créez manuellement une partition LESS THAN
pour prendre en charge les mises à jour historiques. Cela permet une séparation plus claire des données historiques et en temps réel et facilite la gestion des données.
mysql> CREATE TABLE DAILY_TRADE_VALUE -> ( -> `TRADE_DATE` DATEV2 NOT NULL COMMENT 'Trade Date', -> `TRADE_ID` VARCHAR(40) NOT NULL COMMENT 'Trade ID' -> ) -> AUTO PARTITION BY RANGE (DATE_TRUNC(`TRADE_DATE`, 'DAY')) -> ( -> PARTITION `pHistory` VALUES LESS THAN ("2024-01-01") -> ) -> DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 -> PROPERTIES -> ( -> "replication_num" = "1" -> ); Query OK, 0 rows affected (0.11 sec) mysql> insert into DAILY_TRADE_VALUE values ('2015-01-01', 1), ('2020-01-01', 2), ('2024-03-05', 10000), ('2024-03-06', 10001); Query OK, 4 rows affected (0.25 sec) {'label':'label_96dc3d20c6974f4a_946bc1a674d24733', 'status':'VISIBLE', 'txnId':'85092'} mysql> show partitions from DAILY_TRADE_VALUE; +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 577871 | pHistory | 2 | 2024-06-01 08:53:49 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [0000-01-01]; ..types: [DATEV2]; keys: [2024-01-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577940 | p20240305000000 | 2 | 2024-06-01 08:53:49 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2024-03-05]; ..types: [DATEV2]; keys: [2024-03-06]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577919 | p20240306000000 | 2 | 2024-06-01 08:53:49 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2024-03-06]; ..types: [DATEV2]; keys: [2024-03-07]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.10 sec)
Partition NULLE
Avec Auto Partition by LIST, Doris prend en charge le stockage des valeurs NULL dans des partitions NULL. Par exemple:
mysql> CREATE TABLE list_nullable -> ( -> `str` varchar NULL -> ) -> AUTO PARTITION BY LIST (`str`) -> () -> DISTRIBUTED BY HASH(`str`) BUCKETS auto -> PROPERTIES -> ( -> "replication_num" = "1" -> ); Query OK, 0 rows affected (0.10 sec) mysql> insert into list_nullable values ('123'), (''), (NULL); Query OK, 3 rows affected (0.24 sec) {'label':'label_f5489769c2f04f0d_bfb65510f9737fff', 'status':'VISIBLE', 'txnId':'85089'} mysql> show partitions from list_nullable; +-------------+---------------+----------------+---------------------+--------+--------------+------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+---------------+----------------+---------------------+--------+--------------+------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 577297 | pX | 2 | 2024-06-01 08:19:21 | NORMAL | str | [types: [VARCHAR]; keys: [NULL]; ] | str | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577276 | p0 | 2 | 2024-06-01 08:19:21 | NORMAL | str | [types: [VARCHAR]; keys: []; ] | str | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577255 | p1233 | 2 | 2024-06-01 08:19:21 | NORMAL | str | [types: [VARCHAR]; keys: [123]; ] | str | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+---------------+----------------+---------------------+--------+--------------+------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.11 sec)
Cependant, la partition automatique par RANGE ne prend pas en charge les partitions NULL, car les valeurs NULL seront stockées dans la plus petite partition LESS THAN
et il est impossible de déterminer de manière fiable la plage appropriée. Si la partition automatique devait créer une partition NULL avec une plage de (-INFINITY, MIN_VALUE), il y aurait un risque que cette partition soit supprimée par inadvertance en production, car la limite MIN_VALUE peut ne pas représenter avec précision la logique métier prévue.
La partition automatique par LIST prend en charge le partitionnement basé sur plusieurs colonnes , mais chaque partition créée automatiquement ne contient qu'une seule valeur et le nom de la partition ne peut pas dépasser 50 caractères. Notez que les noms de partition suivent des conventions de dénomination spécifiques, qui ont des implications particulières pour la gestion des métadonnées. Cela signifie que la totalité de l'espace de 50 caractères n'est pas à la disposition de l'utilisateur.
La partition automatique par RANGE ne prend en charge qu'une seule colonne de partition , qui doit être de type DATE ou DATETIME .
La partition automatique par LIST prend en charge la colonne de partition NULLABLE et l'insertion de valeurs NULL. La partition automatique par RANGE ne prend pas en charge la colonne de partition NULLABLE.
Auparavant, sans partition automatique, lorsqu'une table ne possède pas la partition requise, le comportement dans Doris consistait pour les nœuds BE à accumuler des erreurs jusqu'à ce qu'un DATA_QUALITY_ERROR
soit signalé. Désormais, avec la partition automatique activée, une requête sera adressée au Doris Frontend pour créer la partition nécessaire à la volée. Une fois la transaction de création de partition terminée, le Doris Frontend répond au coordinateur, qui ouvre ensuite les canaux de communication correspondants (Node Channel et Tablets Channel) pour poursuivre le processus d'ingestion de données. Il s’agit d’une expérience transparente pour les utilisateurs.
Cas 1 : 1 Frontend + 3 Backend ; 6 ensembles de données générés aléatoirement, chacun comportant 100 millions de lignes et 2 000 partitions ; ingéré les 6 ensembles de données simultanément dans 6 tables
Objectif : Évaluer les performances de Auto Partition sous haute pression et vérifier toute dégradation des performances.
Résultats : La partition automatique entraîne une perte de performances moyenne inférieure à 5 % , toutes les transactions d'importation s'exécutant de manière stable.
Cas 2 : 1 Frontend + 3 Backend ; ingérer 100 lignes par seconde à partir de Flink par routine Load ; tests avec 1, 10 et 20 transactions simultanées (tables), respectivement
Objectif : Identifier tout problème potentiel de retard de données qui pourrait survenir avec la partition automatique sous différents niveaux de concurrence.
Résultats : avec ou sans la partition automatique activée, l'ingestion des données a réussi sans aucun problème de contre-pression sur tous les niveaux de concurrence testés, même avec 20 transactions simultanées lorsque l'utilisation du processeur a atteint près de 100 %.