当前位置:首页 > 技术 > 正文内容

Doris Unique Key 模型深度解析:Merge-on-Write 实现原理与生产级调优实战

访客 技术 2026年6月26日 1

适用场景与核心价值

本文面向数据仓库开发、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 实现,具备高压缩比和快速位运算能力。当某条主键被更新时,系统会:

  1. 利用主键索引定位该键所在的老 Rowset 和偏移位置;
  2. 在新 Rowset 的 Delete Bitmap 中标记该位置为"已删除";
  3. 将新值写入当前 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"
);

写入执行步骤

  1. 客户端提交写入请求(StreamLoad / Broker Load / INSERT INTO);
  2. FE 路由至对应 BE 节点;
  3. BE 使用布隆过滤器 + 主键索引定位旧版本主键;
  4. 若存在旧版本,则在即将生成的 Delta Rowset 中设置 Delete Bitmap 标记;
  5. 新数据写入内存结构(MemTable),达到阈值后刷盘为 Segment;
  6. 增量 Rowset 提交,Delete Bitmap 生效,数据对外可见。

不同操作类型处理逻辑

  • INSERT:主键不存在,直接写入,无需打标。
  • UPDATE:查到旧值 → 打删除标记 → 写入新值。
  • DELETE:查到目标主键 → 在 Bitmap 中标记作废 → 不写新数据。

批量写入优化建议

单条 UPDATE 性能极差,应采用批量导入方式:

  • 每批 1,000 ~ 10,000 条数据;
  • 使用 StreamLoad 导入 CSV 或 JSON 格式更新集;
  • 启用批内去重减少内部冲突。

查询机制:高效过滤与谓词下推

MOW 查询路径如下:

  1. 根据分区和分桶定位目标 Tablet;
  2. 加载所有 Rowset 列表及其对应的 Delete Bitmap;
  3. 使用主键索引快速定位候选行;
  4. 结合 ZoneMap、Bloom Filter 和 Delete Bitmap 提前裁剪无效数据块;
  5. 仅读取未被标记的有效记录返回结果。

得益于上述机制,即使经历数十万次更新,查询仍能保持毫秒级响应。

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_dirtablet_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"
);

日常运维规范

  1. 定期巡检 Delete Bitmap 大小、版本数、合并队列;
  2. 每周在低峰期手动触发一次全表 Compaction;
  3. 坚持批量写入原则,禁止高频单条 UPDATE/DELETE。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。