Apache Hudi权威指南——Hudi 的生产级部署与运维

79 阅读34分钟

从开发环境迁移到生产环境常会带来一组全新的运维挑战。本章将为你提供在复杂环境中平滑管理 Apache Hudi 部署的工具与最佳实践,帮助以最低开销保障流水线的可靠性

首先,我们将探讨表管理与恢复相关的工具。你将学会熟练使用 Hudi CLI(一款多用途工具),无需编写自定义代码即可执行日常维护、检查表元数据以排障,并完成各类运维操作。我们还会覆盖 Hudi 的 savepointrestore 操作——它们是灾难恢复的关键。

接着,我们将聚焦将 Hudi 集成到数据平台。我们会介绍平台特性,如 post-commit 回调(post-commit callbacks) :当 Hudi 表上的动作完成时,可用于在 Apache KafkaApache Pulsar 等消息系统中触发下游流程。你将学会如何搭建监控并将关键指标导出到 PrometheusAmazon CloudWatch,以保持对系统健康状况的可见性。我们还将处理元数据一致性的挑战,说明如何使用 Hudi 的**目录同步(catalog sync)**服务,让你的表在多个数据目录(如 AWS GlueDataHub)以及数据仓库(如 Google BigQuerySnowflakeAWS Redshift)中保持已注册且可访问。

最后,我们将深入到性能调优,给出行之有效的实践建议与策略,帮助你在吞吐、延迟与成本之间实现优化平衡。

读完本章,你将具备高效运维 Hudi 数据流水线的能力:不仅能从容应对生产挑战,还能完成平台集成,并持续为组织的数据湖仓(data lakehouse) 维持高性能。

轻松运维(Operating with Ease)

在生产环境中,运维效率往往决定一次小波动与一次重大宕机之间的差别。Hudi 提供了一套完整工具,帮助数据工程师与管理员高效管理他们的数据湖仓。

其中的核心是 CLI:它让团队无需编写自定义代码即可执行日常维护、排障与管理任务。借助 CLI,团队可以快速定位问题、应用修复,并在规模化场景下维护 Hudi 表的健康。Hudi CLI 赋能团队对生产事件快速响应,并自动化例行维护,而不必依赖零散脚本或手工检查文件。本节将讲解如何把这些工具纳入你的运维工作流——从被动救火转向主动管理,即便在最苛刻的湖仓生产环境中,也能真正实现“轻松运维”。

认识 CLI(Getting to Know the CLI)

Hudi CLI 是工程师操作 Hudi 表的“瑞士军刀”。通过一个简洁的终端界面,工程师可以快速查看提交历史(commits) 、浏览元数据、执行恢复维护任务,而无需自写代码。下面介绍将成为你运维工具箱一部分的常用 CLI 命令

环境与安装(Understanding the setup)

Hudi CLI 可通过 Hudi 代码仓库中 packaging/hudi-cli-bundle/ 目录下的脚本 hudi-cli-with-bundle.sh 使用。该脚本依赖可从公共 Maven 仓库下载的两个 bundle JAR:

  • hudi-cli-bundle:提供从 CLI 运行的命令
  • hudi-spark-bundle:提供与 Apache Spark 交互的核心 Hudi 功能与依赖

Tip
你可以用 wget 从公共仓库下载这些 JAR:

export REPO_URL=<URL>                           # 1
export HUDI_CLI_BUNDLE_JAR=<CLI bundle jar>     # 2
export HUDI_SPARK_BUNDLE_JAR=<Spark bundle jar> # 3
wget $REPO_URL/$HUDI_CLI_BUNDLE_JAR
wget $REPO_URL/$HUDI_SPARK_BUNDLE_JAR

1 Hudi 发布产物的 URL:https://repository.apache.org/content/repositories/releases
2 路径示例:org/apache/hudi/hudi-cli-bundle_2.13/1.1.0/hudi-cli-bundle_2.13-1.1.0.jar
3 路径示例:org/apache/hudi/hudi-spark3.5-bundle_2.13/1.1.0/hudi-spark3.5-bundle_2.13-1.1.0.jar

Note
上述示例下载的是 Hudi 1.1 的 bundle JAR(适配 Spark 3.5Scala 2.13)。

运行 Hudi CLI 命令会提交并运行 Spark 作业,对目标 Hudi 表执行各类操作(见图 9-1)。因此,你需要在运行 CLI 的环境中可用的 Spark 安装(通常通过 SPARK_HOME 环境变量指定)。

图示:Hudi CLI 的执行流程:通过 Apache Spark 集群将 CLI 命令转化为对 Hudi 表的各类操作。

image.png

图 9-1. Hudi CLI 流程概览

设置 CLI:

# 设置相关环境变量
export SPARK_HOME=</path/to/spark>
export CLI_BUNDLE_JAR=</path/to/hudi-cli-bundle/jar>
export SPARK_BUNDLE_JAR=</path/to/hudi-spark-bundle/jar>

使用 packaging/hudi-cli-bundle/ 目录下的脚本启动 CLI:

# 启动 CLI
./hudi-cli-with-bundle.sh

该脚本会读取你之前设置的环境变量。除此之外,若在脚本同级创建 conf/ 目录并放置 hudi-defaults.conf,CLI 会自动加载其中的 Hudi 配置,便于对 CLI 执行的表级操作进行细粒度调优

Tip
Hudi CLI 的能力也可通过 **SQL 过程(procedures)**以 CALL 命令形式使用。区别在于命令通过 Spark SQL 控制台执行。详情可参阅相应文档。

检查表信息(Checking table info)

CLI 启动后,你可以创建新表连接已有表并检查其属性。

初始化新 Hudi 表可使用 create 命令在指定路径建立表结构,并定义表名与表类型:

hudi->create --path /path/to/new/table \
--tableName new_hudi_table --tableType COPY_ON_WRITE

若操作已有 Hudi 表,需要先连接该表的基路径(base path)

hudi->connect --path /path/to/table/trips

连接后,可读取表属性以校验配置。desc 命令汇总关键属性,如表类型、记录键与模式:

hudi:trips->desc

如需了解数据结构,可获取表的schema,这对校验数据或规划查询很有帮助:

hudi:trips->fetch table schema

Tip
命令提示符初始为 hudi,表示 CLI 已启动且处于空闲。连接到某张表后,会显示为 hudi:trips,突出当前操作的表名。

检查提交(Inspecting commits)

提交历史(commit history) 以时间顺序展示所有写入操作,便于追踪数据演化与定位问题。

快速感知近期活动可使用 commits show,按关键指标(如写入字节数)排序并限量展示,以把握趋势或发现异常:

hudi:trips->commits show --sortBy "Total Bytes Written" \
--desc true --limit 5

更深入地,你可以按分区拆解某次提交,查看受影响的分区及其统计:

hudi:trips->commit showpartitions \
--commit 20220128160245447 \
--sortBy "Total Bytes Written" --desc true

排查数据问题时,可能需要定位到文件级的变更。commit showfiles 列出该次写入涉及的所有 base/log 文件、分区路径及其他元数据:

hudi:trips->commit showfiles \
--commit 20220128160245447 --sortBy "Partition Path"

查看文件切片与统计(Inspecting file slices and statistics)

Hudi 以文件组(file groups)文件切片(file slices)组织数据。show fsview(即 file system view)用于检查该结构;stats 命令可观察文件大小分布与写放大(write amplification)

查看每个文件组的所有文件切片

hudi:trips->show fsview all

仅查看每个文件组的最新切片

hudi:trips->show fsview latest --partitionPath "2022/01/01"

查看某分区的文件大小分布

hudi:trips->stats filesizes \
--partitionPath 2022/01/01 \
--sortBy "95th" --desc true

查看写放大(写入记录数 / 更新记录数):

hudi:trips->stats wa

管理表服务(Managing table services)

直接在 CLI 中运行表服务(table services) (如 compactionclusteringcleaning;暂不支持 indexing)在多种运维场景下都很有用。若你需要在常规 SparkApache Flink 处理作业之外执行维护,Hudi CLI 提供了按需触发这些服务的便捷途径,尤其适合维护窗口:例如无需等待下次定时清理就能释放存储空间。

  • 执行 compaction:当表累积了一系列 deltacommit 后,用于优化读性能:

    hudi:trips->compaction scheduleAndExecute --parallelism 200 --sparkMemory 4G
    
  • 执行 clustering:优化表的存储布局、提升查询性能:

    hudi:trips->clustering scheduleAndExecute --parallelism 200 --sparkMemory 4G
    
  • 执行 cleaning:在大量删除后回收空间:

    hudi:trips->clean run --commitsToClean 10 --retainCommits 24 --sparkMemory 4G
    

通过在数据处理作业之外调度/执行表服务,你可以更灵活地维护与优化湖仓表。

若你希望写入作业保持运行,同时从 CLI 侧运行表服务,这相当于以standalone 部署模式运行表服务。因此,需要配置锁提供者(lock provider)并为写入作业与 CLI 程序提供并发控制相关配置。以下示例使用 Apache Zookeeper 作为锁提供者:

hoodie.write.concurrency.mode=optimistic_concurrency_control
hoodie.write.lock.provider=\
org.apache.hudi.client.transaction.lock.ZookeeperBasedLockProvider
hoodie.write.lock.zookeeper.url=<zk_url>
hoodie.write.lock.zookeeper.port=<zk_port>
hoodie.write.lock.zookeeper.lock_key=<zk_key>
hoodie.write.lock.zookeeper.base_path=<zk_base_path>

你可以将这些配置与其他 Hudi 配置一起,按“环境与安装”中的说明,添加到 hudi-defaults.conf 中。

执行表级操作(Performing Table Operations)

生产数据系统需要具备运维灵活性,以同时应对日常维护与突发问题。Hudi CLI 提供了一组命令,可在多种场景下管理 Hudi 表:从创建可恢复的检查点并回滚问题变更,到修改关键表属性与维护数据质量。本节介绍四类核心场景:创建与恢复 savepoint 以便数据回放、通过(如去重)修复数据以维护完整性、在工作负载演进时切换表类型,以及为兼容性管理执行表版本升级/降级。借助这些工具,管理员可以在保持可靠与高性能的同时,按需演进 Hudi 表以满足变化的需求。

使用 savepoint 与 restore(Using savepoint and restore)

在生产运维中,管理并从数据问题恢复至关重要。无论是写入损坏、上游脏数据,还是应用逻辑错误,都可能需要回退到一个“已知良好”的状态。Hudi 的 savepoint / restore 功能通过为表状态创建可恢复的快照来解决这一挑战。

理解 savepoint(Understanding savepoints)

顾名思义,savepoint 会保存表在某个特定 commit 动作时间的状态,之后如有需要可将表恢复到该 savepoint(见图 9-2)。创建 savepoint 时,Hudi 会确保 cleaning 表服务不会删除属于该 savepoint 的任何文件,从而为未来的恢复保留它们。需要注意:不能为已被清理掉的 commit 创建 savepoint。通常建议在最近的 commit 上创建 savepoint,这些提交不太可能被清理。

从概念上说,创建 savepoint 类似于备份,但有一处重要区别:Hudi 不会复制表数据;它只是在某个 commit 点记录表的状态,以便必要时回退。

图示:使用 Hudi CLI 创建 savepoint 并执行恢复的流程示意,包含对 Hudi 表的提交与回滚步骤。

image.png

图 9-2. Hudi CLI 的 savepoint 与 restore

使用 restore 流程(Using the restore process)

restore 操作可以把表回退到先前创建的 savepoint 对应的 commit。这是一个强大但不可逆的操作,需谨慎执行。启动 restore 后,Hudi 会删除目标 savepoint 之后所有的数据文件与 commit(timeline 文件)。

执行 restore 期间,务必暂停对该表的所有写;否则这些写很可能失败。类似地,读操作也可能失败,因为快照查询会访问在恢复过程中可能被删除的文件。

通过 Hudi CLI 使用 savepoint / restore(Using savepoint and restore via the Hudi CLI)

创建 savepoint(先按前文说明启动 CLI 并连接到表):

connect --path </path/to/your/hudi_table/>
commits show
savepoint create --commit <COMMIT_TIMESTAMP> --sparkMaster local[2]

执行 restore 前,务必停止所有 writer,避免数据冲突或损坏。随后在 Hudi CLI 中执行:

connect --path </path/to/your/hudi_table/>
commits show
savepoints show
savepoint rollback --savepoint <SAVEPOINT_TIMESTAMP> --sparkMaster local[2]

<SAVEPOINT_TIMESTAMP> 替换为需要恢复到的实际时间戳(可通过 savepoints show 查询)。示例中使用本地模式运行 Spark 作业;在实际环境中请按需配置合适的 Spark 选项。

restore 成功后,表将被重置为 savepoint 时刻的精确状态。savepoint 之后创建的文件切片与时间线条目都会从文件系统中移除,表中仅保留创建 savepoint 时存在的记录。

Tip
建议定期创建 savepoint,尤其在重大操作之前。同时也要管理 savepoint:在创建新的 savepoint 时删除较旧的。Hudi CLI 提供 savepoint delete 命令实现删除。

Note
清理进程不会移除处于活动状态的 savepoint 所关联的文件。如果不删除不再需要的 savepoint,将阻止存储回收,长期会增加存储成本

通过去重修复数据(Repairing data with deduplication)

由于重试、上游系统问题或应用逻辑错误,数据流水线偶发产生重复记录。Hudi CLI 提供去重能力,可基于记录键(record keys)识别并移除重复(见图 9-3)。

图示:Hudi CLI 的去重过程:识别并移除某分区中的重复记录,产出新的修复文件并形成新的写提交。 image.png

图 9-3. Hudi CLI 去重

使用去重修复命令可在指定分区内识别重复记录,并生成修复后的文件以替换损坏的分区数据,从而不必重处理整批数据集即可维护数据完整性:

connect --path </path/to/your/hudi_table/>

# 对特定分区执行去重修复
repair deduplicate --duplicatedPartitionPath "2022/01/15"

Tip
去重过程可能资源开销较大(需要全量读取分区并做内连接)。可通过该命令的 --sparkProperties 参数指定 Spark 相关配置,对操作性能进行调优

切换表类型(Changing table types)

Hudi 允许在 Copy-on-Write(COW)Merge-on-Read(MOR) 之间切换,以适配演进中的工作负载(见图 9-4)。当应用需求变化时,可能需要在不同表类型之间切换,以优化不同的读写模式

图示:使用 Hudi CLI 在 COW 与 MOR 间切换的流程;包含执行与调度 compaction 的步骤。

image.png

图 9-4. Hudi CLI 切换表类型

从 COW 切换到 MOR(Changing from COW to MOR)

当应用需要更高效的写入时,可将 COW 表转换为 MOR 表,操作简单

connect --path </path/to/your/hudi_table/>

table change-table-type MOR

此命令会修改表 hoodie.properties 中的 hoodie.table.typeMERGE_ON_READ

从 MOR 切换到 COW(Changing from MOR to COW)

读性能下游引擎兼容性比写效率更重要时,可将 MOR 表转换为 COW 表。此转换需特别注意:MOR 表存在日志文件,需要先进行压缩(compaction)

connect --path </path/to/your/hudi_table/>

table change-table-type COW

默认行为包括:

  • 执行所有待处理的 compaction。
  • 若仍有日志文件残留,则执行全量 compaction

这样可确保日志文件中的数据在转换前被正确合并进 base 文件,避免数据丢失。

Tip
从 MOR 转 COW 的 compaction 可能资源消耗大(可能对全表做全量压缩)。可通过 change-table-type 命令的 --parallelism--sparkMemory 等参数来调优 Spark 作业。

升级与降级表版本(Upgrading and downgrading table versions)

随着 Hudi 的演进,新表格式与新特性可能需要表版本升级。你可以使用 Hudi CLI 在保持向后兼容的前提下安全地升级(见图 9-5)。

理解表版本(Understanding table versions)

Hudi 通过 ./.hoodie/hoodie.properties 中的 hoodie.table.version 维护表版本。不同的 Hudi 发行版本支持不同的表版本;当切换 Hudi 库版本时,表可能需要升级/降级

表 9-1 展示了 Hudi 表版本Hudi 发行版本之间的对应关系。

表 9-1. Hudi 表版本与对应发行版本
Hudi 表版本Hudi 发行版本
91.1
81.0
60.14–0.15
50.12–0.13
40.11
30.10
20.9
10.6–0.8
00.5 及以下

升级表版本(Upgrading a table version)

Hudi CLI 支持手动升级表版本:修改 hoodie.properties 中所需配置,并补充目标版本要求的属性。

connect --path <table_path>
upgrade table --toVersion <target_version>

若不指定 --toVersionupgrade table 会使用与当前库发行版本对应的最新表版本

图示:通过 Hudi CLI 升级与降级表版本的过程示意;突出在 hoodie.properties 中修改/移除版本相关属性。

image.png

图 9-5. Hudi CLI 的升级与降级

降级表版本(Downgrading a table version)

当需要使用更旧的 Hudi 库版本时,需先在较新版本的 Hudi CLI 中降级表版本,再切换库。这将修改 hoodie.properties 中的配置,并移除与目标版本不兼容的属性。

connect --path <table_path>
downgrade table --toVersion <target_version>

例如将表从版本 6 降级到 2:

downgrade table --toVersion 2 --sparkMaster local[2]

Tip
通常,升级表版本会由Hudi 写客户端在不同部署模式(如 Hudi Streamer)中于升级库版本后自动处理。这通常是推荐方式,而非手动执行 CLI 升级,因为表版本升级可能需要写入端配置参与以推导相应表属性。手动降级只能通过 CLI 完成。

平滑升级到 Hudi 1.0(Smooth Upgrade to Hudi 1.0)

Hudi 1.0 是一个里程碑版本,显著增强了平台的特性与架构稳健性。该版本的一个关键变化是表格式更新。对于仍运行在 Hudi 0.x 的用户,平滑迁移对于保证生产流水线不中断至关重要。

为此,Hudi 1.0 提供向后兼容的写机制,支持分阶段升级。迁移步骤包括更新表服务、writer 与 reader 所使用的 Hudi 依赖等。请参考 1.0 发行说明中的官方迁移指南获取逐步说明细节步骤

集成进平台(Integrating into the Platform)

Hudi 在“单体”形态下已具备很强的价值,但当它全面集成到更广泛的数据平台后,真正的威力才会释放。将 Hudi 与互补工具打通,可实现诸如:通过 post-commit callbacks 触发下游处理、为流水线健康状况实施监控、在多个目录/编目(catalogs)之间同步元数据等工作流。这些集成让 Hudi 从“数据管理工具”跃迁为你湖仓架构的核心组件。良好的一体化部署可以确保跨查询引擎的数据一致性、提供统一的数据资产视图,并支持在组织内部无缝的数据流转。本节将介绍关键的集成模式,帮助你在保持运维简洁可靠的同时,最大化 Hudi 价值

触发 Post-Commit 回调(Triggering Post-Commit Callbacks)

当数据流水线更新一张 Hudi 表时,它很少是孤立存在的。设想一个常见场景:数据工程团队将客户交易数据写入 Hudi 表;一旦提交完成,若干下游系统需要被通知:搜索索引要更新以便新交易可被检索;实时看板要刷新以体现最新业务指标(如商户表现、交易量、营收归因);还需触发基于事件驱动的工作流处理高价值交易。若缺少合适的通知机制,团队往往退而求其次,使用定时轮询或复杂编排工具,导致系统之间强耦合

Hudi 的 post-commit callback 功能为写入操作提供实时事件通知,是事件驱动数据架构的基石。这一能力允许流水线在提交成功立即触发下游流程,无需依赖定时任务或轮询。

图 9-6 所示,回调由完成提交的 Hudi writer 触发。通过将回调配置到 HTTP 端点或消息中间件(如 KafkaPulsar),你可以构建松耦合的事件驱动架构,即时响应数据变更,确保下游消费者总能获取到最新数据

图示:Hudi writer 在提交完成后调用回调端点以触发下游处理。

image.png

图 9-6. Hudi writer 触发 post-commit 回调

HTTP 端点(HTTP endpoints)

你可以将提交通知发送到 REST API / Webhook 接收端,以触发无服务器函数工作流编排器自定义微服务。这种方式便于在云原生架构中集成 Hudi,一旦数据可用就第一时间告知其他系统。

下面示例展示如何在 Hudi Spark writer 中配置 HTTP 回调。每次提交完成后,都会通知下游服务,适合在数据入湖完成即开工的工作流。

先初始化 Spark 会话并准备写入 Hudi 的数据:

// Spark Writer Example (Java)
SparkSession spark = SparkSession.builder()
  .appName("Hudi HTTP Callback Example").getOrCreate();

Dataset<Row> dataFrame = spark.read().json("/path/to/input/data");

接着定义表名、record key、分区路径等写入配置:

// Configure Hudi options for the writer
Map<String, String> hudiOptions = new HashMap<>();

// Table configuration
hudiOptions.put("hoodie.table.name", "customer_orders");
hudiOptions.put("hoodie.datasource.write.recordkey.field", "order_id");
hudiOptions.put("hoodie.datasource.write.partitionpath.field", "order_date");
hudiOptions.put("hoodie.datasource.write.operation", "upsert");

开启 HTTP 回调并设置目标 URL、必要的请求头与鉴权信息:

// HTTP Callback configuration
hudiOptions.put("hoodie.write.commit.callback.on", "true");
hudiOptions.put("hoodie.write.commit.callback.class.name",
  "org.apache.hudi.callback.impl.HoodieWriteCommitHttpCallback");
hudiOptions.put("hoodie.write.commit.callback.http.url",
  "https://order-processing.example.com/api/data-ready");
hudiOptions.put("hoodie.write.commit.callback.http.timeout.seconds", "5");
hudiOptions.put("hoodie.write.commit.callback.http.api.key",
  "secret_api_key_123");
hudiOptions.put("hoodie.write.commit.callback.http.custom.headers",
  "X-Source:hudi-lakehouse;X-Table:customer_orders");

最后执行写入;提交完成后将自动触发 HTTP 回调:

// Apply configuration to your Hudi write
dataFrame.write()
  .format("org.apache.hudi")
  .options(hudiOptions)
  .mode("append")
  .save("/path/to/hudi/customer_orders");

Kafka 端点(Kafka endpoints)

你也可以把提交事件推送到 Kafka 主题,形成可被多方下游消费的事件流。这样可实现并行处理解耦:不同应用可各自独立地对新数据作出反应。

初始化 Spark 会话并准备数据:

// Spark Writer Example (Java) with Kafka Callback
SparkSession spark = SparkSession.builder()
  .appName("Hudi Kafka Callback Example").getOrCreate();

Dataset<Row> dataFrame = spark.read().parquet("/path/to/input/data");

设置写入配置:

// Configure Hudi options for the writer
Map<String, String> hudiOptions = new HashMap<>();

// Table configuration
hudiOptions.put("hoodie.table.name", "product_inventory");
hudiOptions.put("hoodie.datasource.write.recordkey.field", "product_id");
hudiOptions.put("hoodie.datasource.write.partitionpath.field", "category");
hudiOptions.put("hoodie.datasource.write.operation", "bulk_insert");

启用 Kafka 回调 并设置相关 Kafka 参数:

// Enable callback with Kafka implementation
hudiOptions.put("hoodie.write.commit.callback.on", "true");
hudiOptions.put("hoodie.write.commit.callback.class.name",
  "org.apache.hudi.callback.impl.HoodieWriteCommitKafkaCallback");

// Kafka specific configuration
hudiOptions.put("hoodie.write.commit.callback.kafka.bootstrap.servers",
  "kafka1:9092,kafka2:9092");
hudiOptions.put("hoodie.write.commit.callback.kafka.topic", 
  "inventory-updates");
// 使用单分区可确保严格顺序
hudiOptions.put("hoodie.write.commit.callback.kafka.partition", "0");
// 确保持久性
hudiOptions.put("hoodie.write.commit.callback.kafka.acks", "all");
// 重试配置
hudiOptions.put("hoodie.write.commit.callback.kafka.retries", "3");

执行写入;提交完成后将自动向 Kafka 发送事件:

// Apply configuration to your Hudi write
dataFrame.write()
  .format("org.apache.hudi")
  .options(hudiOptions)
  .mode(SaveMode.Append)
  .save("/path/to/hudi/product_inventory");

Pulsar 端点(Pulsar endpoints)

类似 Kafka,你也可以通过 post-commit 回调将提交事件发送到 Pulsar,需要在 writer 中配置相应的 Pulsar 参数。

初始化会话与数据:

// Spark Writer Example (Java) with Pulsar Callback
SparkSession spark = SparkSession.builder()
  .appName("Hudi Pulsar Callback Example").getOrCreate();

Dataset<Row> dataFrame = spark.read().json("/path/to/input/data");

设置表与写入参数(含 precombine 字段):

// Configure Hudi options for the writer
Map<String, String> hudiOptions = new HashMap<>();

// Table configuration
hudiOptions.put("hoodie.table.name", "financial_transactions");
hudiOptions.put("hoodie.datasource.write.recordkey.field", "txn_id");
hudiOptions.put("hoodie.datasource.write.partitionpath.field", "txn_date");
hudiOptions.put("hoodie.datasource.write.precombine.field", "txn_ts");
hudiOptions.put("hoodie.datasource.write.operation", "upsert");

启用 Pulsar 回调并指定实现类:

// Enable Pulsar callback
hudiOptions.put("hoodie.write.commit.callback.on", "true");
hudiOptions.put("hoodie.write.commit.callback.class.name",
  "org.apache.hudi.callback.impl.HoodieWriteCommitPulsarCallback");

配置 Pulsar broker、topic 与生产者设置:

// Pulsar specific configuration
hudiOptions.put("hoodie.write.commit.callback.pulsar.broker.service.url",
  "pulsar://pulsar-broker:6650");
hudiOptions.put("hoodie.write.commit.callback.pulsar.topic",
  "persistent://finance/transactions/hudi-commits");
hudiOptions.put("hoodie.write.commit.callback.pulsar.producer.route-mode",
  "RoundRobinPartition");
hudiOptions.put(
    "hoodie.write.commit.callback.pulsar.producer.pending-queue-size", "2000");
hudiOptions.put("hoodie.write.commit.callback.pulsar.operation-timeout", "45s");
hudiOptions.put("hoodie.write.commit.callback.pulsar.connection-timeout", "15s");

执行写入;提交完成后将自动向 Pulsar 发送消息:

// Apply configuration to your Hudi write
dataFrame.write()
  .format("org.apache.hudi")
  .options(hudiOptions)
  .mode(SaveMode.Append)
  .save("/path/to/hudi/financial_transactions");

Tip
虽然 HTTP/Kafka/Pulsar 已内置支持,你也可以扩展接口 org.apache.hudi.callback.HoodieWriteCommitCallback 自定义实现,从而对提交事件进行任意方式的处理与分发。

接入监控系统(Wiring Up Monitoring Systems)

缺少指标监控去运行数据流水线,就像夜间无灯开车:或许能到终点,但沿途的预警都看不见。监控能够实时洞察表健康、性能瓶颈与异常,在问题升级为故障前将其扼杀在萌芽。对 Hudi 部署而言,恰当的监控是主动运维被动排障的分水岭:当提交变慢、存储异常增长或查询退化时,指标就是你的前哨。将 Hudi 与 PrometheusAWS CloudWatchDatadog 等系统集成,可以让数据湖仓的运行状态透明可观测,使流水线得以持续优化与可靠维护(见图 9-7)。

图示:Hudi writer 将指标发送到监控面板,从写入/提交到指标处理的链路示意。

image.png

图 9-7. Hudi writer 发送指标到监控仪表盘

启用 Hudi 指标(Enabling metrics in Hudi)

Hudi 提供灵活的指标框架,可对接多种监控后端。启用指标收集需设置:

hoodie.metrics.on=true
hoodie.metrics.reporter.type=<REPORTER_TYPE>

其中 <REPORTER_TYPE> 可为:JMXGRAPHITEDATADOGPROMETHEUS_PUSHGATEWAYCLOUDWATCH,或自定义实现。

可用指标(Available metrics)

Hudi 暴露的指标覆盖多类操作:

  • 提交性能:commit、rollback 及其他时间线操作的时长
  • 数据管理:文件创建/更新/删除的统计
  • 记录处理:insert/update/delete 的记录数
  • 资源利用:扫描、合并等操作耗时

这些指标有助于追踪表健康与性能,并在问题变得严重之前及时发现。

集成示例(Integration examples)

Prometheus + Grafana
Prometheus 通过 Pushgateway 接入 Hudi 指标。作业端配置:

hoodie.metrics.on=true
hoodie.metrics.reporter.type=PROMETHEUS_PUSHGATEWAY
hoodie.metrics.pushgateway.host=prometheus-pushgateway
hoodie.metrics.pushgateway.port=9091
# 可选:设置作业名称
hoodie.metrics.pushgateway.job.name=hudi-metrics
# 可选:作业结束后是否保留指标
hoodie.metrics.pushgateway.delete.on.shutdown=false

prometheus.yml 中添加抓取配置:

scrape_configs:
  - job_name: 'pushgateway'
    honor_labels: true
    static_configs:
      - targets: ['prometheus-pushgateway:9091']

随后可用 Grafana 构建实时看板,监控如提交时长趋势、记录处理速率、文件 I/O 活动等。

Datadog
原生集成将指标直接发送到 Datadog:

hoodie.metrics.on=true
hoodie.metrics.reporter.type=DATADOG
# 根据你的 Datadog 实例选择 US 或 EU
hoodie.metrics.datadog.api.site=US
hoodie.metrics.datadog.api.key=<YOUR_DATADOG_API_KEY>
hoodie.metrics.datadog.metric.prefix=hudi

配置完成后即可在 Datadog 创建看板与告警,并与其他基础设施监控联动。

AWS CloudWatch
将 Hudi 与 AWS 生态一并监控:

hoodie.metrics.on=true
hoodie.metrics.reporter.type=CLOUDWATCH
# 可选:若未使用实例角色,这里配置凭据
hoodie.aws.access.key=<YOUR_ACCESS_KEY>
hoodie.aws.secret.key=<YOUR_SECRET_KEY>

自定义指标看板(Building custom metrics dashboards)

构建 Hudi 监控看板时,建议覆盖以下重点:

  • 写入性能
    <table_name>.commit.duration(提交耗时)
    <table_name>.totalRecordsWritten(处理记录数)
    <table_name>.totalFilesInsert / <table_name>.totalFilesUpdate(文件操作)
  • 数据新鲜度
    <table_name>.commitFreshnessInMs(事件时间到提交时间的时延)
    <table_name>.commitLatencyInMs(端到端处理延迟)
  • 存储效率
    <table_name>.totalBytesWritten(写入数据量)
    按分区的文件数量compaction 指标

监控最佳实践(Best practices for monitoring)

  • 依据工作负载模式设置有意义的告警阈值
  • 更关注趋势而非某个绝对值
  • 将 Hudi 指标与基础设施指标(CPU/内存/磁盘 I/O)关联观察
  • 面向不同角色(运维/开发/数据工程师)制作专用看板
  • 引入业务语境,把技术指标与数据 SLA关联起来

Tip
关于指标监控,请参阅 Hudi 文档中对支持的指标系统的说明。若你的首选监控系统不在官方列表中,可扩展 org.apache.hudi.metrics.custom.CustomizableMetricsReporter 实现自定义集成

通过将 Hudi 与监控体系、回调机制与目录同步服务良好集成,你就能获得对数据湖仓健康与性能的清晰可见性,实现主动管理与持续优化数据流水线的目标。

与目录服务同步(Syncing with Catalogs)

在数据管理世界里,没有目录(catalog)的湖仓就像没有索引的图书馆——宝贵且庞大,但难以高效检索。数据目录在原始数据存储与可用性之间搭起桥梁,提供元数据层,让用户无需了解数据的物理位置或结构即可发现、理解与查询数据资产。传统数仓默认内置目录,而数据湖仓需要显式与目录服务集成。Hudi 能将表元数据与常见目录(如 Apache Hive MetastoreAWS GlueGoogle BigQuery)同步,确保表在整个分析生态中可见且可访问。借此,可用 Presto、Trino、Spark SQL、Athena 等熟悉的 SQL 引擎无缝查询,在保留数仓治理与结构化优势的同时,兼具数据湖的灵活与成本效益。通过自动化元数据管理,组织可以维护单一事实来源(single source of truth) ,让用户独立访问与分析数据,而无需频繁依赖工程团队。

目录同步(Catalog synchronization)

目录同步是 Hudi 保持外部元数据仓库(数据目录)与湖仓中表的最新状态一致的过程。该同步在物理数据存储用于发现/查询所需的元数据之间建立桥梁。

一个 Hudi 表在目录中的条目通常包含:

  • **表模式(schema)**信息(列名、数据类型)
  • 分区结构信息与可用分区列表
  • 表属性与统计
  • 表位置(存储路径)
  • 表格式与版本元数据

在 Hudi 中,目录同步作为独立流程写入提交成功后执行(见图 9-8)。其工作流如下:

  1. 对 Hudi 表执行一次写操作(insert、upsert、delete 等)。

  2. 操作成功提交后,Hudi 检查该次提交的提交元数据(commit metadata)

  3. 若开启同步,Hudi 触发已配置的同步工具(sync tool)

  4. 同步工具从 Hudi 表中抽取最新元数据,包括:

    • 当前 schema
    • 分区信息
    • 表属性
  5. 同步工具连接目标数据目录,更新对应的表条目。

图示:Hudi writer 在提交后运行 metasync,将表同步到多个目录(Hive、AWS Glue、BigQuery、DataHub)。

image.png

图 9-8. Hudi writer 运行与目录的 metasync 流程

该流程确保目录中的元数据与 Hudi 表上的实际数据保持一致。

元数据版本化(Metadata versioning)

每当一次提交包含有意义的元数据变更时,目录可能会创建该表条目的新版本(取决于目录的更新策略)。这类变更包括:

  • 模式演进(新增/删除/修改列)
  • 新增或删除分区
  • 表属性或统计发生变化

某些目录(如 AWS Glue)会维护版本化的表条目。为避免产生过多版本,Hudi 提供条件同步选项:仅在实际元数据变化(schema 或分区变化)时才触发同步,而不是每次提交都更新。

为提升效率(尤其在跟踪版本的目录如 Glue 中),可启用条件同步:

hoodie.datasource.meta_sync.condition.sync=true

开启后,Hudi 仅在发生以下实际元数据变更时执行目录同步,例如:

  • Schema 演进(新增/修改列)
  • 新增分区
  • 删除分区
  • 其他显著元数据变化

这样可避免不必要的目录更新,减少目录版本膨胀。

支持的目录集成(Supported catalog integrations)

Hudi 支持与多种常用目录服务同步:

  • Hive Metastore
    最广泛使用的湖仓目录,存储表与分区元数据。Hudi 集成后,表可被 Hive、Presto、Trino 等可连接 Hive Metastore 的 SQL 引擎查询。配置细节见 Syncing with Hive Metastore 文档。
  • AWS Glue Data Catalog
    AWS 的托管元数据仓库,兼容 Hive Metastore 接口。Hudi 提供专用同步工具可直接更新 Glue,使表可被 AWS Athena、Amazon EMR 等服务查询。Glue 的专属配置见 Syncing with AWS Glue 文档。
  • Google BigQuery
    Hudi 表可作为 外部表同步到 BigQuery,无需迁移底层数据即可使用 BigQuery 强大的 SQL 引擎查询。配置细节见 BigQuery integration 文档。
  • DataHub
    开源的数据发现与治理平台。Hudi 可将表元数据同步到 DataHub,增强可发现性并提供丰富上下文。配置见 Syncing with DataHub 文档。
  • Apache XTable
    提供湖仓表格式之间的双向互操作。例如,可将 Hudi 表的元数据翻译Apache Iceberg 的格式,从而在保持原 Hudi 表不变的情况下,以 Iceberg 表的方式读取同一张表;并可在 Hudi/Iceberg/Delta Lake 之间多向转换。这样还可接入更多目录(如 Snowflake Polaris),典型做法是先把 Hudi 转为 Iceberg。配置见 XTable sync 文档。

示例:Hive Metastore 的 HMS 模式同步(Example: Hive Metastore sync with HMS mode)

最常见的目录集成之一是 Hive Metastore,它为多种 SQL 引擎提供集中元数据仓库。Hudi 支持多种 Hive 同步模式,这里使用 HMS 模式(Hive Metastore Service),通过 Thrift 直连 Metastore 服务。

以下 Spark DataSource API(Scala) 示例展示了如何在写入 Hudi 表时启用 Hive 同步。除常规 Hudi 写入选项外,还包含控制同步行为的 Hive 专属配置:

// Write to Hudi with Hive sync enabled
dataFrame.write.format("hudi")
  // Essential Hudi configs
  .option("hoodie.datasource.write.recordkey.field", "recordKey")
  .option("hoodie.datasource.write.partitionpath.field", "partitionPath")
  
  // Hive sync configurations
  .option("hoodie.datasource.meta.sync.enable", "true")
  .option("hoodie.datasource.hive_sync.mode", "hms")
  .option("hoodie.datasource.hive_sync.metastore.uris", 
    "thrift://hive-metastore:9083")
  .option("hoodie.datasource.hive_sync.database", "my_database")
  .option("hoodie.datasource.hive_sync.table", "my_table")
  .option("hoodie.datasource.hive_sync.partition_fields", "partition_field")
  .save("/path/to/hudi/table")

关键选项说明:

  • hoodie.datasource.meta.sync.enable
    必须设为 true 才会在每次写入后激活元数据同步。
  • hoodie.datasource.hive_sync.mode
    设为 hms 表示直连 Hive Metastore 服务(不依赖 HiveServer2)。
  • hoodie.datasource.hive_sync.metastore.uris
    指向 Hive Metastore 的 Thrift URI,用于让同步客户端直连 Metastore。
  • hoodie.datasource.hive_sync.database / hoodie.datasource.hive_sync.table
    决定 Hudi 表在 Hive 中出现的位置,确保可被 Hive、Presto、Trino 等引擎查询。
  • hoodie.datasource.hive_sync.partition_fields
    声明分区键,确保同步过程能正确注册分区表

以上最小配置常足以起步;但在生产中,可能需要更多选项(如 schema 演进、大小写敏感等)。完整配置与更深入示例请参见 Hive Sync 文档。

同步到多个目录(Using multiple catalog syncs)

在许多企业环境中,平台是异构的:不同团队/应用依赖不同的目录查询同一数据集。比如,A 团队在本地用 Hive/Trino + Hive Metastore,B 团队在云上用 AWS Glue + Athena。此时需要保证跨目录的元数据一致,让所有人无论使用何种工具,都能看到表的同一版本

Hudi 支持同时将同一张表同步到多个目录,方法是在配置中指定多个同步工具类。这样每次写入完成后,所有关联的目录都会自动更新

示例:将 Hudi 表在每次提交后同时同步到 Hive Metastore 与 AWS Glue:

hoodie.meta.sync.client.tool.class=\
org.apache.hudi.hive.HiveSyncTool,\
org.apache.hudi.aws.sync.AwsGlueCatalogSyncTool

采用该设置后,Hudi 将先同步 Hive Metastore,再同步 Glue,从而在两个系统间维持一致的表定义

性能调优(Performance Tuning)

即便是精心设计的湖仓,也可能遇到性能瓶颈。在 Hudi 中,性能调优是让数据平台从“能用”进化到“高性能”的关键。通过优化核心配置,你可以显著缩短处理时间,确保可预测的 SLA,并更高效地处理更大的数据量。

实现最佳性能需要细致调优,因为每个生产环境都不相同。理想配置取决于你的具体用例、数据特征(体量、速度、更新频率),以及在写入速度 vs. 查询时延等相互冲突目标之间做出的权衡。

本节汇总了全书最具影响力的调优策略。我们将回顾**写入、读取与表服务(table services)**的基础原则与具体配置,并提供相关章节的交叉引用以便深入阅读。

存储布局调优(Storage Layout Tuning)

在进入具体操作前,先理解对 Hudi 表存储布局进行调优的基础选项——这些往往对整体性能影响最广。

表类型选择(Table type selection)

COW(Copy-On-Write)MOR(Merge-On-Read) 之间做选择,是你要做的最基础的性能决策(详见第 2 章):

  • COW:读性能最佳。更新需要重写整个 base file,写放大会较高。适合读为主、更新不频繁的负载。
  • MOR:写性能最佳。更新以轻量 log files 追加的方式进行,写放小、写入更快。但快照查询需要在线合并 base files 与 log files,查询时延更高。适合写为主/流式、更新频繁的负载。

创建表时可通过如下配置指定格式:

hoodie.table.type=COPY_ON_WRITE // 或 MERGE_ON_READ

最佳实践是依据主流工作负载模式选择表类型。若业务需求或负载模式变化,可使用 Hudi CLI 进行表类型切换(见“Changing table types”)。

文件大小(File sizing)

当数据存储在最佳大小(一般 128 MB–1 GB)的文件中时,Hudi 表现最佳。过多小文件会严重拖累读写性能(频繁打开/关闭文件与读元数据的开销)。

第 3 章介绍了 Hudi 的文件尺寸控制机制。关键配置包括:

  • hoodie.parquet.max.file.size
    基础文件(base file)大小上限
  • hoodie.parquet.small.file.limit
    小文件阈值;低于该阈值时,Hudi 会优先将新增写入填充到现有小文件,而不是新建文件

除在线控制外,还可借助Clustering 表服务(见第 6 章)进行更稳健的异步文件尺寸优化。

分区(Partitioning)

物理分区是传统且有效的加速方式。按列值(如日期、区域)切分,能让查询引擎进行分区裁剪,跳过不相关数据,大幅减少扫描。

第 2 章指出,应选择与最常见 WHERE 条件匹配的分区列,同时避免产生过多稀疏分区(又回到小文件问题)。当分区不合适或不匹配访问模式,可为 Hudi 表构建表达式索引(见第 5 章),以灵活优化多样化查询谓词。

写入性能调优(Write Performance Tuning)

优化写入对于满足摄取 SLA、有效处理更新至关重要。重点关注:并行度、索引、Bulk Insert 优化

并行度调优(Tuning parallelism)

并行度是平衡吞吐、资源利用与输出文件大小的关键杠杆。并行度太低会限速,太高会压垮集群或产出过多小文件。
Spark 中,Hudi 写入包含 shuffle,因此shuffle 并行度尤为关键。重要配置:

  • hoodie.insert.shuffle.parallelism
  • hoodie.upsert.shuffle.parallelism
  • hoodie.bulkinsert.shuffle.parallelism

一个经验起点:按数据量估算并行度,例如 input_data_size / 500MB。理解 Spark 任务、executor cores 与并行度设置之间的互动,有助于高效执行。

Flink 中,调优涉及 task slotstaskmanager.numberOfTaskSlots)与不同阶段的并行度设置,例如:

  • write.tasks(主写入)
  • compaction.tasks(后台压缩)
  • read.tasks(索引引导,如使用)

并行度得当意味着更快的作业、更好的资源利用与合适的输出文件大小。

索引调优(Tuning indexes)

更新/删除选择合适的 Hudi Writer Index 至关重要。索引把record key → 物理文件位置,避免代价高昂的全表扫描。第 5 章详述了多种索引类型及特性:

  • Record Index
    通用高性能,适配各表规模与负载模式
  • Bucket Index
    对更新密集写入最快,适配各表规模
  • Simple Index
    适合随机更新/删除场景,不适合超大规模表
  • Bloom Index
    适合倾斜的更新/删除模式,不适合随机更新/删除

Bulk Insert 优化(Bulk insert optimizations)

用于首批装载仅追加的管道时,bulk_insert 性能最佳。第 3 章解释了其绕过索引与自动文件定尺的路径,写速接近纯 Parquet。

配置示例:

hoodie.datasource.write.operation=bulk_insert
hoodie.bulkinsert.sort.mode=PARTITION_PATH_REPARTITION

进一步提速可使用轻量压缩编解码器(如 Snappy):

hoodie.parquet.compression.codec=snappy

读取性能调优(Read Performance Tuning)

对读为主的负载,目标是最小化查询时延。Hudi 提供多种强力加速特性。

借助元数据表的数据跳读(Data skipping with the metadata table)

数据跳读(data skipping)是最有效的加速手段之一。第 5 章介绍了 Hudi Metadata Table 可存储列级统计(min/max、空值计数),在文件与分区粒度聚合(列统计与分区统计索引)。当执行带相关谓词的查询时,Hudi 使用这些统计跳过不可能命中的分区与文件。

读取侧关键配置:

hoodie.enable.data.skipping=true
hoodie.metadata.enable=true

Hudi 1.0 起,默认启用 metadata table,且列统计/分区统计索引也默认开启。对大表的选择性查询,这可带来数量级的性能提升。

MOR 表的查询类型(Query types for MOR tables)

MOR 表,你可在数据新鲜度性能之间选择两类查询(见第 4 章):

  • Snapshot query(默认)
    提供最新视图:读时在线合并 base files 与 log files。保证新鲜度,但增加读时开销。
  • Read-optimized query
    读取仅列式 base files忽略 log files,不做在线合并;记录可能滞后,但速度更快。

通过如下配置选择类型:

hoodie.datasource.query.type=snapshot // 或 read_optimized

注意
read_optimized 不适用于 COW 表,因为 COW 没有需要与 base files 合并的 log files;COW 的 snapshot 查询已是最优路径。

表服务调优(Table Services Tuning)

正如第 6 章所述,Hudi 通过运行**表服务(table services)**来维护表的长期健康与性能。

压缩(Compaction)

CompactionMOR(Merge-On-Read) 表的关键服务。它会将行式的日志文件(log files)与列式的基础文件(base files)合并,防止读取性能随时间退化。最佳实践是异步运行压缩,将这项资源密集型工作与主摄取(ingestion)流水线解耦,以降低写入时延。你还应根据工作负载调优触发策略规划策略,例如:

  • 触发策略:hoodie.compaction.trigger.strategy=NUM_COMMITS
  • 规划策略:hoodie.compaction.strategy

聚类(Clustering)

Clustering 通过重写数据来优化物理布局,典型做法是基于高频过滤列进行排序并合并小文件。与压缩类似,建议异步运行以避免影响写性能。同时使用规划策略精准选择需要优化的文件组(如含有大量小文件的那些),例如:

  • 规划策略类:hoodie.clustering.plan.strategy.class

清理(Cleaning)

Cleaning 服务通过移除因更新和表服务产生的旧的、未使用的文件版本来回收存储空间。应配置与组织的数据留存(retention)时光回溯(time travel)需求一致的清理策略hoodie.cleaner.policy)。常见选择是 KEEP_LATEST_COMMITS,既能清理旧版本,又能确保长跑查询在清理期间不至于失败。

小结(Summary)

本章我们回顾了在生产环境中运行 Hudi 所需的核心组件:如何用 CLI 简化管理任务、用 savepoint/restore 保障数据安全,以及借助提交后回调(post-commit callbacks)目录同步(catalog sync)将 Hudi 平滑融入更广泛的数据平台。同时,我们也复盘了前文的性能调优要点,帮助你从部署中榨取最大收益。

这些基础实践对任何生产级 Hudi 部署都至关重要。而在真实世界里,成功往往来自把这些“积木”组合成贴合业务场景的解决方案。第 10 章将更进一步,以端到端案例展示 Hudi 在不同行业中的实际应用;你将看到组织如何把 Hudi 编织进其数据架构来解决独特难题,并获得可直接借鉴到你生产环境的实战蓝图