基于本文回答
0
评论

当进行超大规模数据的 COUNT(DISTINCT) 计算时,Doris 提供哪些优化手段?

在 Apache Doris 中,针对超大规模数据的 COUNT(DISTINCT) 计算,由于涉及到跨节点的数据 Shuffle(重分布)和内存占用,通常是性能瓶颈所在。

为了解决这一痛点,Doris 提供了从算法、存储、执行引擎到查询规划的全方位优化手段。主要可以分为以下四大类:


一、 算法与数据结构优化(最核心)

Doris 提供了两种非传统(非 Hash Set)的去重方式:近似去重(HLL)精确去重(Bitmap)

1. 近似去重:HLL (HyperLogLog)

对于超大规模数据(如百亿、千亿级),如果业务能够容忍 1% 左右的误差,HLL 是性能最高、最省内存的选择。

  • 原理:利用 HyperLogLog 算法,将任意大小的数据集压缩成固定大小的 HLL 列(通常只有几 KB 到几十 KB),内存占用为 O(1)O(1)
  • 优化手段
    • 直接查询:使用 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 进行加速。

二、 存储与预计算优化(空间换时间)

在存储层提前做好聚合,避免查询时进行海量数据的现场计算。

1. 聚合模型(Aggregate Key Model)

在建表时采用聚合模型,将去重列定义为 BITMAP_UNIONHLL_UNION

  • 效果:数据在导入(Load)阶段和后台 Compaction 阶段就已经完成了去重预折叠。查询时,Doris 只需要扫描已经聚合好的极少数据量。

2. 强力武器:物化视图(Materialized View)

如果基表是明细模型(Duplicate Key Model),无法直接使用聚合列,可以为基表创建含有 BITMAP_UNIONHLL_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 的内存布局进行了精心设计(如使用连续内存、减少指针跳转),大幅提升了单核处理去重时的吞吐量。


四、 参数调优与最佳实践指南

为了让上述优化手段发挥最大威力,可以调整以下配置:

  1. 分桶列选择:尽量将经常用于 GROUP BYCOUNT(DISTINCT) 的列设为分桶键(Bucket Key),以触发 Colocate 聚合。
  2. 合理设置 Runtime Filter:在多表关联(Join)后再去重的场景下,Runtime Filter 可以把 Join 的过滤条件提前推送到扫描端,大幅减少进入去重算子的数据量。
  3. 内存限制调整:对于极高基数的精确去重,如果出现内存溢出(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)
右滑查看面试常问