COW、MOR、MOW

先说结论

COW:Copy-on-Write这是一种较为普遍和通用的存储优化策略在Linux和中都有使用,也叫写时拷贝

  1. 写入时合并,重写整个数据文件,必定存在写入吞吐量低、写入延迟高,写入效率低
  2. 读取时,直接读最新的全量数据,读取性能高

MOR:Merge-on-Read,读时合并

  1. 写入时,追加增量数据,不合并,写入效率低
  2. 读取时,需要合并数据,读取性能低

MOW:Merge-on-Write,Doris2.0后推出的功能

  1. 写入时,会判断是否有该key,若有则先delete记录到Delete Bitmap再insert 实现合并的效果,写入效率高
  2. 读取时,先读取Delete Bitmap,将被标记删除的行过滤掉,只返回有效数据,读取性能高
  3. 高度依赖主键,需要额外存储Delete Bitmap

一.COW

1.先说定义

通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

举个例子 :当一条 UPDATEDELETE 操作到来时,Flink 作业(如 Flink CDC 或使用 Hudi CoW 表的作业)会:

  1. 定位到这条记录所在的原始数据文件(parquet文件)
  2. 读取整个原始文件到内存。
  3. 在内存中合并更改:应用更新或删除标记。
  4. 将整个合并后的新数据写回一个新的文件
  5. 更新元数据,将查询指向新文件,并异步删除旧文件

2.适用场景

  • 读多写少的维表,对查询性能和延迟有极致要求的场景
  • 对写入要求不高

3.优缺点

优点

  • 极佳的查询性能:读取路径简单高效,直接读取合并好的列式文件。
  • 数据立即可见:数据一旦写入就是最终状态,没有状态延迟。
  • 简化数据模型:对查询引擎透明,任何支持 Parquet/ORC 的引擎都可以直接高效查询。

缺点

  • 写入放大 (Write Amplification) :重写整个文件导致极高的 I/O 成本和写入延迟。
  • 写入吞吐量低:不适合高频更新的场景。
  • 存储成本较高:在旧文件被清理前,同一数据会有多个副本存储。

二.MOR

1.先说定义

举个例子 :当一条 UPDATEDELETE 操作到来时,Flink 作业会:

  1. 将这条变更记录(通常是完整的行) 直接以行格式(如Avro) 快速追加写入到一个专门的增量日志文件中。
  2. 原有的基础文件(Base File,Parquet格式)保持不变
  3. 表的元数据中会记录哪些增量日志文件对应哪个基础文件。
  4. 当有查询到来时:
    • 读优化查询(Read Optimized) :仅读取基础文件(Parquet),查询不到最新的更新,只能看到上次Compaction前的状态。
    • 快照查询(Snapshot Query) :查询引擎需要同时读取基础文件(Parquet)和增量日志文件(Avro) ,然后在内存中进行合并,得到最新状态的数据。这个过程有计算开销

2.适用场景

  • 写多读少的事实表,低延迟写入,高吞吐写入
  • 允许查询有一定延迟

3.优缺点

优点

  • 极高的写入吞吐量和低延迟:写入是顺序追加,速度极快。
  • 支持真正的流式更新:非常适合处理无界的实时数据流。

缺点

  • 查询性能开销大且不稳定:快照查询需要在读取时合并数据,消耗大量 CPU 和内存,延迟高。
  • 架构复杂 :必须引入并精心调优后台压缩 (Compaction) 作业。如果压缩跟不上写入,日志会膨胀,查询性能会持续恶化。
  • 数据延迟性:"读优化查询"看到的数据不是最新的,需要合并。

三.MOW---Doris2.0后推出

1.先说定义

基本工作原理:MoW的核心思想是在数据写入时就完成新旧数据的合并操作,而非等到查询时才进行合并,从而显著提升查询性能。

  1. 对于每一条待写入的数据,系统会通过主键索引查找该键在基础数据(Base Data)中的位置
  2. 如果该键已存在,则将原数据标记为删除(记录在Delete Bitmap中)
  3. 将新数据写入新的Rowset中,使新数据立即可见
  4. 查询时只需过滤掉被标记删除的行,即可获取最新数据

Doris的MOW(Merge-on-Write)模式配合Delete Bitmap + Primary Index技术

2.Delete + Insert机制

Doris的MoW实现参考了微软SQL Server在2015年VLDB上提出的方案,采用"Delete + Insert"的方式处理更新:

  1. 主键查找:对于每条待写入的Key,通过主键索引查找其在Base数据中的位置(rowsetid + segmentid + 行号)
  2. 标记删除:如果Key存在,则将该行数据标记删除,删除信息记录在Delete Bitmap中(每个Segment对应一个Delete Bitmap)
  3. 写入新数据:将更新的数据写入新的Rowset中,完成事务使新数据可见
  4. 查询过滤:查询时读取Delete Bitmap,将被标记删除的行过滤掉,只返回有效数据

这种设计的优势在于:

  • 任何有效的主键只存在于一个地方(要么在Base Data中,要么在Delta Store中)
  • 避免了查询时的大量归并排序消耗
  • Base数据中的各种列存索引仍然有效

3.适用场景

  • 需要高性能upsert、高吞吐写入、低延迟查询
  • 高度依赖主键,对存储空间无极致要求

4.优缺点

优点

  • 优秀的读写平衡:既保证了接近 MOR 的高写入吞吐,又提供了接近 COW 的低延迟查询。
  • 高效的 Upsert:通过"Delete + Insert"机制,完美支持主键更新。
  • 避免读时合并:查询引擎无需进行昂贵的合并计算,直接读取即可。
  • 索引有效:Base 数据中的前缀索引、Bloom Filter 等仍然有效,因为数据本身没有改变,只是被标记了。

缺点

  • 依赖主键索引:性能和效率高度依赖于主键索引的设计和查找速度。如果主键是随机分布且数据量极大,索引查找可能成为瓶颈。
  • 存储放大:虽然避免了文件重写,但需要额外的存储空间来维护 Delete Bitmap。
  • Compaction 依然需要
相关推荐
程序员小假2 小时前
我们来说说当一个线程两次调用 start() 方法会出现什么情况?
java·后端
bobz9652 小时前
I/O复用 select、poll、epoll
后端
无限大62 小时前
一文读懂HTTP 1.1/2.0/3.0:从原理到应用的通俗解析
后端·面试
SimonKing3 小时前
Archery:开源、一站式的数据库 SQL 审核与运维平台
java·后端·程序员
AI小智3 小时前
为了帮我搞定旅行清单:我的小白老婆报名了30万奖金的黑客松!
后端
双向333 小时前
RTX 4090助力深度学习:从PyTorch到生产环境的完整实践指南
后端
shengjk13 小时前
Java vs Python Web 服务器深度对比:从传统到现代的演进之路
后端
绝无仅有3 小时前
某辅导教育大厂真实面试过程与经验总结
后端·面试·架构
绝无仅有3 小时前
Java后端技术面试:银行业技术架构相关问题解答
后端·面试·github