由hudi了解Copy-on-Write(COW)和Merge-on-Read(MOR)

最近专研Hudi,对于其两种存储类型感到迷惑,Copy on Write(COW)和Merge on Read(MOR),本篇文章用来简单总结一下两种方式的异同点。方便自己更好的理解。

Copy on Write(COW)

先总结cow,这是一种较为普遍和通用的存储优化策略在Linux和jdk中都有使用,可以翻译为写时拷贝。在wiki中的定义如下:

如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。

简单来说就是在程序运行阶段,所有线程(A,B,C)共享同一个内存数据,直到其中的一个线程B需要对数据进行修改时,那么系统将会复制一份当前的数据,此后线程B所做的数据修改都是在复制的数据段上进行的。这个过程对所有的线程都是透明的,也就是说AC两个线程使用的还是原来的内存数据,当线程B将数据更改后,系统将数据指针移向修改后的数据段上,这样在所有线程都不知情的情况下完成了数据的更新操作。

在hudi中支持cow,每个存储的文件分片只包含基本列文件(parquet),并且每次提交都会产生新的列文件副本。在新的副本上进行写入操作,这种方式带来的问题就是在每次数据写入时,都需要重写整列数据文件,哪怕只有一个字节的数据写入(也就是所谓的写放大)。

每次新数据的写入,都会基于当前的数据文件产生一个带有提交时间戳的新副本文件,新数据会插入到当前的新副本文件中,直到整个操作没有完成前,所有的查询操作都不会看到这个新的文件副本。
在新文件数据写入提交后,新的查询将会获取到新的数据。因此,查询不受任何写入失败/部分写入的影响,仅运行在已提交数据上。

写时复制存储的目的是从根本上改善当前管理数据集的方式,通过以下方法来实现:

  • 优先支持在文件级原子更新数据,而无需重写整个表/分区
  • 能够只读取更新的部分,而不是进行低效的扫描或搜索
  • 严格控制文件大小来保持出色的查询性能(小的文件会严重损害查询性能)。

同样COW在提供了一些优秀的性能同时也存在一些缺点,在hudi中举例说:

  • 数据延迟较高:只能够保证数据的最终一致性,不能够保证数据的实时一致性。
  • io请求较高:每次数据的更新写入需要重写整个parquet文件。
  • 写放大:每次写入数据,哪怕只有一字节,那么都需要重写整个文件。

当然COW不仅仅在hudi中出现,仔细了解后其在Linux、JDK都有体现:

聊聊并发-Java中的Copy-On-Write容器

Linux写时拷贝技术(copy-on-write)

Merge on Read(MOR)

mor可以称为读时合并,该方案应该说是hudi特有的属性,他算得上是cow的升级版,支持通过读优化表提供数据集的读取优化视图,也就是cow的功能。
此外又添加了基于行的增量日志文件(avro)插入功能,也就是说在当前数据文件更新前将对应的数据插入到日志中。
立即读取那么将会通过parquet和avro合并同时给出最新的读视图,而后其也会对两个文件的数据进行合并,通过文件id,将增量日志和最新版本的基本文件进行合并,从而提供近实时的数据查询。

这种方式可以平衡读写成本,以减少cow带来的数据时延,能够提供能一种近实时的查询。

读时合并存储上的目的是直接在DFS上启用近实时处理,而不是将数据复制到专用系统,后者可能无法处理大数据量。 该存储还有一些其他方面的好处,例如通过避免数据的同步合并来减少写放大,即批量数据中每1字节数据需要的写入数据量。