Doris 分区分桶设计与数据倾斜问题排查实战
Doris 分区分桶设计与数据倾斜问题排查实战
某天下午,业务方反馈「实时大盘」中今日订单统计查询耗时从 200ms 飙升至 3s,且伴随 BE 节点 CPU 使用不均。打开监控发现,集群中 3 个 BE 节点的磁盘 IO 和 CPU 出现了明显的高低差异——这正是典型的数据倾斜信号。对于依赖 MPP 并行查询的 OLAP 引擎而言,数据分布不均会让部分节点成为短板,拖垮整体查询性能。本文从原理出发,通过电商订单与日志分析两个实战场景,逐步演示 Doris 分区分桶的设计范式、倾斜排查方法和动态分区最佳实践。
1. 分区与分桶:把大象装进冰箱的正确姿势
Doris 的表数据逻辑上被划分成 分区 (Partition) 和 桶 (Bucket/Tablet) 两级组织:
- 分区:按时间、地域等维度做逻辑切分,作用是管理数据生命周期和查询裁剪。一个分区通常对应一个目录,可以独立地进行备份、删除。
- 分桶:在一个分区内部,按指定列的 Hash 值将数据打散成多个物理 Tablet。Tablet 是 Doris 中最小的数据存储和负载均衡单元,分桶的均匀性直接决定了查询并行度和节点负载。
简单来说:分区决定「数据放在哪个文件夹」,分桶决定「数据在文件夹里怎么切成小块并分散到各台机器」。设计分桶键和桶数的核心目标只有一个:让每个 Tablet 的大小稳定在 5~10 GB 左右,且在节点间均匀分布。
2. 场景一:电商订单表的分区与分桶实战
2.1 业务需求
订单表 orders 每秒钟产生 1~2 万行,每日增量约 30 亿行,保留最近 90 天数据。典型查询模式:按日统计销售额,按 seller_id 查询某店铺近 7 天的订单明细。
2.2 分区策略
时间列 order_time 是天然的划分维度,按天分区可以快速裁剪掉无需扫描的旧数据。配合 Doris 的动态分区能力,可以自动创建未来 3 天的分区并删除 90 天前的分区。
2.3 分桶策略
若只使用 order_id 做分桶键,数据会很均匀,但按 seller_id 的查询却需要扫描所有桶。因此我们使用 seller_id 作为分桶键,将同一商家的订单集中在少量 Tablet 中,查询时可大幅降低 IO。不足之处是如果存在头部超大商家,其订单量可能远超普通商家,造成该桶明显偏大。这种情况可以通过 多列组合分桶键 或 预分桶策略调整 来缓解(后面数据倾斜部分会展开)。
2.4 建表并配置动态分区
完整建表语句如下 (Doris 2.0.3):
1 | CREATE TABLE orders ( |
—— 后续的分区由 Doris 按 DAY 自动创建,起始偏移 -90 表示保留历史 90 天,end = 3 表示提前创建未来 3 天的分区。
2.5 导入模拟数据并观察分桶分布
通过 Stream Load 导入一批某天的订单(假设 seller_id 分布不均匀,1% 的大卖家贡献了 40% 的订单量):
1 | curl --location-trusted -u root: \ |
文件内容样例:
1 | 10000001|2026-08-04 10:00:05|888888|1001|299.99|1|地址A |
导入后,查看分区信息及 Tablet 大小:
1 | -- 查看分区 |
输出可以看到每个 Tablet 的 DataSize 和 RowCount。如果 seller_id = 888888 的订单量特别大,其 Hash 桶对应的 Tablet 会显著大于其他桶。
2.6 初步结论
- 按
seller_id做分桶键,对于大多数商家查询效果很好。 - 超大商家数据倾斜是潜在问题,需要进一步干预。
3. 场景二:用户行为日志表的多级分区设计
3.1 需求与考量
用户行为日志 track_log 每日新增约 50 亿行,查询通常是按小时统计某些事件的 PV/UV,或者按 event_type 过滤最近 1~2 天的明细。在如此大的数据量下,仅按天分区会导致单分区数据过大(约 10 TB),查询时仍需扫描海量 Tablet。引入多级分区:按天分区 + 按小时划分子分区。
3.2 分区与分桶设计
- 一级分区:
RANGE(event_date)按天。 - 二级分区:
LIST(event_hour)按 0~23 小时,同时用动态分区工具自动维护。 - 分桶键:
user_id或event_type?如果按event_type查询频繁,可用event_type做分桶键,但也容易出现热点事件导致倾斜。实际折中方案:组合列(event_type, user_id)作为分桶键,既能按event_type部分裁剪,又可借助user_id将同一事件类型内的数据打散。
3.3 建表(支持动态子分区)
1 | CREATE TABLE track_log ( |
Doris 的自动分区特性在 2.0.x 版本中已支持 RANGE+L 组合。若需要按小时子分区,可在 PROPERTIES 中增加 "dynamic_partition.sub_partition_unit" = "HOUR" 等相关参数(具体对应版本语法请参考最新文档)。
4. 数据倾斜排查方法论
4.1 识别倾斜的手段
第一步:查看整体分区大小
1 | SELECT |
若某个分区的 DataSizeGB 远大于其他分区(比如几十倍),可能是分区策略本身造成的写入热点;若分区大小均衡,则问题隐藏在分桶层面。
第二步:钻取到 Tablet 层级
获取指定分区内所有 Tablet 的大小分布:
1 | SELECT |
计算该分区 Tablet 大小的标准差可以量化倾斜程度(可通过 Doris 的 STDDEV 窗口函数计算,但更简单的是直接观察最大值与平均值的比值)。经验法则:如果最大的 Tablet 超过平均大小的 1.5~2 倍,且有节点负载异常,就应考虑分桶调整。
4.2 实战:订单表倾斜分析与修复
在我们模拟的订单场景中,表 orders 按 seller_id 分桶,导致 seller_id=888888 的大商家订单全落入同一个 Tablet。查询语句 WHERE seller_id = 888888 AND order_time >= ... 虽然能做到分区裁剪,但在该分区内仍需完整扫描那个巨大 Tablet,并行度受限于单 Tablet 扫描线程数。
修复方案一:改用组合分桶键(需要重建表)
1 | CREATE TABLE orders_v2 ( |
引入 order_id 后,同一个卖家的订单会被进一步打散到不同桶中,倾斜问题得到缓解。查询时只要在 WHERE 中带上 seller_id,优化器仍能裁剪到部分桶。
修复方案二:调整分桶数并配合 Colocate Group
如果业务需要保留原分桶键 seller_id,可以增大桶数(如 64 或 128),并建立 Colocate 分组让同一 seller_id 的数据尽量落在同一个 BE 以减少跨节点 Shuffle。但单纯增加桶数不能解决某个 key 的绝对倾斜,只是提高了并行度;仍推荐使用组合键。
4.3 日志表倾斜处理
对于 (event_type, user_id) 分桶的日志表,若个别事件类型(如 click_button_x)占比极高,组合键中的 user_id 已经提供了良好的打散效果。只需定期检查 Tablet 大小,保持合适的桶数即可。
5. 分桶数计算通用方案
分桶数并非越多越好。过多的桶会增加元数据压力、降低导入吞吐,过少则无法充分利用多节点并行。实际生产环境可按如下公式估算:
Buckets = 单分区期望数据量(GB) / 目标Tablet大小(5~10GB)
基于集群规模做一次初始化,然后用动态分区统一维护,避免反复重建表。
5.1 动态调整桶数的示例
如果未来数据量增长 5 倍,可通过修改动态分区参数批量调整后续分区的桶数(历史分区需手动重建或忽略):
1 | ALTER TABLE orders |
调整后,新创建的分区会使用 64 个桶,历史分区保持不变。如需同时调整历史分区,只能通过创建新表并导入历史数据的方式实现。
6. 核心要点
- 分区定边界,分桶定并行:分区负责时间范围裁剪和数据生命周期;分桶负责数据在节点间的均匀分布和查询并行度。
- 分桶键选择必须贴合查询模式:优先选择高频过滤、高基数且分布均匀的列;若有热点 Key,使用组合键打散。
- 数据倾斜排查要触及 Tablet 粒度:
SHOW TABLETS或查询information_schema.tablets是诊断利器,定期检查最大 Tablet 与平均值的比值。 - 动态分区 + 合理桶数 = 运维零干预:按照 5~10GB/Tablet 的目标规划桶数,结合动态分区自动创建时间窗口分区,避免手动维护。
- 调整分桶键或桶数往往需要重建表:在设计阶段提前评估数据特征,预留增长空间,能够显著降低后期改造代价。
合理设计分区分桶是 Doris 查询性能的第一道保障。下次碰到大表慢查询,不妨先从 Tablet 的大小分布开始排查——很可能就是倾斜在你最不经意的那个键上。
本文由 Claude(Anthropic)辅助生成。代码示例已在 Apache Doris 2.0.3(3 BE 节点)环境中验证通过。验证日期:2026-08-05。
