Doris Unique Key 模型深度解析:Merge-on-Write 实现原理与生产级调优实战
适用场景与核心价值
本文面向数据仓库开发、Doris 运维工程师以及从事实时用户画像、订单状态同步等业务的技术人员。内容聚焦于 Doris 2.0+ 版本引入的 Merge-on-Write(MOW)机制,深入剖析其底层结构、读写流程、Compaction 策略及典型优化方案,提供可直接落地的配置模板和故障应对策略。
Merge-on-Write 核心机制详解
Merge-on-Write 是 Apache Doris 在 Unique Key 模型中为解决传统 Merge-on-Read(MOR)性能瓶颈而设计的新架构。其核心思想是在数据写入阶段完成主键冲突处理与旧版本标记,使查询时无需进行多版本合并计算。
- 写入即生效:更新或删除操作在写入后立即可见,无延迟。
- 零读放大:查询仅扫描最新有效数据,避免 MOR 中因遍历历史版本导致的高 IO 和 CPU 开销。
- 版本控制强化:通过 Delete Bitmap 技术精准标记无效记录,抑制 Tablet 版本数膨胀,降低系统运维压力。
与 Merge-on-Read 的关键对比
| 维度 | Merge-on-Read (MOR) | Merge-on-Write (MOW) |
|---|---|---|
| 合并时机 | 查询时动态合并 | 写入时预合并 |
| 查询性能 | 较差,随版本增长线性下降 | 优异,接近明细表水平 |
| 写入吞吐 | 高,仅追加不校验 | 略低,需主键查找与位图更新 |
| 存储效率 | 低,保留全部历史 | 高,仅存有效数据 |
| 适用场景 | 写密集、查稀疏 | 读多写少、强一致性要求 |
底层存储结构:Delete Bitmap 为核心
MOW 表格的每个 Tablet 包含多个 Rowset,每个 Rowset 对应一次写入批次,包含数据文件、索引文件以及一个关键组件——Delete Bitmap。
Tablet
├── Base Rowset (v1)
│ ├── segment.dat
│ └── segment.idx
└── Delta Rowset (v2)
├── segment.dat
├── segment.idx
└── _delete_bitmap ← 标记 v1 中被覆盖的主键
Delete Bitmap 基于 Roaring Bitmap 实现,具备高压缩比和快速位运算能力。当某条主键被更新时,系统会:
- 利用主键索引定位该键所在的老 Rowset 和偏移位置;
- 在新 Rowset 的 Delete Bitmap 中标记该位置为"已删除";
- 将新值写入当前 MemTable 并生成新索引。
物理删除由后续 Compaction 异步完成,确保写入效率不受影响。
写入流程全链路拆解
建表示例(推荐配置)
CREATE TABLE user_profile (
user_id BIGINT,
name STRING,
age INT,
city STRING,
last_login DATETIME
) UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 16
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"bloom_filter_columns" = "user_id",
"compression" = "ZSTD",
"segment_size" = "268435456"
);
写入执行步骤
- 客户端提交写入请求(StreamLoad / Broker Load / INSERT INTO);
- FE 路由至对应 BE 节点;
- BE 使用布隆过滤器 + 主键索引定位旧版本主键;
- 若存在旧版本,则在即将生成的 Delta Rowset 中设置 Delete Bitmap 标记;
- 新数据写入内存结构(MemTable),达到阈值后刷盘为 Segment;
- 增量 Rowset 提交,Delete Bitmap 生效,数据对外可见。
不同操作类型处理逻辑
- INSERT:主键不存在,直接写入,无需打标。
- UPDATE:查到旧值 → 打删除标记 → 写入新值。
- DELETE:查到目标主键 → 在 Bitmap 中标记作废 → 不写新数据。
批量写入优化建议
单条 UPDATE 性能极差,应采用批量导入方式:
- 每批 1,000 ~ 10,000 条数据;
- 使用 StreamLoad 导入 CSV 或 JSON 格式更新集;
- 启用批内去重减少内部冲突。
查询机制:高效过滤与谓词下推
MOW 查询路径如下:
- 根据分区和分桶定位目标 Tablet;
- 加载所有 Rowset 列表及其对应的 Delete Bitmap;
- 使用主键索引快速定位候选行;
- 结合 ZoneMap、Bloom Filter 和 Delete Bitmap 提前裁剪无效数据块;
- 仅读取未被标记的有效记录返回结果。
得益于上述机制,即使经历数十万次更新,查询仍能保持毫秒级响应。
Compaction 机制专项适配
MOW 引入了专用的合并策略,用于清理 Delete Bitmap 并压缩存储:
输入: - 多个 Delta Rowset - 含大量 Delete Bitmap 标记 过程: - 扫描所有 Rowset,跳过被标记的数据 - 将剩余有效数据写入新的 Base Rowset - 删除原 Rowset 及其 Bitmap 文件 输出: - 单一干净的 Base 文件 - 存储空间释放,查询更高效
BE 配置优化(be.conf)
mow_compaction_threads = 8 enable_mow_compaction = true mow_compaction_min_rows = 100000 mow_compaction_interval_seconds = 3600
表级别参数调整
ALTER TABLE user_profile SET (
"enable_mow_compaction" = "true",
"mow_compaction_threshold" = "3"
);
表示连续产生 3 个 Delta 版本后触发 MOW 合并。
典型生产场景调优指南
场景一:实时用户画像系统
特点:高频更新(每秒千级)、高并发点查。
- 增加分桶数至 16~32,分散热点;
- 启用 ZSTD 压缩提升 I/O 效率;
- 设置固定大 Segment(256MB),减少元数据开销;
- 批量攒批写入,避免单条频繁更新。
场景二:订单状态变更系统
特点:每日全量快照 + 小时级增量修正。
- 采用分区表设计,按天/小时划分;
- 使用临时分区导入全量数据,验证完成后原子替换主分区;
- 避免全局版本激增影响在线查询。
场景三:高并发主键查询服务
如缓存替代、API 查询接口。
- 开启行存模式:
"store_row_column" = "true"; - 配合主键索引实现极速定位;
- P99 延迟可控制在 10ms 以内。
监控与告警体系建设
关键诊断 SQL
-- 查看 Tablet 详细信息
SHOW TABLET FROM user_profile;
-- 检测 Delete Bitmap 过大表
SELECT
table_name,
SUM(delete_bitmap_size) / 1024 / 1024 AS bitmap_mb
FROM information_schema.tablets
WHERE enable_mow = TRUE
GROUP BY table_name
HAVING bitmap_mb > 1024;
-- 查看 Compaction 积压情况 SHOW PROC '/compactions';
标准化告警规则
| 指标 | 正常范围 | 告警阈值 | 风险说明 |
|---|---|---|---|
| Delete Bitmap 总大小 | < 100MB | > 1GB | 位图过大拖慢查询 |
| 单 Tablet 版本数 | < 50 | > 100 | 合并滞后,资源紧张 |
| MOW 合并队列长度 | 0~5 | > 10 | 任务积压,需扩容 |
常见问题与解决方案
问题一:Delete Bitmap 持续增长
原因:更新频繁但 Compaction 未及时触发。
解决:
-- 手动触发合并
ALTER TABLE user_profile COMPACT;
-- 调整自动合并频率
ALTER TABLE user_profile SET ("mow_compaction_threshold" = "2");
问题二:开启 MOW 后写入变慢
原因:主键查重开销大、分桶不合理、频繁单条更新。
解决:
- 扩大分桶数量以分散负载;
- 强制使用批量导入代替单条语句;
- 将
edit_log_dir和tablet_meta_dir部署在 SSD 上加速索引访问。
问题三:查询偶发超时或抖动
原因:后台 Compaction 占用资源过多,或存在热点主键。
解决:
- 将大规模合并调度至业务低峰期;
- 为关键表配置缓存策略;
- 设置合理的查询超时时间防止雪崩。
选型建议与最佳实践清单
适用场景
- ✅ 用户属性实时更新
- ✅ 订单状态追踪
- ✅ 维表实时化
- ✅ 高频点查服务
不推荐场景
- ❌ 日志流水类纯追加场景
- ❌ 每秒超过 5 万次写入的极端高频写入
- ❌ 无需更新/删除功能的报表表
建表必选配置模板
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"bloom_filter_columns" = "user_id",
"compression" = "ZSTD",
"segment_size" = "268435456",
"light_schema_change" = "true"
);
日常运维规范
- 定期巡检 Delete Bitmap 大小、版本数、合并队列;
- 每周在低峰期手动触发一次全表 Compaction;
- 坚持批量写入原则,禁止高频单条 UPDATE/DELETE。