Doris architecture

ANALYTICAL DATABASE / SOURCE READING / LESSON 07

Compaction and version management

Study version chains, publish flow, compaction, and visibility in Doris storage.

Reading
40 min
Track
Doris architecture
Source
Chinese source notes

The source notes for this track are currently maintained in Chinese.

预计阅读时间: 40 分钟 前置阅读: doc-06 (Rowset/Segment) 下一次阅读: doc-08 (索引)


1. Tablet 版本模型

版本的核心概念

Tablet 使用单调递增的 int64 版本号来跟踪数据的修改。每次写入产生一个新 Rowset, 其版本号 = 写入前 Tablet.max_version + 1。

Tablet 初始状态:
  max_version = 0, visible_version = 0, rowsets = []

第一次写入 (INSERT 100 行):
  DeltaWriter → Rowset [version=1, rows=[1..100]]
  Tablet.rowsets = [Rowset(v1)]
  max_version = 1, visible_version = 0 (还未 Publish)

Publish:
  → Tablet::publish_version(1)
  visible_version = 1, Rowset(v1) 变为 VISIBLE

第二次写入 (INSERT 50 行):
  Rowset [version=2, rows=[101..150]]
  Publish → visible_version = 2

查询时:
  所有 version ≤ visible_version 的 Rowset 都可以被读取
  需要读: Rowset(v1) + Rowset(v2)

关键概念

术语含义源码位置
max_versionTablet 的最新版本号Tablet::_max_version
visible_version已 Publish 的最新版本 (查询可见)Tablet::_visible_version
next_version下一个写入的版本号 (=max_version + 1)Tablet::_next_version
PublishVersionFE → BE Publish RPC 指定的版本TPublishVersionRequest Thrift struct

源码导航

文件关键类/方法职责
be/src/storage/tablet.hpublish_version(), max_version()版本管理
be/src/storage/compaction/compaction.hCompactionMixin::execute_compact()Compaction 实现
be/src/storage/compaction/cumulative_compaction.hCumulativeCompaction增量 Compaction
be/src/storage/compaction/base_compaction.hBaseCompaction全量 Compaction
be/src/storage/compaction/cumulative_compaction_policy.cppCumulativeCompactionPolicyCompaction 策略
be/src/storage/compaction/compaction_permit_limiter.hCompactionPermitLimiter并发控制
be/src/agent/task_worker_pool.cppPublishVersionWorkerPoolPublish 任务执行(BE Agent)
fe/fe-core/.../transaction/PublishVersionDaemon.javaPublishVersionDaemonPublish 守护线程(FE)

存储引擎——Compaction 与版本管理 图 01

2. Compaction 类型

全部类型

类型触发条件合并哪些 Rowset源码
Cumulative Compaction持续后台运行最后 N 个 Delta Rowset (N = cumulative_compaction_num_singleton_deltas, 默认5)cumulative_compaction.cpp
Base Compaction低峰时段执行所有 Rowset (全部合并为1个)base_compaction.cpp
Full Compaction用户手动触发 (Schema Change)所有 Rowsetcompaction.cpp
Cold Compaction冷数据 (cooldown 迁移后)冷 Rowset → 合并后写入对象存储cold_data_compaction.cpp

Cumulative vs Base 对比

Cumulative Compaction (频繁):
  Before: [Base: 0-9] + [D1: 10] + [D2: 11] + [D3: 12] + [D4: 13] + [D5: 14]
  合并最后 5 个 Delta:
  After:  [Base: 0-9] + [Merged: 10-14]

Base Compaction (低峰):
  Before: [Base: 0-9] + [Merged: 10-14]
  合并所有:
  After:  [New Base: 0-14]

3. Compaction 调度

调度流程

1. Pick Candidates
   CumulativeCompactionPolicy::pick_candidate(rowsets)
   → 选择最后 N 个连续的 Delta Rowset

2. Priority Calculation
   → Priority = f(num_rows, num_rowsets, compaction_size)
   → 行数越多, Rowset 越多 → Priority 越高

3. Permit Limiter (并发控制)
   → CompactionPermitLimiter::request()
   → 每个 BE 最多 N_be_compaction 个 Compaction 并发 (默认 5)
   → 如果 permit 不足 → 入队列等待

4. Execute Merge
   → CompactionMerger::merge(rowsets, output_rowset)
   → 逐列读取每个 Rowset 的 Segment → Merge → 写新 Rowset

5. Commit (原子提交)
   → Tablet::commit_compaction(output_rowset, input_rowsets)
   → 原子操作: 删除旧 Rowset + 添加新 Rowset

4. Merge 过程详解

内部执行

CompactionMerger::merge()
  → for each Column (dt, status, amount, city, ...):
      → for each Input Rowset:
          → read Column Page →
      → MergeSort (按主键排序)
        → 遇到同 key: 选择最新版本的行 (version大的)
        → 遇到 DeleteBitmap 标记的行 → 丢弃
      → write Merged Column Page → SegmentWriter
  → SegmentWriter::finalize()
  → 生成新的 Rowset

GC 时机

Compaction 生成新 Rowset 后, 旧的 Rowset 被标记为 "删除待 GC"。GC 在以下时机清理:

  1. 后台 GC 线程定期执行 (默认每 60s)
  2. 删除时检查: 是否还有其他查询引用旧 Rowset (Reference Counting)
  3. 如果有引用 → 延迟 GC (直到引用释放)

5. 版本可见性与 DeleteBitmap

删除的实现方式

Doris 不真正删除磁盘数据 (直到 Compaction), 而是通过 DeleteBitmap 标记 "已删除的行":

写入:   Rowset v1: rows {1: alive, 2: alive, 3: alive}
DELETE: 生成 DeleteBitmap v2: {2 → deleted, 3 → deleted}
查询:   SegmentReader 读取 v1 的 rows{1,2,3} → DeleteBitmap 过滤 {2,3} → 返回 {1}
Compaction: v1 和 v2 合并 → 新 Rowset v3: rows{1} (物理删除 2,3)

DeleteBitmap 的来源

来源场景
DELETE FROM 语句用户显式删除
Unique Key 模型 (MoW)新 key 替换旧 key → 旧行 marked deleted
Load 去重同名 Label 的重试 → 第一次写入的行 marked deleted

6. Vertical Compaction / Segment Compaction 优化

优化原理效果
Vertical Compaction按列组 (Column Group) 分离执行合并: 热点列组先合并, 减少内存峰值内存降低 30-50%
Segment CompactionFlush 时直接在内存中合并多个小 Segment 为 1 个 (避免小 Rowset 进入 Compaction 队列)减少 Compaction 调度次数
Ordered Compaction对于 key 有序的数据, 跳过 MergeSort (因为 Rowset 之间天然有序)CPU 降低 20-30%

7. 常见问题 / 面试题

Q1: Cumulative Compaction 的 "最后 N 个" 为什么是 5 (默认)? A: 合并更多 Delta → 减少 Rowset 数量更快, 但每次合并的成本 (IO+CPU) 也更高。N=5 是在性能基准测试中平衡成本和收益得出的经验值。写入吞吐非常高的场景可调大到 7-10。

Q2: 为什么需要两种 Compaction (Cumulative + Base)? A: Cumulative 是"增量合并", 只合并最近的 Delta, 低成本但不够彻底; Base 是"全量合并", 将所有 Rowset 合并为 1 个, 彻底消除碎片但成本高。两者互补, Cumulative 保证 Rowset 数量不爆炸, Base 在低峰时彻底优化。

Q3: 如果一个查询正在读一个 Rowset, 同时 Compaction 要删除它, 会怎么处理? A: Compaction 提交时会检查 Rowset 的引用计数 (RefCount)。如果查询正在读 → RefCount > 0 → Compaction 将旧 Rowset 加入 _stale_rs_meta 列表但不释放文件 → 查询结束后 RefCount 为 0 → GC 线程最终清理文件。

Q4: Compaction 会影响写入性能吗? A: 通过以下机制控制: (1) CompactionPermitLimiter 限制并发; (2) Compaction 使用低优先级的 IO 调度 (避免与用户查询的 IO 竞争); (3) Compaction 在数据写入完成后异步执行, 不阻塞写入路径。


Compaction 流程 & Version 演进

下一步