Hudi 生产问题排障-Hudi表数据查询字段丢失

0 阅读5分钟

一、背景与问题   

我们生产环境下使用Flink+Hudi完成实时入湖(DWD),配合Spark+Hudi多表关联的定时微批任务,实现汇总指标的准实时加工处理,写入Hudi汇总表(DWS)。在下游查询分析端,我们提供了Kyuubi作为统一 SQL 网关,对接 Trino(427)和 Spark两种查询引擎,用于查询数据湖的Hive/Hudi表数据,以满足多样化的分析需求。     

有一次,一个下游项目组反馈,从某个时间点开始,通过 Kyuubi-Trino查询某张 Hudi 表的结果数据部分字段缺失(字段内容空值)。

二、排查与分析

1.初步排查   

因用户反馈是从某一时刻开始出现字段空值,我们首先检查该Hudi表的上游Flink作业与Spark微批作业的运行情况,然而并未发现异常,作业均稳定运行,各项监控指标都健康正常。   

接下来我们验证部分字段出现空值是否为正常现象,即从某时刻起上游的该数据字段(问题来源)本身是空,我们在Kafka服务端通过kafka-console-consumer排查上游数据内容发现该字段是有数据的,排除源头问题。考虑到用户是使用Kyuubi-Trino查询方式,我们决定使用Kyuubi-Spark方式复查一次,以排除查询引擎的差别,结果通过 kyuubi-Spark查询同一张表时,对应缺失的字段(问题来源,即CREATOR_CHANNLE)数据是能被查询出来的,基本确定此问题是查询引擎的差异导致的。

2.Spark/Trino引擎查询Hudi表差异分析   

首先检查该 Hudi 表的类型,通过 Hudi 表的元数据确认,该表为 MOR(Merge On Read)类型。MOR 表的特点是:写操作先写入 avro 格式的 log 文件,随后通过异步或同步的 compaction 将 log 文件合并为 parquet 格式的 base 文件。查询时,若需看到最新数据,必须同时读取 base 文件和 log 文件并合并。   

接着我们怀疑会不会是两种查询引擎对Hudi-MOR表的查询行为有差异呢?通过查阅官方文档,结果如下:

  • Spark 查询 Hudi MOR 表:Spark 的 Hudi 数据源实现了 实时视图,默认会合并 base 文件和 log 文件。它通过 HoodieRealtimeFileReader等组件,在读取时动态合并 parquet 和 avro 数据,因此能返回完整的最新记录。
  • Trino 查询 Hudi MOR 表:Trino 的 Hudi 连接器目前仅支持读取 Hudi 表的 base 文件(parquet),尚未实现 log 文件的解析与合并。这意味着 Trino 只能看到已经完成 compaction 并被固化到 parquet 文件中的数据,而仍在 log 文件中的增量更新则被忽略。

    排查到这里,发现两种引擎对接Hudi的查询设计差异,此问题大概率是由于空列的字段数据仍在Hudi-MOR表的log文件中未被合并,导致查询为空。那log文件为什么没有被合并呢?   

    Hudi结果表的上游是一个Spark微批作业,我们检查作业参数发现,写入该 Hudi-MOR 表的 Spark 作业未开启即时合并(inline compaction),即参数 hoodie.compact.inline未设置为 true。默认情况下,Hudi 不会在每次写入时触发 compaction,而是依赖异步 compaction 或手动触发。因此,随着数据持续写入,log 文件不断累积,而 base 文件未能及时合并最新数据,导致Trino查询不到log中的新数据;同时,随着log文件的不断膨胀,长此以往还会拖垮表数据查询的性能直至查询超时。

3.Trino-Hudi connector源码简要分析   

Trino 的 Hudi 连接器在360+的版本后内置,在trino/plugin/trino-hudi目录下,HudiSplitManager.getSplits()方法获取 split 时调用HudiSplitSource的初始化构建,使用HudiReadOptimizedDirectoryLister.listStatus()方法监听basefile,没有log file。在 HudiPageSource中,仅匹配 Parquet 类型 base 文件,其他都会抛出异常。然而在HudiFileGroup中一直包含logfile属性与对应方法,只是Trino一直未使用(个人感觉是出于提升查询性能、提供数据一致性、降低复杂度等方向考虑)。由此可见,问题的根本原因是Spark/Trino引擎对 Hudi-MOR 表支持度不同,加上 compaction 策略配置不当,导致数据一致性问题。

三、解决方案

1.配置及时合并策略

通过修改 Spark 写入 Hudi MOR 表的作业配置,启用Inline Compaction,确保增量数据及时合并到 Parquet Base 文件中,使 Trino 能读取到完整数据。

# 启用即时合并(Inline Compaction)
hoodie.compact.inline=true
# 每1次delta commit后触发一次Inline Compaction(默认值为5,调小阈值加快合并
hoodie.compact.inline.max.delta.commits=1
# 保留1个commit版本(默认值为10,减少历史版本存储占用)
hoodie.cleaner.commits.retained=1

2.调整Hudi表类型

因为是Spark定时微批作业,写负载可控,不用必须使用MOR表;通过修改Hudi结果表的类型为COW表,就能规避Trino查不到log的问题,也能同步提升数据查询的性能。

3.增加Trino查询Hudi-MOR的log解析能力

这是在组件源码层面的根治措施,结合官方文档releaseNote,Trino在450+版本后支持了Hudi的log文件解析;该问题不是强阻塞点,我们通过前两种方案解决了用户Trino查不到最新Hudi数据的问题,Trino版本升级会统一纳入后续的迭代计划。

四、总结展望   

本次生产故障的排查过程,揭示了多引擎查询 Hudi MOR 表时容易忽略的细节:引擎对表类型的支持差异、compaction 策略的重要性。类似问题也可能出现在其他数据湖格式(如 Iceberg)或不同查询引擎之间。当混合使用多种计算引擎时,我们要务必深入了解各引擎对数据湖特性的实现程度,并在写入端做好相应配置,确保数据在不同引擎下的一致性。只有这样,才能充分发挥数据湖的灵活性和查询引擎的性能优势,避免生产故障。