Hudi的存储类型

创建Hudi数据集时,您可以指定该数据集是写入时复制还是读取时合并两种存储类型。

  • 写入时复制(CoW)–数据以列格式(Parquet)存储,并且每次更新都会在写入过程中创建文件的新版本。 CoW是默认存储类型。

  • 读取时合并(MoR)–数据使用列式(Parquet)+ 基于行(Avro)格式的组合存储。更新记录到基于行的增量文件中,然后根据需要进行同步或者异步的压缩以生成列存储文件的新版本。

对于CoW数据集,每次对记录进行更新时,都会使用更新后的值来重写包含记录的文件。对于MoR数据集,每次数据更新时,Hudi都只记录更改的那一行。 MoR更适合读取次数较少而写入或更改频繁的工作负载。 CoW更适合于不那么频繁更改数据而重读取的工作负载。

权衡要素 写时复制 读时合并
数据延迟 更高 更低
更新代价(I/O) 更高(重写整个parquet文件) 更低(追加到增量日志)
Parquet文件大小 更小(高更新代价(I/o)) 更大(低更新代价)
写放大 更高 更低(取决于压缩策略)

写入时复制(CoW)

写时复制存储中的文件片仅包含基本/列文件,并且每次提交都会生成新版本的基本文件。

换句话说,我们压缩每个提交,从而所有的数据都是以列数据的形式储存。在这种情况下,写入数据非常昂贵(我们需要重写整个列数据文件,即使只有一个字节的新数据被提交),而读取数据的成本则没有增加。

这种视图有利于读取繁重的分析工作。

以下内容说明了将数据写入写时复制存储并在其上运行两个查询时,它是如何工作的。

随着数据的写入,对现有文件组的更新将为该文件组生成一个带有提交即时时间标记的新切片,而插入分配一个新文件组并写入该文件组的第一个切片。 这些文件切片及其提交即时时间在上面用颜色编码。

针对这样的数据集运行SQL查询(例如:select count(*)统计该分区中的记录数目),首先检查时间轴上的最新提交并过滤每个文件组中除最新文件片以外的所有文件片。

如您所见,旧查询不会看到以粉红色标记的当前进行中的提交的文件,但是在该提交后的新查询会获取新数据。因此,查询不受任何写入失败/部分写入的影响,仅运行在已提交数据上。

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

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

读取时合并(MoR)

读时合并存储是写时复制的升级版,从某种意义上说,它仍然可以通过读优化表提供数据集的读取优化视图(写时复制的功能)。

此外,它将每个文件组的更新插入存储到基于行的增量日志中,通过文件id,将增量日志和最新版本的基本文件进行合并,从而提供近实时的数据查询。因此,此存储类型智能地平衡了读和写的成本,以提供近乎实时的查询。

这里最重要的一点是压缩器,它现在可以仔细挑选需要压缩到其列式基础文件中的增量日志(根据增量日志的文件大小),以保持查询性能(较大的增量日志将会提升近实时的查询时间,并同时需要更长的合并时间)。

以下内容说明了存储的工作方式,并显示了对近实时表和读优化表的查询。 此示例中发生了很多有趣的事情,体现出该方法的微妙之处。

  • 图解示例中我们每1分钟左右就有一次提交,这是其他存储类型无法做到的。
  • 在每个文件id组中,都有一个增量日志,其中包含对基础列文件中记录的更新。在示例中,增量日志包含10:05至10:10的所有数据。与以前一样,基本列式文件仍使用提交进行版本控制。因此,如果只看一眼基本文件,那么存储布局看起来就像是写时复制表的副本。
  • 定期压缩过程会从增量日志中合并这些更改,并生成基础文件的新版本,就像示例中10:05发生的情况一样。
  • 有两种查询同一存储的方式:读优化(RO)表和近实时(RT)表,具体取决于我们选择查询性能还是数据新鲜度。
  • 对于RO表来说,提交数据在何时可用于查询将有些许不同。 请注意,以10:10运行的(在RO表上的)此类查询将不会看到10:05之后的数据,而在RT表上的查询总会看到最新的数据。
  • 何时触发压缩以及压缩什么是解决这些难题的关键。通过实施压缩策略,在该策略中,与较旧的分区相比,我们会积极地压缩最新的分区,从而确保RO表能够以一致的方式看到几分钟内发布的数据。

读时合并存储上的目的是直接在DFS上启用近实时处理,而不是将数据复制到专用系统,后者可能无法处理大数据量。

该存储还有一些其他方面的好处,例如通过避免数据的同步合并来减少写放大,即批量数据中每1字节数据需要的写入数据量。