Apache Doris 全新分区策略 Auto Partition 应用场景与功能详解 | Deep Dive系列
编辑:SelectDB 技术团队
在当今数据驱动的时代,如何高效、有序地管理数据库中的海量数据成为挑战。为了处理庞大的数据集,分布式数据库引入了类似分区和分桶策略,通过将数据按特定规则划分成较小的单位并分布到不同节点上,利用并行计算能力以提升处理和分析性能,并加强了数据管理的灵活性。
在 Apache Doris 中,数据划分包含分区和分桶两个层级。分区一般按照时间或其他连续值对数据进行划分,在查询时,通过分区裁剪过滤不必要的范围扫描,提升执行效率,同时极大地方便了对分区数据的增删改等管理操作;分桶则是基于某个或某些列的哈希值将数据分配到不同的桶中,从而有效定位数据、避免数据倾斜。
在 2.1 版本以前,Apache Doris 的分区主要依赖手动分区和动态分区功能(Dynamic Partition)自动创建两种方式:
- 手动创建分区:需要在建表时指定该表包含的分区,或者在使用过程中通过 DDL 语句修改。
- 动态分区:主要支持按照时间维度分区,以建表时的现实时间为标准来维护一个范围内的分区。
这两种方式都有其不够灵活之处,因此我们在 2.1 版本引入了 自动分区(Auto Partition)来拓展分区功能。自动分区同时支持按时间维度的 Range 分区,和支持多种数据类型的 List 分区,按照导入数据的实际分布创建分区,提供了更为灵活的分区创建手段,相比于动态分区,保证流程自动化的前提下极大提升了分区使用的自由度。
分区策略演进
面对数据分布的设计维度时,我们往往更关注分区的规划,因为分区列和分区间隔的选择与实际的数据分布模式强相关,合理的分区设计能够大幅提升表的查询和存储效率。
在 Doris 中,数据表(Table)按照分区(Partition)和分桶(Bucket)两种方式依次划分,最终同一个分桶中的数据形成数据分片(Tablet,可视作 Bucket)。Tablet 是 Doris 中多副本高可用、集群间数据调度与均衡的最小物理存储单位。图示如下:
01 手动创建分区
最常见也最基本的创建方式是手动创建,Doris 支持 Range 和 List 两种分区创建方式。对于日志、交易记录等基础业务场景,数据的时间维度较为明确,我们一般按照时间维度创建 Range 分区,建表语句示例如下:
-- Range Partition
CREATE TABLE IF NOT EXISTS example_range_tbl
(`user_id` LARGEINT NOT NULL COMMENT "用户id",`date` DATE NOT NULL COMMENT "数据灌入日期时间",`timestamp` DATETIME NOT NULL COMMENT "数据灌入的时间戳",`city` VARCHAR(20) COMMENT "用户所在城市",`age` SMALLINT COMMENT "用户年龄",`sex` TINYINT COMMENT "用户性别",`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",`cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费",`max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间",`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间"
)
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"
);
该表按照数据导入日期 date 进行分区,并预先创建了 4 个分区。在每个分区下,又根据 user_id
的哈希值划分成 16 个分桶。此时对 2018 年及以后的数据查询时,根据该表的分区设计,实际我们只需要对 p2018
进行扫描,查询语句如下:
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 |
| |
+--------------------------------------------------------------------------------------+
即使入库的数据集中在某几个分区内,分桶的哈希运算机制也能根据 user_id
的值对数据进行二次划分,避免在查询和存储时对部分机器造成不合理的负载倾斜。
在数据量较少的情况下,手动分区尚能应对,而在实际业务场景中,一个集群可能有上万张分区表,此时管理难度将呈指数级上升。例如:
CREATE TABLE `DAILY_TRADE_VALUE`
(`TRADE_DATE` datev2 NOT NULL COMMENT '交易日期',`TRADE_ID` varchar(40) NOT NULL COMMENT '交易编号',......
)
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 (......
);
该表通过手动、逐月的方式创建分区,每个月都需要手动重复增添下一个分区。这不仅需要管理员定期维护表结构变更,在处理实时数据时,可能还需要更频繁地按天、甚至按小时来划分数据分区,给 DBA 带来了沉重负担。
02 动态分区
因此 Doris 引入了动态分区(Dynamic partition)来处理重复性较高的时间分区需求,自动化创建和回收数据分区。通过指定分区单位、历史分区数量和未来分区数量,让 Doris 根据现实时间自动完成分区的创建和回收。
例如按天为单位创建分区,设置 start
为 -7 ,end
为 3:预创建未来 3 天的数据分区,并自动回收距今超过 7 天的历史数据分区。这一功能的实现依赖 FE 端的固定线程,通过不断轮询,检查当前是否需要创建新分区或回收旧分区,从而定期更新数据表的分区结构,建表语句示例如下:
CREATE TABLE `DAILY_TRADE_VALUE`
(`TRADE_DATE` datev2 NOT NULL COMMENT '交易日期',`TRADE_ID` varchar(40) NOT NULL COMMENT '交易编号',......
)
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"
);
随时间推移,该表将始终保持 [当前日期-7, 当前日期+3]
范围内的分区。对于实时数据收集场景,例如 ODS 层直接从外部数据源(如 Kafka)接收数据时,动态分区功能尤为适用。
由于 start
和 end
参数限定了分区的固定范围,用户只能在此范围内管理分区,若需要包含更久远的历史数据,不得不将start
值调大,而这会导致集群中元数据的不必要浪费。因此,在使用动态分区功能时,需要权衡实时管理的便利性与元数据管理的效率。
数据库分区管理的设计思考
对于更复杂的业务场景来说,动态分区有着明显的局限性:
- 仅支持 Range 分区,而无法支持 List 分区
- 只能应用于现实世界的时间维度,如果数据与现实时间无关则无法使用
- 只能包含 1 个连续分区段,无法容纳该范围以外的分区
这导致在某些特定场景下,无法仅依靠动态分区实现分区管理,例如:
- 当分区的时间维度不再和当前现实时间相关,而是对历史数据进行重放计算。例如处理过往某一年的数据,且需要进行天级别的分区。
- 在当前数据导入过程中,偶尔发生历史数据变更。例如在天级别的分区表中,偶尔导入若干年前的数据,是否需要将动态分区的 start 调整到非常大的级别以容纳这些数据?
基于上述的功能局限,我们开始思考,能否提出一种新的分区方式,进一步提升分区管理的自动化程度、简化数据表的维护工作?分析发现,理想分区的实现是同时满足 2 个条件:
- 建表后无需手动调整分区
- 所有的入库数据都有对应分区
前者是动态分区已经具备的“自动化”能力,后者是希望拥有一种“更加灵活”的分区创建能力。而这种能力的本质,是要求分区创建与实际数据关联。
因此我们开始思考:分区的创建能否从建表时或者日常轮询,延后到数据到达时?从预先构造分区的分布,转为定义“从数据到分区”的映射规则,等数据入库后等待分区容纳时,再根据规则创建对应的分区。这样,相较于手动分区,整个流程都是自动发生的,不再需要人工维护;相较于动态分区,避免了有而无用和用而没有的分区情况。
让分区的创建与实际数据的分布自动关联, 是我们理想分区方式的核心思想。
更灵活便捷的自动分区创建策略
基于以上思考,我们在 Apache Doris 2.1 版本引入了“自动分区”(Auto Partition)功能,不再预先创建分区,而是在数据导入过程中根据设置的规则为创建对应的分区。负责数据处理、分发的 BE 节点会在执行计划的 DataSink 算子中尝试为每行数据找到它所属的 Partition。在以往分区表中,找不到对应分区的新增导入数据将被过滤或直接报错。而在自动分区表中,我们仅需在建表时定义分区创建规则,就可以随数据导入自动生成对应分区。接下来介绍自动分区的具体使用方式。
01 Range 自动分区
Range 自动分区(Auto Range Partition)提供了时间维度上的更优分区方案,弥补了动态分区在调参方面的局限性。它的语法如下:
-- AUTO RANGE PARTITION 语法
AUTO PARTITION BY RANGE (FUNC_CALL_EXPR)
()
FUNC_CALL_EXPR ::= DATE_TRUNC ( <partition_column>, '<interval>' )
其中 <partition_column>
指分区列名,<interval>
指分区单位,也就是希望生成分区的宽度。例如分区列为 k0,按照月级别分区,那么最终的分区描述语句就是 AUTO PARTITION BY RANGE (DATE_TRUNC(k0, 'month'))
。此时对于所有导入数据,我们会调用 (DATE_TRUNC(k0, 'month')
对 k0
计算出分区的左端点,再增加一个 interval
得到分区的右端点。通俗来说就是,此处选定的时间单位是“月”,数据导入后自动创建的分区区间是其所属的自然月。
前文动态分区章节中介绍的 DAILY_TRADE_VALUE
表,通过自动分区功能优化如下:
CREATE TABLE DAILY_TRADE_VALUE
(`TRADE_DATE` DATEV2 NOT NULL COMMENT '交易日期',`TRADE_ID` VARCHAR(40) NOT NULL COMMENT '交易编号',......
)
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 | p20150101000000 | 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)
可以看到,该表在导入数据之后自动创建了数据所属的对应分区,而没有数据的分区则不会自动创建。
02 List 自动分区
List 自动分区(Auto List Partition)用来应对实际业务场景中,非时间维度的数据划分需求,例如事件所属的地域、部门等维度。在 Doris 以往的功能中,List 分区不存在一个近似“动态分区”的自动管理机制,自动分区同时补齐了这一短板。它的语法如下:
-- AUTO LIST PARTITION 语法
AUTO PARTITION BY LIST (`partition_col`)
()
例如,使用一张表的 VARCHAR
列作为分区列,实际含义为条目所属的城市:
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 ("Beijing"), ("Shanghai"), ("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 | pBeijing7 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Beijing]; ] | 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 | pShanghai8 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Shanghai]; ] | 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)
可以看到,插入“北京”、“上海”、“洛杉矶”三个城市名后,结果根据城市名划分了对应分区,而以往只能通过手动的 DDL 语句实现。Auto List Partition 功能的引入在很大程度上降低了自定义分区的维护成本,拓宽了 Doris 的使用自由度。
03 使用技巧与注意事项
手动调整历史分区
对于写入最新实时数据和零散历史变更数据的表,由于 Auto Partition 不会自动回收历史分区,我们推荐两种可能的处理方式:
-
正常使用自动分区功能,为零散数据自动创建分区。相较于动态分区,避免创建冗余的空置分区,极大地节省了元数据使用量。
-
自动分区与手动创建分区相结合,按时间维度创建一个 LESS THAN 分区,容纳历史变更数据,这样可以更清晰地划分历史与实时数据,也为后续的数据管理带来效率上的提升。
mysql> CREATE TABLE DAILY_TRADE_VALUE-> (-> `TRADE_DATE` DATEV2 NOT NULL COMMENT '交易日期',-> `TRADE_ID` VARCHAR(40) NOT NULL COMMENT '交易编号'-> )-> 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)
NULL 值分区
Doris 支持分区表中存储 NULL 值。对于自动分区功能而言,List 分区表的 NULL 值将会存储在真正的 NULL 分区中,例如:
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)
而 Range 自动分区目前并不支持 NULL 值分区。这是因为在 Doris 中,Range 分区的 NULL 值将会存入最小的 LESS THAN 分区,Auto Partition 难以确定该分区应有的范围。如果按照(-INFINITY, MIN_VALUE)
范围创建,则分区有在业务中被误删除的风险。
04 功能总结
自动分区在功能上基本覆盖了动态分区的使用场景,并带来分区规则前置的拓展,大大减轻了DBA 在管理数据时的工作负担。完成分区规则的定义后,大量的分区创建工作将全部由 Doris 自动完成。在使用自动分区前,我们需要先明确相关限制条件,包括:
- LIST 自动分区支持多列分区,每个自动创建的分区仅包含一个分区值,分区名长度不能超过 50。Auto List Partition 中,分区名的创建依赖某种特定的规则,对元数据维护具有特定的含义,长度 50 的分区名,所能包含的数据实际长度可能更短。
- RANGE 自动分区支持单个分区列,分区列类型必须为 DATE 或 DATETIME
- LIST 自动分区支持 NULLABLE 分区列和实际插入 NULL 值;RANGE 自动分区不支持 NULLABLE 分区列
- 自从 Doris 2.1.3 版本开始,自动分区不再支持与动态分区共同使用。动态分区在进行分区回收时,不会考虑分区的创建来源。导致即使是 Auto Partition 自动创建的分区,也有可能被立即回收,导致不易察觉的数据丢失。
性能对比
自动分区和动态分区的功能区别主要体现在创建与删除、支持类型以及性能影响这三个方面。
动态分区通过固定线程创建并定期检查和回收分区,只支持按 RANGE 分区。而自动分区根据特定的分区规则在导入数据时按需创建,在 Range 分区的基础上提供了 LIST 分区支持。总体而言,自动分区在灵活性和节约人力方面都具有显著优势。
在导入数据时,动态分区在导入过程中基本不会影响性能,而自动分区会先检索已有分区,按需自动创建,这中间会存在一定的时间开销。因此我们将在后文展示对于自动分区具体的性能测试结果。
自动分区导入流程详解
接下来,详细介绍 Doris 自动分区导入的技术实现。以 Stream Load 为例,Doris 发起导入时,其中一个 BE 会完成前期的数据处理工作,并将数据发送给对应的 BE。用来处理数据的 BE 被称为 Coordinator,其他 BE 则统称为 Executor。
在 Coordinator 执行流程中,最后一个算子是 Datasink Node。在该算子中,数据需要先确定其对应的分区、分桶以及所在 BE 的位置,才能被成功发送到正确的 BE 节点并存储。为了实现数据传输,Coordinator 与 Executor 之间通过特定的 Channel 建立通信桥梁,发送端称为 Node Channel,与之对应的接收端称为 Tablets Channel。
自动分区主要在寻找数据对应分区这一环节发挥作用,具体工作机制如下:
以往找不到分区时,BE 会累计错误直至报错 DATA_QUALITY_ERROR
,而开启了 Auto Partition 的表,则会在此阶段发起一个新建分区的请求给 FE 并创建对应的分区。Coordinator 等待 FE 完成分区创建的回传结果后,打开分区导入的对应通道,即对应的 Node Channel 和 Tablets Channel,继续完成数据的导入。
经过上述步骤,自动分区即可实现用户侧无感知的分区创建,使导入顺利完成。
在实际集群运行环境中,Coordinator 等待 FE 完成创建分区事务往往面临巨大的时间开销成本。原因是 Thrift RPC 过程产生的固有开销,以及高负载情况下 FE 的锁开销。为了提高数据导入的效率,我们在 Auto Partition 场景中进行了攒批操作,从而大幅减少 RPC 调用次数,这一改进显著提升了数据写入性能
需要注意的是,目前 FE Master 在“创建对应分区”环节完成对应分区的创建事务后,分区即刻可见,但如果导入流程最终失败或被取消,所创建的分区不会被自动回收。
自动分区性能表现
我们基于不同场景对自动分区进行了性能和稳定性测试,具体如下:
场景1: 1FE + 3BE 环境,随机生成数据集,每个数据集 1 亿行数据、涉及 2000 个分区,6 个数据集并行导入 6 张对应表。
结果:对比开启自动分区前后,所有导入事务的耗时都较为平稳,平均性能损耗不足 5%。
场景 2:1FE + 3BE 环境,使用 Flink 数据源每秒采集 100 条数据,通过 Routine Load 进行导入,分别测试在 1、10、20 个事务(表)并发下的导入反压情况。
结果:在以上并发下,开启自动分区前后均能顺利完成数据导入、未出现反压情况,20 个事务并发时 CPU 利用率接近 100%,整体表现极为平稳。
上述测试方法分别对应两个典型场景:
- 在贴近生产环境的高负载场景下,检验 Auto Partition 功能面向集群高压力的情况,是否会发生性能劣化;
- 在 Routine Load 不同并发压力下,检验 Auto Partition 功能是否存在导入瓶颈、数据积压问题;
通过以上真实场景的模拟测试我们发现,开启 Auto Partition 前后对导入性能影响甚微。不论是简单的数据插入、或是生产环境中常见的 Routine Load 消费 Flink 数据源,Auto Partition 都表现出了优异的导入性能和稳定的系统表现。即使面对高负载、集群压力较大的情况,开启 Auto Partition 的导入性能损失仅有 5% 左右,性能表现依旧出色,足以满足实际生产环境的使用需求。
基于 Doris 的自动分区在性能和稳定性方面的出色表现,用户可以放心使用该功能,替代旧有分区方式,简化数据操作流程。
总结与展望
自 Apache Doris 2.1 版本起,自动分区的出现进一步简化了复杂场景下的 DDL 和分区表的维护工作,在我们已发布的版本中,许多用户已经使用该功能简化了工作流程,并且极大的便利了从其他数据库系统迁移到 Doris 的工作,自动分区已成为处理大规模数据和应对高并发场景的理想选择。
不仅如此,我们还将对自动分区功能展开更深入的拓展,以应对更加复杂的数据模型。
对于 Auto Range Partition:
- 当前仅支持时间类型上的划分,未来期望支持更丰富的类型,如数值类型等,
- 通过指定上下界的计算方式,创建对应分区
对于 Auto List Partition:
- 将多个值按特定规则合并到同一分区
这些都是我们未来会考虑的改进方向,欢迎在分区创建方面有需求的同学积极使用并前往 Doris 问答论坛反馈建议,期待与你共建更好的 Apache Doris 社区。
参考文献
- Doris Stream Load 原理解析
- 一文教你玩转 Apache Doris 分区分桶新功能|新版本揭秘
相关文章:

Apache Doris 全新分区策略 Auto Partition 应用场景与功能详解 | Deep Dive系列
编辑:SelectDB 技术团队 在当今数据驱动的时代,如何高效、有序地管理数据库中的海量数据成为挑战。为了处理庞大的数据集,分布式数据库引入了类似分区和分桶策略,通过将数据按特定规则划分成较小的单位并分布到不同节点上&#x…...

【Linux】关于在华为云中开放了端口后仍然无法访问的问题
已在安全组中添加规则: 通过指令: netstat -nltp | head -2 && netstat -nltp | grep 8080 运行结果: 可以看到服务器确实处于监听状态了. 通过指令 telnet 公网ip port 也提示: "正在连接xxx.xx.xx.xxx...无法打开到主机的连接。 在端口 8080: 连接失败"…...

Linux系统ubuntu20.04 无人机PX4 开发环境搭建(失败率很低)
Linux系统ubuntu20.04 无人机PX4 开发环境搭建 PX4固件下载开发环境搭建MAVROS安装安装地面站QGC PX4固件下载 PX4的源码处于GitHub,因为众所周知的原因git clone经常失败,此处从Gitee获取PX4源码和依赖模块。 git clone https://gitee.com/voima/PX4-…...
中间件(express)
中间件(express) 在Express.js中,中间件(Middleware)是一个重要的组成部分,用于处理HTTP请求和响应。中间件函数具有特定的签名,并可以接受请求对象(req)、响应对象&…...
【代码随想录算法训练Day44】LeetCode 322.零钱兑换、LeetCode 279.完全平方数、LeetCode139.单词拆分
Day44 动态规划第六天 LeetCode 322.零钱兑换 dp数组的含义:装满容量为j的背包需要的最少物品数为dp[j] 递推公式:dp[j]min(dp[j-coins[i]]1,dp[j]) 初始化:dp[0]0,dp[j]INT_MAX 遍历顺序:个数问题与遍历顺序无关,都…...
ChatGLM2-6B 部署
本文主要对 ChatGLM2-6B 模型的部署和推理过程进行介绍。 一、部署环境 在阿里云服务器上部署,具体环境如下: modelscope:1.9.5 pytorch 2.0.1 tensorflow 2.13.0 python 3.8 cuda 118 ubuntu 20.04 CPU 8 core 内存 30 GiB GPU NVIDIA A10 2…...

武汉工程大学24计算机考研数据,有学硕招收调剂,而专硕不招收调剂!
武汉工程大学是一所以工为主,覆盖工、理、管、经、文、法、艺术、医学、教育学等九大学科门类的多科性教学研究型大学,是湖北省重点建设高校、湖北省国内一流学科建设高校,入选卓越工程师教育培养计划、中西部高校基础能力建设工程、“新工科…...

python爬虫之selenium自动化操作
python爬虫之selenium自动化操作 需求:操作淘宝去掉弹窗广告搜索物品后进入百度回退又前进 selenium模块的基本使用 问题:selenium模块和爬虫之间具有怎样的关联? 1、便捷的获取网站中动态加载的数据 2、便捷实现模拟登录 什么是selenium模块&#x…...

【漏洞复现】红帆iOffice.net wssRtSyn接口处存在SQL注入
【产品&&漏洞简述】 红帆iOffice.net从最早满足医院行政办公需求(传统OA),到目前融合了卫生主管部门的管理规范和众多行业特色应用,是目前唯一定位于解决医院综合业务管理的软件,是最符合医院行业特点的医院综…...

云计算【第一阶段(17)】账号和权限管理
目录 一、用户账号和组账号概述 1.1、用户账号的三种角色 1.2、组账号的两个角色 二、用户账号文件 2.1、/etc/passwd 2.2、/etc/shadow 2.3、chage 命令 三、组账号文件 3.1、/etc/group 3.2、/etc/gshadow 四、添加组账户 4.1、添加删除组成员 4.2、删除组账号 …...

环境配置02:CUDA安装
1. CUDA安装 Nvidia官网下载对应版本CUDA Toolkit CUDA Toolkit 12.1 Downloads | NVIDIA Developer CUDA Toolkit 12.5 Downloads | NVIDIA Developer 安装配置步骤参考:配置显卡cuda与配置pytorch - 知乎 (zhihu.com) 2. 根据CUDA版本,安装cudnn …...

Ranger配置图片及json文件预览
文章目录 前言下载apt下载pip下载 配置使用json文件预览方法一 修改scope用cat预览方法二 安装jq预览配置ranger 图片文件预览方法一 使用img2txt预览方法二 使用fim预览配置ranger 总结 前言 本文主要讲解Ranger12如何配置json及图片的预览设置,如下是ranger的介绍…...

C语言 | Leetcode C语言题解之第169题多数元素
题目: 题解: int majorityElement(int* nums, int numsSize) {int ans 0;for (int i 0, cnts 0; i < numsSize; i) {if (nums[i] ans) {cnts;} else if (cnts 0) {ans nums[i];} else {cnts--;}}return ans; }...

常说的云VR是什么意思?与传统vr的区别
虚拟现实(Virtual Reality,简称VR)是一种利用计算机技术模拟产生一个三维空间的虚拟世界,让用户通过视觉、听觉、触觉等感官,获得与现实世界类似或超越的体验。VR技术发展历程可追溯至上世纪,经历概念提出、…...

华为云CodeArts API:API管理一体化平台 5月新特性上线啦!
CodeArts API是华为云API全生命周期管理一体化解决方案平台,支持开发者高效实现API设计、API开发、API测试、API托管、API运维、API变现的一站式体验。 通过以API契约为锚点,CodeArts API保证API各阶段数据高度一致,为开发者提供友好易用的A…...

ubuntu16因swap分区uuid错误启动慢排查
感觉ubuntu16启动特别慢 dmesg查看如下: [ 10.050123] audit: type1400 audit(1718608189.395:11): apparmor"STATUS" operation"profile_load" profile"unconfined" name"webbrowser-app//oxide_helper" pid708 comm&q…...

[保姆级]uniapp自定义导航栏
文章目录 导文隐藏默认导航栏:全局隐藏当前页面隐藏 添加自定义导航栏视图:手写导航栏组件导航栏 导文 在 UniApp 中,自定义导航栏通常涉及到隐藏默认的导航栏,并在页面顶部添加自定义的视图组件来模拟导航栏的功能。 隐藏默认导航…...

Java 桥接模式(Bridge Pattern)是设计模式中的一种结构型设计模式,桥接模式的核心思想是将抽象与实现解耦
桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与它的实现部分分离,使它们都可以独立地变化。桥接模式的核心思想是将抽象与实现解耦,使得它们可以独立扩展。 在桥接模式中,通常包含以下四个…...

入门Ansible常用模块
自动化运维Devops-Ansible Ansible是新出现的自动化运维工具,基于Python 开发,集合了众多运维工具(puppet 、cfengine、chef、func、fabric)的优点,实现了批量系统配置 、批量程序部署、批量运行命令 等功能。Ansible…...

全能AI客户端:ChatGPT Web Midjourney Proxy,AI绘画+GPT4o对话
这绝对是目前最全能的 AI 客户端,ui 界面集成 ChatGPT AI 对话、Midjourney AI 画图、Suno AI 音乐等等市面主流的 AI 功能,只需绑定一个 API key 即可使用全部 AI 功能,Midjourney 甚至比官方好用几倍! 项目简介 ChatGPT Web Mi…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...

CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)
漏洞概述 漏洞名称:Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号:CVE-2023-25194 CVSS评分:8.8 影响版本:Apache Kafka 2.3.0 - 3.3.2 修复版本:≥ 3.4.0 漏洞类型:反序列化导致的远程代…...

vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能
vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能 查看官网:https://vxetable.cn 效果 代码 通过 checkbox-config.isShift 启用批量选中,启用后按住快捷键和鼠标批量选取 <template><div><vxe-grid v-bind"gri…...
零基础在实践中学习网络安全-皮卡丘靶场(第十一期-目录遍历模块)
经过前面几期的内容我们学习了很多网络安全的知识,而这期内容就涉及到了前面的第六期-RCE模块,第七期-File inclusion模块,第八期-Unsafe Filedownload模块。 什么是"遍历"呢:对学过一些开发语言的朋友来说应该知道&…...