1. 引入
线上用户反馈使用 Presto 查询 Hudi 表出现错误, 而将 Hudi 表的文件单独创建 parquet 类型表时查询无任何问题, 关键报错信息如下
40931f6e-3422-4ffd-a692-6c70f75c9380-0_0-384-2545_20200513165135.parquet, start=0, length=67108864, fileSize=67108864, hosts=[], forceLocalScheduling=false, partitionName=dt=2020-05-08, s3SelectPushdownEnabled=false} (start = 2.3651547291593433E10, wall = 163 ms, CPU = 0 ms, wait = 0 ms, calls = 1): HIVE_BAD_DATA: Not valid Parquet file:
报 Hudi 表中文件格式不是合法的 parquet 格式错误.
2. 问题复现
开始根据用户提供的信息, 模拟线上 Hudi 数据集大小, Presto 和 Hudi 版本 (0.5.2-incubating) 来复现该问题.
进行试验发现当 Hudi 表单文件大小较小时, 使用 Presto 查询一切正常.
构建 Hudi 表中单文件大小为 100MB 以上数据集, 使用 Presto 查询.
可以看到, 当 Hudi 数据集中文件大小为 100MB 时复现了 Not Valid Parquet file 异常, 通过 Presto 的 web ui 可以看到具体的错误堆栈如下
通过错误堆栈可以进一步确认在读取 parquet 文件时校验失败, 开始怀疑 parquet 文件确实被损坏, 但使用 parquet-tools 工具检查本地 parquet 文件, 发现无问题.
3. 问题排查
经过上述步骤复现了问题, 问题能够复现就好排查. 但 Presto 对于合法 parquet 文件检查为何会报错? 带着这个疑问开始在本地 debug Presto, 首先在 Presto 服务端和 IDEA 中进行相应的配置.
3.1 Presto 服务端配置
要想能够连接到 Presto 服务端, 需要在 PRESTO_HOME 根目录下创建 etc 目录, 然后创建 jvm.properties 文件, 内容如下
- -server
- -Xmx8G
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=32M
- -XX:+UseGCOverheadLimit
- -XX:+ExplicitGCInvokesConcurrent
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:+ExitOnOutOfMemoryError
- -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
- -XX:+TraceClassLoading
- -XX:+TraceClassUnloading
- -verbose:class
上述配置除了可以连接服务端进行 debug 外, 添加的 - XX:+TraceClassLoading,-XX:+TraceClassUnloading 两个配置项, 还会打印每个类加载和卸载的日志(这个在排查 presto 类加载器问题时非常有用, 建议开启).
3.2 IDEA 配置
配置完 Presto 服务端后, 在 IDEA 进行如下配置即可.
3.3 单步调试
IDEA 中开启了 debug 后, 通过 Presto 客户端查询时(select * from hudi_big_table), 就可以进行单步调试, 首先我们在 BackgroundHiveSplitLoader 类中打了些断点(该类是加载 Split 的关键类).
通过 shouldUseFileSplitsFromInputFormat 方法判断是否直接通过注解 (@UseFileSplitsFromInputFormat) 获取 FileSplit.Hudi 与外部系统交互的 HoodieParquetInputFormat 和 HoodieParquetRealtimeInputFormat 两个类都使用了该注解.
从上图可以看到 100MB 的文件被分成了四个 InputSplit(按照 32MB 大小进行切分), 后续 Presto 会根据 InputSplit 来构造对应的 InternalHiveSplit.
进一步在异常堆栈地方打断点如下
根据上述代码逻辑可知, 从文件中读取 magic 与 parquet 文件的 MAGIC 不相等导致抛出了异常.
值得注意的是 fileSize 的大小为 33554432, 表示一个 InputSplit 的大小, 而并非文件大小, 因此获取 metadataLength 时并不准确, 导致并非读取了 parquet 文件的 magic, 而是读取了 InputSplit 的数据, 因此校验时抛出异常. 理论上对于不同的 InputSplit, 该方法传入的 fileSize 大小应该等于文件的大小, 而非 InputSplit 的大小, 那么这个 fileSize 的大小是在哪个步骤传递错误的呢? 带着这个疑问, 继续进行 debug.
根据前面 debug 信息得知 Presto 会通过 InputSplit 创建 InternalHiveSplit, 继续 debug 生成 InternalHiveSplit 的逻辑
可以看到在上面构造 InternalHiveSplit 时, 传递的参数值为 start=0,start + length=33554432,length=33554432, 而 InternalHiveSplit 本身的参数对应为 start,end,fileSize, 可以看到错误地将 length 当成 fileSize 传递了.
既然怀疑这个参数传递错误导致了异常, 那么修改参数为 fileSize 后是否可以修复该问题? 于是打包验证观察异常是否还会出现, 即对 presto-hive 模块重新打包, 放入 $PRESTO_HOME/plugin/presto-hive 目录中, 重启 Presto 服务, 再次进行验证.
可以看到修改参数后, 查询一切正常!!!
另外对 Hudi 的小文件也进行了回归测试, 查询也正常! 自此可以发现是由于参数不对的 bug 导致了异常, 鉴于这个 bug 对 Presto 社区其他用户也可能产生影响, 于是查看 Presto 的 master 分支是否修复了该问题, 若未修复, 可将该 patch 回推到社区, 于是查看了 Presto 的 master 分支对应代码, 发现已经有开发者修复了!
找到对应的 PR:https://github.com/prestodb/presto/pull/14355(也仅仅只是修改了上述的一行代码), 在 4 月 7 号合入 master 分支, 从这个 PR 得知, 该 bug 是由 https://github.com/prestodb/presto/pull/12780 引入.
3.4 影响的版本
由于该缺陷是在 2019 年 5 月引入 Presto 社区, 在 2020 年 4 月得以修复, 期间发布的版本 (0.221 ~ 0.235) 都会受到影响, 如本地测试 0.227,0.231 版本都有问题. 最近社区发布了 0.236 版本修复了该问题, 如果生产环境使用的版本在 0.221 ~ 0.235 之间, 建议升级或者 cherry-pick 对应的 patch.
4. 总结
根据线上用户反馈查询 Hudi 表问题, 由于线上环境不好 debug, 需根据上线环境在本地模拟复现问题, 然后快速 debug 排查修复问题. 当然本篇文章省略了 debug 的旁路路径, 只给出了 debug 的关键路径.
来源: https://www.cnblogs.com/leesf456/p/12943901.html