Apache Hudi 社区一直在快速发展,各公司正在寻找方法来利用其强大的功能来有效地摄取和管理大规模数据集。每周社区都会收到一些常见问题,最常见的问题与 Hudi 如何执行更新插入有关,以确保以低延迟访问最新数据。
选择合适的存储表类型
快速更新插入的主要考虑因素之一是选择正确的存储表类型。Hudi 支持两种不同的存储表类型——Copy-On-Write (COW) 和 Merge-On-Read (MOR)。由于处理数据更新的方法不同,每种表类型都会对 upsert 性能产生不同的影响。
COW表
与 MOR 表相比,COW 表的操作更简单,因为所有更新都写入 Apache Parquet 格式的基础文件。不需要运行像压缩这样的单独服务来管理任何日志文件以提高读取或存储效率。COW通过完全重写文件以生成新版本的基本文件来处理更新。因此 COW 表表现出更高的写放大,因为创建新的基本文件版本会进行同步合并。然而 COW 表的一个关键优势是它们的零读取放大,因为所有数据都在基础文件中可用,随时可以读取。查询所需的磁盘读取很少,因为它们不需要读取多个位置或合并数据。
MOR表
与 COW 表相比,MOR 表具有更高的操作复杂性。MOR 不会重写整个文件,而是将更新写入单独的日志文件,然后这些日志文件稍后与基本文件合并为一个新的文件版本,这是通过压缩服务完成的。需要压缩来限制日志文件的增长,这样查询性能就不会下降并优化存储。
直接写入日志文件避免了多次重写整个基本文件,从而降低了写入放大——如果正在处理流数据,这种差异就会变得很明显,也就是说 MOR 表进行了写入优化。但是由于需要读取基本文件和日志文件并动态合并数据,MOR 表在压缩之间对快照查询有更高的读取放大。
COW 和 MOR 表的注意事项
如果更新插入比率很高并且对摄取延迟很敏感,那么更适合使用 MOR 表。如流数据源——通常会希望更快地根据洞察采取行动,以便为用户提供相关和及时的信息。但是如果工作负载更多地基于插入,并且可以容忍合理的摄取延迟,那么更适合使用 COW 表。
根据记录键选择正确的索引类型
通过利用索引,Hudi 在更新插入期间查找记录时避免全表扫描,这比较耗费时间和资源。Hudi 的索引层将记录键映射到相应的文件位置,索引层是可插拔的,有多种索引类型可供选择。需要考虑的是索引延迟取决于多种因素,例如正在摄取多少数据、表中有多少数据、是否有分区表或非分区表、选择的索引类型、工作负载的更新程度和记录键的时间特性。根据所需的性能和唯一性保证,Hudi 提供了不同的开箱即用的索引策略,可以分为全局或非全局索引[1]。
全局与非全局索引
• 非全局索引:Hudi 确保一对分区路径和记录键在整个表中是唯一的。索引查找性能与正在摄取的传入记录之间的匹配分区的大小成正比。
• 全局索引:该索引策略在表的所有分区中强制执行键的唯一性,即保证对于给定的记录键,表中恰好存在一条记录。全局索引提供了更强的保证,但是更新/删除成本随着表的大小而增长。
由于唯一性保证的差异,全局与非全局之间的主要考虑因素之一与索引查找延迟有关:
非全局索引仅查找匹配的分区:例如如果有 100 个分区并且传入的批处理仅包含最后 2 个分区的记录,则只会查找属于这 2 个分区的文件组。对于大规模的更新插入工作负载可能需要考虑非全局索引,例如非全局布隆、非全局简单索引和桶索引。
全局索引查看所有分区中的所有文件组:例如如果有 100 个分区并且传入的记录批次中有最后 2 个分区的记录,则将查找所有 100 个分区中的所有文件组(因为 Hudi 必须保证整个表中只有一个版本的记录键)。这会增加大规模更新插入工作负载的延迟。
Hudi 提供开箱即用的索引类型
• 布隆索引:这是一种索引策略,可以有效地管理文件组中的更新插入和记录查找。该索引利用布隆过滤器,这是一种概率数据结构,有助于确定给定记录键是否存在于特定文件组中。适用于全局和非全局索引。
• 简单索引:这是一种索引策略,它提供了一种将记录键映射到其相应文件组的直接方法。它针对从存储表中提取的键执行传入更新/删除记录的连接。适用于全局和非全局索引。
• HBase索引:该索引策略使用HBase存储索引来映射记录键及其在文件组中对应的文件位置。适用于全局索引。
• 桶索引:这是一种索引策略,它使用散列将记录路由到静态分配的文件组。适用于非全局索引。
• 一致性哈希桶索引:这是一种索引策略,是桶索引的高级版本。虽然桶索引需要为每个分区预先分配文件组,但使用一致的哈希索引可以根据负载动态地增加或收缩每个分区的文件组。适用于非全局索引。
更新密集型工作负载要考虑的索引类型
• Bloom 索引:如果记录键按某些标准(例如基于时间戳)排序并且更新与最近的数据集相关,那么这对于更新繁重的工作负载是一个很好的索引策略。例如如果记录键是根据时间戳排序的,并且我们在最近几天更新数据。
• Bloom 索引用例:假设每 10 分钟就会摄取一批新数据。我们假设新批次包含最近 3 天内的数据更新。Hudi 根据布隆索引,识别出文件组中的候选更新记录,并从基础文件页脚中获取布隆过滤器,进一步裁剪文件组中每个文件中要查找的记录。如果没有找到记录则被视为插入。
• 简单索引:如果偶尔更新整个表范围内的文件并且记录键是随机的,即不基于时间戳,那么这对于更新繁重的工作负载是一个很好的索引策略。
• 简单索引用例:如果有一个维度表,其中记录键是旅行 ID(随机 UUID)并且分区是按城市 ID。如果我们要更新分布在一系列城市的 10000 条行程,Hudi 首先根据传入的城市 ID 识别相关分区。Hudi 通过执行连接有效地找到包含记录的文件。
• 桶索引:如果每个分区存储的数据总量在所有分区中都相似,这是一个很好的索引策略。每个分区的桶(或文件组)数量必须预先为给定的表定义。更多细节参考如下文档[2]。
• 桶索引用例:当定义桶数量后,Hudi会对记录键应用一个哈希函数来将记录均匀地分布在桶中。哈希函数将每个记录 ID 分配给一个桶号,当更新时 Hudi 将哈希函数应用于记录 ID 并确定相应的桶,然后 Hudi 将写入委托给相应的桶(文件组)。
分区路径粒度
分区是一种技术,用于根据数据集中的某些属性或列将大型数据集拆分为较小的、易于管理的部分。这可以大大提高查询性能,因为在查询期间只需要扫描数据的一个子集。然而分区的有效性在很大程度上取决于分区的粒度。
一个常见的误区是将分区设置得过于精细,例如按 //
划分分区。根据工作负载每小时粒度的数据可能不足,从而导致许多只有几千字节的小文件。如果小文件越多,磁盘寻道成本就越高,查询性能就会下降。其次在摄取方面,小文件也会影响索引查找,因为修剪不相关文件需要更长的时间。根据正在使用的索引策略,这可能会影响写入性能。因此建议用户始终从较粗糙的分区方案开始,如 / 以避免小文件的问题,如果仍然觉得需要粒度分区,可以根据查询模式重新评估分区方案和/或可以潜在地利用Clustering服务来平衡摄取和查询性能。