当进行超大规模数据的 COUNT(DISTINCT) 计算时,Doris 提供哪些优化手段?
在 Apache Doris 中,针对超大规模数据的 COUNT(DISTINCT) 计算,由于涉及到跨节点的数据 Shuffle(重分布)和内存占用,通常是性能瓶颈所在。
为了解决这一痛点,Doris 提供了从算法、存储、执行引擎到查询规划的全方位优化手段。主要可以分为以下四大类:
一、 算法与数据结构优化(最核心)
Doris 提供了两种非传统(非 Hash Set)的去重方式:近似去重(HLL)和精确去重(Bitmap)。
1. 近似去重:HLL (HyperLogLog)
对于超大规模数据(如百亿、千亿级),如果业务能够容忍 1% 左右的误差,HLL 是性能最高、最省内存的选择。
- 原理:利用 HyperLogLog 算法,将任意大小的数据集压缩成固定大小的 HLL 列(通常只有几 KB 到几十 KB),内存占用为 。
- 优化手段:
- 直接查询:使用
APPROX_COUNT_DISTINCT(col)。 - 预聚合(HLL 类型的列):在聚合模型(Aggregate Key)中,定义列类型为
HLL,聚合类型为HLL_UNION。数据导入时自动预聚合,查询时使用HLL_UNION_AGG(),速度提升数十倍。
- 直接查询:使用
2. 精确去重:Bitmap (Roaring Bitmap)
如果必须要求 100% 精确,且去重列可以转化为整型,Bitmap 是最佳方案。
- 原理:Doris 内部采用高效的 Roaring Bitmap 压缩算法。每个 ID 对应 Bit 数组中的一位,不仅极大地节省了存储空间,而且把去重操作转化为了底层的位运算(AND/OR/COUNT),速度极快。
- 优化手段:
- Bitmap 类型列:在建表时定义列为
BITMAP类型,聚合类型为BITMAP_UNION。 - 全局字典(Global Dictionary):如果去重列是
String类型,Doris 支持通过全局字典将String映射为Int,从而也能利用 Bitmap 进行加速。
- Bitmap 类型列:在建表时定义列为
二、 存储与预计算优化(空间换时间)
在存储层提前做好聚合,避免查询时进行海量数据的现场计算。
1. 聚合模型(Aggregate Key Model)
在建表时采用聚合模型,将去重列定义为 BITMAP_UNION 或 HLL_UNION。
- 效果:数据在导入(Load)阶段和后台 Compaction 阶段就已经完成了去重预折叠。查询时,Doris 只需要扫描已经聚合好的极少数据量。
2. 强力武器:物化视图(Materialized View)
如果基表是明细模型(Duplicate Key Model),无法直接使用聚合列,可以为基表创建含有 BITMAP_UNION 或 HLL_UNION 的物化视图。
- 示例:sql
CREATE MATERIALIZED VIEW mv_user_count AS SELECT page_id, BITMAP_UNION(to_bitmap(user_id)) FROM base_table GROUP BY page_id; - 效果:Doris 会在后台自动维护物化视图的数据。当用户查询
SELECT page_id, COUNT(DISTINCT user_id) FROM base_table GROUP BY page_id时,优化器会自动重写查询,直接路由到物化视图,避免扫描明细表。
三、 查询执行与算子优化(计算引擎层)
Doris 的向量化执行引擎针对 COUNT(DISTINCT) 做了深度优化,减少网络传输和内存开销。
1. 两阶段与三阶段聚合(Two-Phase / Three-Phase Aggregation)
- 两阶段聚合:默认情况下,Doris 会先在本地(Local)进行部分聚合(Reduce 局部数据),然后将数据 Shuffle 到各个节点进行全局(Global)聚合。
- 三阶段聚合:当存在高基数(High Cardinality)的
GROUP BY+COUNT(DISTINCT)且数据倾斜严重时,Doris 优化器会拆分成三个阶段执行,引入一个中间的预聚合阶段,极大缓解了单个节点的内存压力和网络瓶颈。
2. Colocate 聚合(免除 Shuffle)
如果查询的 GROUP BY 列和 COUNT(DISTINCT) 列与建表时的 DISTRIBUTED BY (bucketing_key) 列一致。
- 效果:Doris 知道相同的数据已经在同一个节点上了,因此会直接执行 Local Distinct Aggregation,完全避免了节点间的网络 Shuffle,性能呈指数级提升。
3. 向量化(Vectorized Execution)
Doris 拥有全向量化的执行引擎。在进行 Hash Set 的去重计算时,能够利用 CPU 的 SIMD 指令集批量处理数据,并对 Hash Table 的内存布局进行了精心设计(如使用连续内存、减少指针跳转),大幅提升了单核处理去重时的吞吐量。
四、 参数调优与最佳实践指南
为了让上述优化手段发挥最大威力,可以调整以下配置:
- 分桶列选择:尽量将经常用于
GROUP BY和COUNT(DISTINCT)的列设为分桶键(Bucket Key),以触发 Colocate 聚合。 - 合理设置 Runtime Filter:在多表关联(Join)后再去重的场景下,Runtime Filter 可以把 Join 的过滤条件提前推送到扫描端,大幅减少进入去重算子的数据量。
- 内存限制调整:对于极高基数的精确去重,如果出现内存溢出(OOM),可以适当调大单次查询的内存限制:sql
SET exec_mem_limit = 16106127360; -- 调整为 15GB
总结:如何选择?
| 场景需求 | 推荐方案 | 核心技术 | 性能提升 |
|---|---|---|---|
| 允许 1% 左右误差 | 近似去重 | HLL / APPROX_COUNT_DISTINCT |
极高(内存占用极小) |
| 精确去重 + 整数ID | Bitmap 预聚合 | BITMAP 类型 + BITMAP_UNION |
极高(位运算) |
| 精确去重 + 字符串 | 全局字典 + Bitmap | 全局字典转化为整型 + Bitmap | 高 |
| 非预聚合明细表查询 | 物化视图自动路由 | Materialized View |
高(透明加速) |
| 无法预聚合的即席查询 | 执行引擎优化 | Colocate Join / 三阶段聚合 | 中等(避免 Shuffle) |
右滑查看面试常问