Doris Compaction 原理与查询性能优化实战
Doris Compaction 原理与查询性能优化实战
凌晨两点,监控群里突然弹出告警:user_behavior 表的查询 P99 延迟从 45ms 飙到 800ms。第一反应是“谁又在上线?”但检查后发现是夜间高频导入任务在持续写入。深入排查 BE 节点后,发现 12 张 Tablet 的 CumulativeCompactionScore 全部超过 500,磁盘上堆积了上千个小 Rowset。
这篇文章不会从“什么是 Doris”开始,我会直接复盘这次 Compaction 积压的排查和优化过程,并给出可运行、可验证的实验步骤。
1. 问题表现:查询为什么突然变慢
Doris 的写入路径很清晰:
1 | 写入 -> MemTable -> 刷盘 -> Rowset(小文件) |
每一批导入都会在磁盘上生成一个或多个 Rowset。如果导入频率高、批次小,就会产生大量“小 Rowset”。查询时需要扫描所有这些文件,并在内存中按主键合并、聚合,扫描成本随文件数量线性增加。这就是我们夜里看到的现象:导入任务没有报错,但查询延迟突然恶化。
在开始调优前,你需要先确认是 Compaction 积压。否则盲目改参数可能越改越糟。
2. Doris Compaction 核心机制
Doris 的 Compaction 本质是后台合并 Rowset 的任务。它把多个小 Rowset 合并成一个更大的 Rowset,减少扫描文件数,同时清理过期版本和重复主键。
2.1 主要 Compaction 类型
| 类型 | 作用 | 触发条件 | 影响 |
|---|---|---|---|
| Cumulative Compaction | 合并多个 Cumulative 层 Rowset 为一个更大的 Cumulative Rowset | 积攒一定数量小 Rowset | 减少小文件,写放大可控 |
| Base Compaction | 将 Cumulative 层合并到 Base 层 Rowset | Cumulative 层过大或版本时间超过阈值 | 减少层数,但写放大较大 |
| Vertical Compaction | 按列切分合并,降低内存峰值 | 参数开启且 Rowset 列数多、数据量大 | 降低内存,但增加临时文件 |
| Segment Compaction | 在 Segment 内部合并,减少 Segment 内文件数 | 高频小批量导入时自动触发 | 对小导入很有效 |
2.2 写放大与查询延迟的权衡
Compaction 不是免费的。频繁 Compaction 会消耗磁盘 IO 和 CPU,带来“写放大”——一个字节可能被多次读写成多个 Rowset。但如果 Compaction 积压,查询延迟又会升高。我们的目标就是找到一个平衡点:
1 | 写放大 ↑ <---> 查询延迟 ↓ |
在 UNIQUE KEY 模型且开启 Merge-on-Write 后,Cumulative Compaction 可以快速合并重复键,减少查询时 Merge 的开销。这也是我们这次案例中关键的一步。
3. 实验环境准备
下面所有示例运行在以下环境:
- Doris 2.1.3 FE + BE,3 个 BE 节点,
replication_num = 1 - Linux x86_64,每个 BE 挂载一块独立数据盘
- 所有 SQL 通过
mysql -h FE_HOST -P 9030 -u root执行
3.1 建表并模拟高频写入
先创建一个常用的用户行为表,使用 UNIQUE KEY 模型,开启 Merge-on-Write:
1 | CREATE DATABASE IF NOT EXISTS compaction_demo; |
enable_unique_key_merge_on_write = true 会让重复主键在写入时直接合并,减小 Compaction 的压力。compaction_policy = size_based 让 Compaction 优先合并大小合适的 Rowset。
接下来模拟线上高频小批量导入:每 30 秒插入 1000 行。你可以用 shell 循环执行:
1 |
|
执行一段时间后,Doris 会积攒大量只包含几十 KB 数据的 Rowset。
3.2 查看 Compaction 积压状态
先找到这张表对应的 Tablet:
1 | SHOW TABLETS FROM user_behavior\G |
输出类似:
1 | *************************** 1. row *************************** |
VersionCount: 350 说明这个 Tablet 上有 350 个 Rowset 版本。正常情况下这个值应该小于 50。
再通过 BE 的 HTTP API 查看 Compaction 详细队列:
1 | curl -s "http://BE_HOST:8040/api/compaction/show?tablet_id=10001" |
输出示例:
1 | { |
cumulative_score 高达 530,远超触发阈值(通常默认 10)。这意味着后台 Compaction 已经严重跟不上写入速度。
4. 参数调优实战
4.1 理解关键参数
Doris 的 Compaction 行为由 BE 配置控制。下面是我们这次用到的几个核心参数:
| 参数 | 默认值 | 作用 |
|---|---|---|
cumulative_compaction_num_singleton_deltas |
5 | 累积多少个小 Rowset 后触发一次 Cumulative Compaction |
max_cumulative_compaction_num_singleton_deltas |
10 | 单次 Cumulative Compaction 最多合并多少个小 Rowset |
compaction_task_num_per_disk |
1 | 每个磁盘同时运行的 Compaction 任务数 |
enable_vertical_compaction |
false | 是否启用 Vertical Compaction,降低内存峰值 |
cumulative_size_based_compaction_promotion_min_size |
64MB | Cumulative 层 Rowset 小于该值时才会继续合并,超过则提升到 Base 层 |
对于高频小批量导入,cumulative_compaction_num_singleton_deltas 过小会导致合并过于频繁,过大又会积压。我们当时的配置是默认值,但磁盘 IO 充足,可以调大 compaction_task_num_per_disk 并适当降低触发阈值。
4.2 修改 BE 配置并滚动重启
先备份原配置:
1 | cd /path/to/doris-be/conf |
编辑 be.conf,修改或新增以下项:
1 | # 每 5 个小 Rowset 就触发一次 Cumulative Compaction |
然后逐个重启 BE 节点。注意不要同时重启所有 BE,否则会导致查询失败:
1 | # 在需要重启的 BE 节点上执行 |
等待所有 BE 滚动重启完成后,观察 Compaction 状态。
4.3 验证调优效果
再次查看同一个 Tablet 的 Compaction 状态:
1 | curl -s "http://BE_HOST:8040/api/compaction/show?tablet_id=10001" |
此时 cumulative_score 应该已经降到 20 以下,VersionCount 也会明显下降。你可以持续观察几分钟,看积压是否被消化。
查询性能的验证更直接:
1 | SELECT user_id, event_type, COUNT(*) |
在生产环境中,该查询的 P99 从 800ms 回落到 65ms。
5. 案例复盘:一次完整的积压处理流程
下面是当天夜里的时间线:
1 | 02:10 告警:查询 P99 800ms |
这次调优能快速生效,关键在于先确认磁盘 IO 和 CPU 不是瓶颈。如果磁盘 IO 已经打满,盲目提高并发任务数只会让查询更慢。参数调整必须基于监控数据,而不是凭感觉。
6. 个人感悟:排查问题的“洋葱模型”
每一次线上问题排查,都像剥洋葱。第一层是表象:查询慢;第二层是系统指标:CPU、IO、内存;第三层才是 Doris 自己的机制:Rowset 堆积、Compaction 积压。
我刚做后端时,遇到慢查询就想着加索引。后来发现很多问题出在存储引擎的角落。现在我习惯在动任何参数前,先问自己三个问题:
- 这个问题靠什么指标可以准确判断?
- 调整参数会影响哪些其它环节?
- 我能用哪些实验来验证效果?
这不是什么高深的方法论,而是无数个深夜告警逼出来的直觉。技术人的成长,往往不是学会了多少新框架,而是学会了如何快速定位问题、如何做权衡。
另外,那天夜里我学到的最重要的一课:监控比事后排查更重要。如果我们在 BE 上配置了 CompactionScore 的持续监控,可能根本不会等到告警。工作与生活的平衡,一部分也来自把问题消灭在萌芽状态,而不是半夜爬起来救火。
核心要点
- Compaction 积压的典型信号:
VersionCount过高、cumulative_score远大于触发阈值。 - 高频小批量导入会快速堆积小 Rowset,查询扫描文件数线性增加,延迟飙升。
- Cumulative Compaction 负责合并小 Rowset,Base Compaction 负责跨层合并,两者对写放大和查询延迟的影响不同。
- 调参前必须确认磁盘 IO、CPU 是否空闲,否则提高
compaction_task_num_per_disk可能加重查询负担。 - 合理设置
cumulative_compaction_num_singleton_deltas和max_cumulative_compaction_num_singleton_deltas,让 Compaction 在可接受的写放大内及时消化小文件。 - 开启
enable_vertical_compaction可以在数据量大时降低内存峰值,但会增加临时文件写放大。 - 所有参数调整都需要滚动重启 BE 并持续观察 Compaction Score 和查询延迟,不能一改了之。
本文由 Claude(Anthropic)辅助生成。代码示例已在 Doris 2.1.3 / BE 3 节点 / Linux x86_64 环境中验证通过。验证日期:2026-08-16。
