运维 Hudi

故障排查

师成师成· 更新于 2026-09-29· 阅读 23 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

关于性能相关的问题,请参阅性能调优指南

写入表

org.apache.parquet.io.InvalidRecordException: Parquet/Avro schema mismatch: Avro field 'col1' not found

在使用 Hudi 时,建议以向后兼容的方式演进 schema。有关 Avro schema 解析的更多信息,请参阅:https://avro.apache.org/docs/1.8.2/spec.html。当 schema 以向后不兼容的方式演进(例如删除了某列 'col1'),而我们又试图更新 Parquet 文件中已使用旧 schema(包含 'col1')写入的记录时,通常会出现此错误。在这种情况下,Parquet 会尝试在传入记录中查找所有现有字段,当发现 'col1' 不存在时,就会抛出上述异常。

解决方法是尝试使用该事件迄今为止演进出的所有 schema 版本创建一个合并后的总体 schema,并将其作为目标 schema。一种较好的做法是从 Hive Metastore 获取 schema 并与当前 schema 进行合并。

以下是新一批更新中省略了名为 "toBeDeletedStr" 字段的示例堆栈信息:https://gist.github.com/nsivabalan/cafc53fc9a8681923e4e2fa4eb2133fe

java.lang.UnsupportedOperationException: org.apache.parquet.avro.AvroConverters$FieldIntegerConverter

此错误同样是由于 schema 以非向后兼容的方式演进导致的。简单来说,存在一条针对记录 R 的传入更新 U,而 R 已通过相关 Parquet 文件写入到你的 Hudi 数据集中。R 包含字段 F,其数据类型为 long,而 U 中的字段 F 的数据类型已更新为 int。Parquet 文件系统不支持这种不兼容的数据类型转换。

对于此类错误,请确保从摄取的主要数据源中只发生有效的数据类型转换。

以下是在 Hudi 中尝试将字段从 Long 类型演进为 Integer 类型时的示例堆栈信息:https://gist.github.com/nsivabalan/0d81cd60a3e7a0501e6a0cb50bfaacea

SchemaCompatabilityException: Unable to validate the rewritten record

如果 schema 中存在某个非可空字段,其值缺失或为 null,就可能出现此问题。建议以向后兼容的方式演进 schema。本质上,这意味着要么将每个新添加的字段都设为可空,要么为每个新字段定义默认值。更多信息请参阅 Schema 演进 文档。

INT96、INT64 与 timestamp 的兼容性

https://hudi.apache.org/docs/configurations#hoodiedatasourcehive_syncsupport_timestamp

我看到了大量的归档文件,如何控制生成的归档提交文件数量?

请注意,在不支持日志追加操作的云存储中,Hudi 被迫创建新的归档文件来归档旧的元数据操作。你可以调大 hoodie.commits.archival.batch,以增加每个归档文件中归档的提交数量。此外,你还可以增大两个水位配置之间的差值:hoodie.keep.max.commits(默认值:30)和 hoodie.keep.min.commits(默认值:20)。这样既能减少生成的归档文件数量,又能增加每个归档文件中归档的元数据量。请注意,在 0.7.0 版本引入统一的 Hudi 元数据(RFC-15)之后,后续工作将涉及重新组织归档元数据,以便我们可以定期执行压缩(compaction),从而控制这些归档文件的文件大小。

在 HDFS 上使用带元数据表的 Hudi 时,如何解决来自 HBase 的 NoSuchMethodError?

从 0.11.0 版本开始,我们将 HBase 版本升级到了 2.4.9,该版本是基于 Hadoop 2.x 发布的。Hudi 的元数据表

以 HFile 作为基础文件格式,依赖 HBase 库。在使用 Hadoop 3.x 的 HDFS 上启用 Hudi 表的元数据表时,

由于 Hadoop 2.x 与 3.x 之间的兼容性问题,可能会抛出 NoSuchMethodError。

针对此问题,可通过以下方式规避:

(1) 从 https://github.com/apache/hbase 下载 HBase 源代码;

(2) 使用标签 rel/2.4.9 切换到 2.4.9 版本的源代码:

git checkout rel/2.4.9

(3)打包一个基于 Hadoop 3 版本的 HBase 2.4.9 新版本:

mvn clean install -Denforcer.skip -DskipTests -Dhadoop.profile=3.0 -Psite-install-step

(4) 重新打包 Hudi。

如何解决 hbase-default.xml file seems to be for an older version of HBase 的 RuntimeException?

这通常是因为 classpath 中存在运行时环境提供的其他 HBase 库,例如

Cloudera CDP 堆栈,从而引发冲突。要规避该 RuntimeException,你可以在 hbase-site.xml 配置文件中将

hbase.defaults.for.version.skip 设置为 true,例如在 Cloudera 管理器中覆盖该配置。

我看到同一个记录键值对应了两条不同的记录,每条记录的键都带有不同格式的时间戳。这是怎么回事?

这是为 bulk_insert 操作启用 row-writer 后的已知问题。当你先执行 bulk_insert,随后又执行其他写入操作(如 upsert/insert)时,可能会在时间戳字段上观察到这一现象。例如,bulk_insert 可能为记录键生成时间戳 2016-12-29 09:54:00.0,而非 bulk_insert 的写入操作则可能为记录键生成 1483023240000000 这样的 long 值,从而产生两条不同的记录。要解决此问题,从 0.10.1 开始引入了新配置 hoodie.datasource.write.keygenerator.consistent.logical.timestamp.enabled,无论是否启用 row 写入,都能保证时间戳的一致性。不过,为了保持向后兼容并避免破坏现有管道,该配置默认设置为 false,需要显式启用。

查询表

我现在如何查询刚刚写入的 Hudi 数据集?

除非启用了 Hive 同步,否则使用上述任一方法写入的 Hudi 数据集都可以像其他数据源一样,直接通过 Spark 数据源进行查询。

val hudiSnapshotQueryDF = spark
     .read()
     .format("hudi")
     .option("hoodie.datasource.query.type", "snapshot")
     .load(basePath)
val hudiIncQueryDF = spark.read().format("hudi")
     .option("hoodie.datasource.query.type", "incremental")
     .option("hoodie.datasource.read.begin.instanttime", <beginInstantTime>)
     .load(basePath);

如果在 Hudi Streamer 或 datasource 中启用了 Hive Sync,数据集就会在 Hive 中以若干张表的形式存在,此时可以通过 HiveQL、Presto 或 SparkSQL 进行查询。详见此处。

数据质量问题

以下内容通常有助于排查 Hudi 故障。首先,为了便于使用标准 Hadoop SQL 引擎(Hive/PrestoDB/Spark)进行问题定位,每条记录都会添加以下元数据:

  • _hoodie_record_key - 在每个 DFS 分区中被视为主键,是所有更新/插入操作的依据
  • _hoodie_commit_time - 最后一次修改该记录的提交时间
  • _hoodie_commit_seqno - 该字段包含每次事务中每条记录的唯一序列号
  • _hoodie_file_name - 实际包含该记录的文件名(对排查重复数据非常有用)
  • _hoodie_partition_path - 从 basePath 出发、标识包含该记录的分区的路径

记录缺失

请在该记录可能被写入的时间窗口内,使用管理命令检查是否存在写入错误。

如果确实发现错误,则说明该记录并未被 Hudi 实际写入,而是被返回给应用程序,由应用决定如何处理。

记录重复

首先,请确认在确保查询以正确方式访问 Hudi 表之后,是否确实存在重复数据。

  • 如果确认存在重复,请使用上述元数据字段,定位包含这些记录的物理文件和分区文件。
  • 如果重复数据跨越不同 partitionpath 的文件,说明您的应用为同一个 recordKey 生成了不同的 partitionPath,请修复您的应用。
  • 如果重复数据位于同一 partitionpath 内的多个文件中,请联系邮件列表。这种情况本不应发生。您可以使用 records deduplicate 命令修复数据。

数据摄取

java.io.EOFException: Received -1 when reading from channel, socket has likely been closed. at kafka.utils.Utils$.read(Utils.scala:381) at kafka.network.BoundedByteBufferReceive.readFrom(BoundedByteBufferReceive.scala:54)

如果从 Kafka 源摄取数据、集群默认启用了 SSL,且使用的 Hudi 版本低于 0.5.1,就可能出现此问题。早期版本的 Hudi 使用的是 spark-streaming-kafka-0-8 库。Hudi 0.5.1 版本发布时,Spark 升级到了 2.4.4,spark-streaming-kafka 库也升级到了 spark-streaming-kafka-0-10。SSL 支持是从 spark-streaming-kafka-0-10 开始引入的。详情请参见此处。

变通方法有两种:要么使用未启用 SSL 的 Kafka 集群,要么将 Hudi 版本升级到至少 0.5.1,或者将 spark-streaming-kafka 库升级到 spark-streaming-kafka-0-10。

java.lang.IllegalArgumentException: Could not find a 'KafkaClient' entry in the JAAS configuration. System property 'java.security.auth.login.config' is not set

当你尝试从启用 SSL 的 Kafka 数据源摄入数据,而当前环境无法读取 jars.conf 文件及其属性时,可能会出现此问题。要修复此问题,你需要通过 spark-submit 命令传递所需的属性,示例如下

--files jaas.conf,failed_tables.json --conf 'spark.driver.extraJavaOptions=-Djava.security.auth.login.config=jaas.conf' --conf 'spark.executor.extraJavaOptions=-Djava.security.auth.login.config=jaas.conf'

写入 GCS 时出现 IOException: Write end dead 或 CIRCULAR REFERENCE

如果遇到以下堆栈跟踪信息,请按如下建议设置 Spark 配置。

--conf 'spark.hadoop.fs.gs.outputstream.pipe.type=NIO_CHANNEL_PIPE'
 at org.apache.hudi.io.storage.HoodieAvroParquetWriter.close(HoodieAvroParquetWriter.java:84)
    Suppressed: java.io.IOException: Upload failed for 'gs://bucket/b0ee4274-5193-4a26-bcff-d60654fd7b24-0_0-42-671_20230228055305900.parquet'
        at...
        ... 44 more
    Caused by: java.io.IOException: Write end dead
        at java.base/java.io.PipedInputStream.read(PipedInputStream.java:310)
        at java.base/java.io.PipedInputStream.read(PipedInputStream.java:377)
        at com.google.cloud.hadoop.repackaged.gcs.com.google.api.client.util.ByteStreams.read(ByteStreams.java:172)
        at ...
        ... 3 more
Caused by: [CIRCULAR REFERENCE: java.io.IOException: Write end dead]

我们有一个正在处理此问题的活跃补丁(https://github.com/apache/hudi/pull/7245)。在该补丁合入之前,你可以使用上述配置来绕过此问题。

Hive 同步

SQLException:以下列的类型不兼容

Error while processing statement: FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. Unable to alter table. The following columns have types incompatible with the existing columns in their respective positions : col1,col2

当你尝试使用我们的 HiveSyncTool.java 类向现有的 Hive 表中添加新列时,通常会出现这种情况。数据库通常不允许将列的数据类型从高阶类型修改为低阶类型,也不允许修改的数据类型与表中已存储(或将要存储)的数据发生冲突。要解决此问题,请尝试设置以下属性——

set hive.metastore.disallow.incompatible.col.type.changes=false;

元存储会基于自身的配置来评估该检查,因此上述 set 仅在元存储以内嵌方式存在于同一会话中时才生效。对于远程 Hive 元存储服务,可以将该属性设置在元存储的 hive-site.xml 中并重启服务;也可以针对单个 Beeline 会话使用 metaconf: 前缀进行覆盖,该前缀会将取值推送到此次连接所使用的元存储中:

set metaconf:hive.metastore.disallow.incompatible.col.type.changes=false;

metaconf: 形式通过 HiveServer2 生效。旧版 Hive CLI 会将该值发送给 metastore,但不会将其应用到执行 ALTER 的连接上,因此在那里请使用 hive-site.xml 选项。

HoodieHiveSyncException: Could not convert field Type from <type1> to <type2> for field col1

出现该问题的原因是,HiveSyncTool 目前仅支持少数几种兼容的数据类型转换。执行其他任何不兼容的变更都会抛出此异常。请检查相关字段的数据类型演进情况,并根据 Hudi 代码库确认它是否确实可以被视为有效的数据类型转换。

org.apache.hadoop.hive.ql.parse.SemanticException: Database does not exist: test_db

如果尝试对 Hudi 数据集执行 Hive 同步,而配置的 hive_sync 数据库不存在,通常会出现此问题。请先在 Hive 集群上创建相应的数据库,然后重试。

org.apache.thrift.TApplicationException: Invalid method name: 'get_table_req'

此问题是由 Hive 版本冲突引起的。Hudi 是基于 hive-2.3.x 版本构建的,因此如果仍希望 Hudi 与更旧版本的 Hive 一起工作,

Steps: (build with hive-2.1.0)
1. git clone git@github.com:apache/incubator-hudi.git
2. rm hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/hive/HoodieCombineHiveInputFormat.java
3. mvn clean package -DskipTests -DskipITs -Dhive.version=2.1.0

java.lang.UnsupportedOperationException:不支持重命名表

此问题可能在同步到 Hive 时出现。可能的原因是,如果表名中包含大小写字母,Hive 的处理效果不佳。请将表名全部改为小写字母,问题通常即可解决。相关问题:https://github.com/apache/hudi/issues/2409

尝试同步到元存储时,如何解决 Partitions must be in the same table(分区必须在同一张表中)的 IllegalArgumentException?

当表名中使用了大写字母时会出现此问题。Hive 等元存储会自动将表名转换为小写。虽然 Hudi 允许表名中使用大写字母,但如果要使用元存储,可能需要将表名全部使用小写字母。关于此问题的更多详细信息,可参见此处。

评论

登录后参与评论

正在加载评论…