高效地读取与查询数据是任何数据湖仓的最终目的,它直接影响分析与机器学习的速度与灵活性。因此,深入理解 Hudi 的读侧能力——以及它如何与各类查询引擎集成——对构建高性能且可靠的数据平台至关重要。基于第 2 章中的表布局基础概念和第 3 章探讨的写入操作,本章将技术深潜与实践示例结合,成为你从 Apache Hudi 读取数据的权威指南。
本章分为三部分,全面讲解 Hudi 的读取能力。**“与查询引擎的集成”解释主流查询引擎如何与 Hudi 交互:我们将介绍 Hudi 的读取流程、为无缝高效查询所构建的集成机制,并梳理数据目录在其中的角色。为结合实战, “探索查询类型”将通过示例展示 Hudi 多样的读取能力,讲解不同查询类型的行为与相关配置,帮助你解决广泛的分析挑战。Hudi 读操作的灵活与强大还得益于若干面向高级湖仓模式的关键特性, “亮点特性”**将进一步展开这些设计。
读完本章,你将能够有效地从 Hudi 读取数据:既清楚整体读流程,也能针对不同场景选择合适的查询类型,并会运用高级特性为下游应用构建高效可靠的数据平台。
与查询引擎的集成(Integrating with Query Engines)
在湖仓架构中,查询引擎是负责处理用户查询的计算核心。可以把它看作你的数据“总图书管理员”:它解释你的请求(通常是 SQL),并从存储层(如 Amazon S3、GCS 或 HDFS)中高效检索所需信息。Apache Spark、Presto、Trino 等引擎提供读取、处理与返回表数据的 API 与分布式执行能力。它们实际上实现了计算层与存储层的解耦,让你能按需选择最合适的分析工具。
查询生命周期(Query Lifecycle)
理解 Hudi 如何与查询引擎集成,首先要了解查询的典型生命周期。尽管 Spark 与 Trino 的实现细节不同,但它们都会遵循类似的多阶段流程,将用户的 SQL 语句转化为结果集,并尽可能在分布式集群上高效执行。关键阶段如下(见图 4-1):
图示:分布式查询引擎中的查询生命周期,突出解析、规划、执行与结果汇总等关键阶段,并展示与数据目录的交互。
图 4-1. 分布式查询引擎中的查询生命周期
- 解析(Parsing)
用户提交查询(通常是 SQL)。引擎先进行语法与结构解析,识别 SELECT 列、FROM 表、WHERE 条件等组成部分,并组织成抽象语法树(AST) 。 - 规划(Planning)
通常分为逻辑规划与物理规划两步。
- 逻辑规划:引擎使用解析出的库表名,查询数据目录(如 Hive Metastore 或 AWS Glue),获取表的存储位置、数据格式(如 Parquet)、模式与分区信息,并据此构建逻辑计划(获取数据的抽象“食谱”)。
- 物理规划:优化器在多种执行策略间做权衡(如不同 join 算法、过滤下推机会),基于成本模型选择最高效的物理计划。输出通常是一个有向无环图(DAG) ,详述将要执行的操作序列。
- 执行(Execution)
引擎把物理计划翻译为具体的并行任务,分发到集群的工作节点。各节点并行读取底层存储中的必要数据文件并执行计算。 - 结果汇总(Results Collection)
各工作节点的部分结果被协调者/Driver 汇集、聚合与最终处理后,形成完整的结果集返回给用户,至此生命周期结束。
数据目录(Data Catalog)
在湖仓语境下,数据目录是集中式的元数据管理服务,维护所有数据资产的清单,是连接查询引擎与底层数据的关键纽带。目录记录表、模式、分区与文件位置等信息,为查询生命周期中的规划阶段提供必要元数据,使数据可被发现与查询。
现代湖仓往往拥有联邦式目录体系:
- 一类是与平台执行引擎深度集成的技术型目录,如 Hive Metastore、AWS Glue、Databricks Unity Catalog、Apache Polaris Catalog。
- 另一类是专门的治理与元数据管理目录,如 DataHub、Alation、Atlan、Collibra,提供模式演进追踪、数据血缘、数据质量画像等能力。二者协同实现健全治理,确保在多样化生态中数据的可靠与一致。
Hudi 支持与多种数据目录无缝集成(第 9 章将详述)。关键点在于:把 Hudi 表注册进目录,即可让查询引擎连接目录、获取所需元数据,并高效完成查询规划。
尽管没有目录也能查询 Hudi——你可以用 Spark 或 Flink 的编程式 API 直接按存储路径读表——但现实里更常见做法是建立一个中心目录:这带来数据发现与治理的好处,更重要的是能够充分利用 SQL 这一数据分析的“通用语言”。因此,本章余下示例将以 SQL 展示 Hudi 的读取能力。
Hudi 集成(Hudi Integration)
在理解了标准查询生命周期后,我们来看 Hudi 如何融入这一过程。Hudi 与查询引擎的集成并非要替代这一生命周期,而是增强它:通过专用连接器与逻辑,Hudi 在规划与执行阶段注入自身“智能”,帮助引擎高效理解其独特的文件布局与事务保证,从而让对 Hudi 表的查询既快又准。
如图 4-2 所示,Hudi 与查询引擎的深度集成主要发生在规划与执行阶段。这依赖于面向不同引擎的制品与连接器:例如 Spark 的 hudi-spark-bundle,以及 Presto/Trino 的专用连接器。该集成扮演了桥梁:在查询引擎的通用处理逻辑与 Hudi 的特定存储格式之间进行对接。
图示:Hudi 在查询引擎读流程中的集成位置,突出其在规划与执行阶段与数据目录、Hudi 表的交互。
图 4-2. 查询引擎读流程中的 Hudi 集成
获取已裁剪的文件切片(Get pruned file slices)
在规划阶段,Hudi 的集成层负责一个至关重要的优化:识别满足查询所需的最小文件切片集合。这是通过**索引读取器(index reader)与时间线读取器(timeline reader)**的协同完成的(见图 4-3)。
图示:在规划阶段,Hudi 集成如何裁剪文件切片,突出索引读取器、文件切片与 Hudi MOR 表之间的交互。
图 4-3. 规划阶段获取已裁剪的文件切片
索引读取器利用查询中的过滤谓词执行数据跳过(data skipping) 。它会查询存放于元数据表(metadata table)中的各类索引(该表是 Hudi 表内的索引子系统),以定位可能包含目标记录的文件切片。元数据表提供强大的索引能力,可基于多种过滤类型(包括范围比较与等值匹配)高效地裁剪分区与单个文件——第 5 章将对此做深入探讨。该查找过程会综合利用所有可用过滤条件,从而大幅减少候选文件切片的数量。
时间线读取器随后加载时间线(timeline)上的各个瞬时(instant),对候选列表进一步收敛。例如在时光回溯查询(time travel)中,时间线读取器会用目标时间戳,找到早于该时间点、距离最近的文件切片。同时,它还会剔除那些已被 replacecommit 动作(如第 3 章介绍的 INSERT OVERWRITE)重写的文件切片(前提是该 replacecommit 落在本次查询的目标时间戳范围内)。总体而言,这些步骤会产出一份已裁剪的文件切片列表,进入下一阶段处理。
高效读取文件切片(Read file slices efficiently)
在随后进行的执行阶段,查询引擎会将读取这些目标文件切片的任务分发到各个工作节点。当文件组读取器(file group reader)从文件中读取记录时,引擎会应用行级过滤,并做列投影以仅保留被请求的字段(见图 4-4)。
图示:在执行阶段读取 Hudi MOR 表的一个文件切片,展示文件组读取器如何结合查询过滤与列投影,将日志文件与基准文件进行合并。
图 4-4. 执行阶段读取文件切片
当一个文件切片只包含基准文件(base file)时,读取相对直接——这在 Copy-on-Write(COW) 表中始终如此。
而在 Merge-on-Read(MOR) 表中,一个文件切片还可能包含若干日志文件(log files) ,其中保存了新增或更新的记录。此时,文件组读取器会依据表上配置的合并模式(merge mode,见第 3 章) ,即时(on the fly)将日志文件中的记录与基准文件中的记录进行合并。该高度优化的合并过程确保查询引擎能够从目标文件切片中高效且正确地获取每条记录的最新版本并返回。
总之,Hudi 与查询引擎的集成是对标准查询生命周期的增强:在规划阶段利用索引能力进行高效裁剪,在执行阶段依据事务边界进行时间线过滤与按需合并,从而保证查询既高效又一致。正是这种无缝集成,使得用户能够轻松并高性能地查询 Hudi 表。
在理解了 Hudi 与查询引擎如何协作之后,接下来我们将把焦点转向 Hudi 支持的不同查询类型,它们分别面向特定的分析需求而设计。
探索查询类型(Exploring Query Types)
为让本章概念更具象,我们以一家虚构的网约车公司 LetsMotor 为贯穿示例。公司最初为城市居民提供中短途打车服务,后来业务扩展,数据需求也随之增长。本节将围绕其一张核心 Hudi 表 trips 展开。该表包含用户行程的详细记录:唯一行程 ID、行程开始时间戳、车费、乘客、行程记录更新时间戳、司机姓名,以及发车城市等属性。
先创建表:
CREATE TABLE trips (
uuid STRING,
start_ts BIGINT,
rider STRING,
driver STRING,
fare DOUBLE,
update_ts BIGINT,
city STRING
) USING HUDI
TBLPROPERTIES(
type = 'mor',
primaryKey = 'uuid',
preCombineField = 'update_ts'
)
PARTITIONED BY (city);
插入一些示例数据:
INSERT INTO trips VALUES
('ride-001', 1672531200, 'rider-A', 'driver-1', 15.75, 1672531260, 'SF'),
('ride-002', 1672534800, 'rider-B', 'driver-2', 22.50, 1672534860, 'NYC'),
('ride-003', 1672538400, 'rider-C', 'driver-3', 8.25, 1672538480, 'SF'),
('ride-004', 1672542000, 'rider-D', 'driver-4', 31.80, 1672542120, 'LA'),
('ride-005', 1672545600, 'rider-E', 'driver-5', 12.40, 1672545720, 'SEA');
鉴于 LetsMotor 平台有大量写入与更新,MOR 表型更合适。下面要讲的查询类型同时适用于 MOR 与 COW,其行为在两者之间是一致的。
快照查询(Snapshot Query)
快照查询是读取 Hudi 表的默认类型。顾名思义,它返回最新一次提交时的表数据快照,用来读取最新已提交的数据。标准 SELECT 即可:
SELECT rider, driver, fare, city
FROM trips
WHERE fare > 20;
正如第 2 章谈到的表类型权衡:当存在日志文件时,MOR 上的快照查询通常比 COW 更慢,因为引擎需要即时合并基准文件与日志文件以返回最新数据。
如果你希望查询更快并愿意牺牲部分新鲜度,仍可使用 MOR 的快速写入优势,同时通过只读基准文件来优化读性能:在查询前设置
SET hoodie.datasource.query.type=read_optimized
该(仅对 MOR 有效的)设置会只读基准文件,跳过日志中的最新更新,速度可接近 COW。但请注意,结果不包含日志里的最新记录。
提示
若要使用 read-optimized 查询,也应考虑运行压缩(compaction)表服务(见第 6 章)。它会把日志合并入基准文件,生成新的基准文件,使 read-optimized 查询也能拿到更“新”的数据。你可以定向对最常被查询的分区执行压缩。
时光回溯查询(Time Travel Query)
第 2 章我们已简述过,这里更近一步。设想需要因促销而调整一条行程记录的车费,可这样更新:
UPDATE trips
SET fare = fare - 5,
update_ts = unix_timestamp()
WHERE uuid = 'ride-002';
财务团队可能需要在促销前的车费上做分析。时光回溯查询非常合适。通过 TIMESTAMP AS OF,可取回某一时间点的记录值:
SELECT rider, driver, fare, city
FROM trips TIMESTAMP AS OF '20230101091628123'
WHERE uuid = 'ride-002';
这里的时间戳使用 Hudi 时间线格式
yyyyMMddHHmmssSSS。
查询时,Hudi 的时间线读取器会把给定时间戳与表时间线比对,找到精确匹配或最近且更早的提交。随后用该提交时间过滤掉之后创建的文件切片,从而重建该时间点的历史快照。若时间戳早于时间线第一笔提交,将无结果;若晚于最新提交,则等价于快照查询。
提示
可以把快照查询看作时光回溯查询的特例:查询时间永远指向时间线最新提交。
为了易用,Hudi 也支持常见时间格式:
SELECT * FROM trips
TIMESTAMP AS OF '2023-01-01 09:16:28.123'
WHERE uuid = 'ride-002';
SELECT * FROM trips
TIMESTAMP AS OF '2023-01-01'
WHERE uuid = 'ride-002';
时光回溯对合规审计、内部数据调试/排错等下游分析都很有用。
增量查询:latest_state 模式(Incremental Query: The Latest-State Mode)
Hudi 的一个常见用法是支撑多阶段数据管道:数据从原始态逐步被转换、规范化。LetsMotor 的平台团队需要对 trips 在此类管道中的流转建模。
在采用 Hudi 之前,每一阶段的 ETL 都要重算全量上游数据,这是因为传统数据湖无法简单定位“自上次处理以来新增或变更的记录”。
Hudi 的增量查询从根本上解决了这个问题:只提供两个时间点之间发生变化的记录视图,从而让管道只处理增量,显著提升下游速度。
默认的 latest_state 模式就是主要场景:给定开始/结束提交时间,这种查询会返回时间窗内被插入或更新的每个键的完整且最新记录。它回答的问题是:“自上次检查后,所有变更记录的最新状态是什么?”
记录级变更跟踪
Hudi 如何高效筛出仅变更的记录?步骤分两相:
- 类似时光回溯,增量查询先按时间选择文件切片,但这里用的是起止时间范围,选择在该窗内创建的文件切片。
- 这还不够精确:一个基准文件可能包含多个提交关联的记录(例如重写带入旧数据)。因此在执行阶段再做记录级二次过滤。第 3 章介绍过
_hoodie_record_key、_hoodie_partition_path,而增量查询的关键是第三个元字段_hoodie_commit_time:每条记录都带有最后修改它的提交时间戳。扫描时,引擎据此只保留提交时间落在目标窗口内的记录。对 MOR 表,日志文件也在其元数据块中编码了该提交时间,起到相同过滤作用。
参数说明
LetsMotor 平台团队可用如下表值函数模板来确定下游需要应用的更新:
SELECT * FROM
hudi_table_changes(table or path, query_type, start_time [, end_time]);
四个参数见下表:
| 参数名 | 描述 | 备注 |
|---|---|---|
| table or path | 表标识或表的基路径 | 表标识通常为 <db>.<table>;路径为存储系统绝对路径 |
| query_type | 增量查询模式 | 有效值:latest_state 或 cdc(cdc 下一节讨论) |
| start_time | 增量窗口的起点(不含) | 可为 earliest 或时间线格式 yyyyMMddHHmmssSSS |
| end_time | 增量窗口的终点(含),可选 | 省略则取到时间线最新提交(含) |
若要获取自表历史开始至今所有变更记录的最新状态,可将起点设为 'earliest' 并省略终点。该查询等价于对当前状态做一次快照查询:
SELECT * FROM hudi_table_changes('trips', 'latest_state', 'earliest');
假设我们发现 2024-05-19 之后的一批更新存在问题,想看到该日期之前的表状态。下面的查询检索自历史开始到 2024-05-19 00:00:00(含)为止所有变更记录的最新状态——本质上等于把表“回拨”到这个时间点:
SELECT * FROM hudi_table_changes(
'trips', 'latest_state', 'earliest', '20240519000000000'
);
我们也可以限定某个窗口,仅查看 2024-05-18 00:00:00(不含) 至 2024-05-19 00:00:00(含) 期间的变更:
SELECT * FROM hudi_table_changes(
'trips', 'latest_state', '20240518000000000', '20240519000000000'
);
上述查询分别返回对应时间窗内发生变更的记录之最新状态;当包含终点时,状态与终点时间一致。
增量查询:变更数据捕获(CDC)模式
尽管 latest_state 模式非常适合向下游传播记录的最终状态,但某些场景需要更细粒度地了解数据是如何变化的。以 LetsMotor 的实时司机排行榜为例:一次行程的评分或小费金额,可能在行程结束很久之后仍被更新。为了保持排行榜的准确性,下游流程需要知道每一次改动的确切性质——例如,它必须区分是新增了一笔小费、更正了已有小费,还是删除了一条无效的行程记录——这些细节是 latest_state 模式无法提供的。
这正是 变更数据捕获(CDC) 大显身手的场景。在 Hudi 中,你可以为增量查询启用 CDC 模式,以获取逐记录的变更明细日志。CDC 查询不仅返回每次变更的操作类型(insert、update、delete),还会在更新的情况下同时给出记录的“变更前”与“变更后”图像(见图 4-5)。这种粒度的洞察对于需要对特定数据变更做出反应的复杂下游应用而言极其宝贵。
图示:一个 Hudi 表在两个时间点 t1 与 t2 之间的 CDC 场景,展示新增、删除与更新,以及对应 CDC 查询返回的数据。
图 4-5. 典型 CDC 场景
图 4-5 展示了 Hudi 表在 t1 与 t2 两个时刻的两个快照。在这段时间内,有一条记录被插入,一条被更新,另一条被删除。对这个时间窗口进行 CDC 模式的增量查询,会返回一份这些操作的详细日志。返回结果包含一个操作类型列 op:i 表示插入,u 表示更新,d 表示删除。
- 插入(i) :
before列为null,after列包含新插入的记录。 - 更新(u) :
before列包含更新前版本,after列包含更新后版本。 - 删除(d) :
before列包含删除前版本,after列为null,表示该记录已被删除。
启用 CDC
要使用 CDC 查询,必须先在表级别启用该特性。下面是创建新 trips 表时设置的示例:
CREATE TABLE trips (
uuid STRING,
start_ts BIGINT,
rider STRING,
driver STRING,
fare DOUBLE,
update_ts BIGINT,
city STRING
) USING HUDI
TBLPROPERTIES(
primaryKey = 'uuid',
preCombineField = 'update_ts',
'hoodie.table.cdc.enabled' = 'true', -- 1 启用 CDC
'hoodie.table.cdc.supplemental.logging.mode' = 'OP_KEY_ONLY' -- 2 设置 CDC 日志模式
)
PARTITIONED BY (city);
hoodie.table.cdc.enabled:开启表的 CDC 功能。hoodie.table.cdc.supplemental.logging.mode:配置 CDC 相关数据的补充日志模式。
一旦启用 CDC,Hudi 的 writer 会在每个文件组中、与基准文件与常规日志文件并列,生成后缀为 .cdc 的 CDC 日志文件。它们记录每次写入操作产生的变更明细,供查询引擎读取解析。
CDC 补充日志模式
hoodie.table.cdc.supplemental.logging.mode 控制 CDC 日志中记录的内容。各模式如下表所示:
| CDC 日志模式 | 说明 |
|---|---|
| OP_KEY_ONLY | CDC 日志只存储发生变化的记录键及其操作类型。 |
| DATA_BEFORE | CDC 日志存储变更前的完整记录,以及操作类型。 |
| DATA_BEFORE_AFTER | CDC 日志同时存储变更前与变更后的完整记录,以及操作类型(默认)。 |
如上所示,OP_KEY_ONLY 对存储最节省,但查询时需要引擎做额外回查来还原 before/after;DATA_BEFORE_AFTER 直接记录前后图像,查询性能最佳,但存储开销最大;DATA_BEFORE 则在二者之间折中。选择哪种模式,就是在存储开销与 CDC 查询性能之间做取舍。
警告
hoodie.table.cdc.enabled 与 hoodie.table.cdc.supplemental.logging.mode 都是永久的表级配置:一旦为该表设定,就不能修改或关闭。
运行 CDC 增量查询
在为表启用 CDC 之后,CDC 查询使用与 latest_state 模式相同的表值函数 hudi_table_changes。唯一的区别是第二个参数应设置为 'cdc' 。例如:
SELECT *
FROM hudi_table_changes('trips', 'cdc', '20240518000000000', '20240519000000000');
上述查询会在给定时间窗内,返回逐记录的变更日志(包含 op、before、after 等列),从而为下游实时榜单、审计、回放与精细化数据同步提供所需的完整语义。
值得关注的读侧特性(Highlighting Noteworthy Features)
在前文介绍了多种查询类型后,我们已经看到 Hudi 如何覆盖关键的分析型读取需求。接下来聚焦三个额外的读相关特性,说明其适用场景与收益。
流式读取(Streaming Read)
Hudi 的 timeline 是其强大的增量处理能力的核心,这使其天然契合流式读取模式。简而言之,流式读取是基于源端偏移量,以小而连续的批次处理数据。这与 Hudi 的增量查询机制(利用 timeline 上的动作时间戳仅抓取自上次读取以来发生变化的数据)完美吻合。
Hudi 开箱即用地与主流流处理框架深度集成,尤其是Spark Structured Streaming 与 Flink(两者都秉持“Streaming-first”的设计理念)。这种原生支持大幅简化实时数据管道的开发。下面用 PySpark 展示如何实现一次流式读取:
from pyspark.sql import functions as F
base_path = "/base/path/to/the/table"
def func(batch_df, batch_id):
# 简单的聚合逻辑——返回聚合后的 DataFrame
return (batch_df.groupBy("column_name")
.agg(
F.count("*").alias("record_count"),
F.sum("numeric_column").alias("total_amount"),
F.avg("numeric_column").alias("avg_amount"),
F.max("timestamp_column").alias("max_timestamp")
))
(spark.readStream.format("hudi")
.option("hoodie.datasource.query.type", "incremental")
.option("hoodie.datasource.query.incremental.format", "cdc")
.load(base_path)
.writeStream.format("console")
.foreachBatch(func)
.start())
上述示例展示了常见用法:对已启用 CDC 的 Hudi 表执行持续的 CDC 增量查询。我们设置了一个增量读取的流式源,对每个微批次进行聚合,并打印结果。这里无需显式设置增量查询的起止时间戳,Hudi 会在持续查询执行过程中内部管理。
在真实业务中,你通常会把变换后的流写回另一张 Hudi 表,形成链式的流式转换。这种“串接”流作业的能力可构建复杂的多阶段数据管道。Hudi 将细粒度变更追踪的 timeline与 MOR 表吸收高吞吐写入的能力结合,为现代流式架构打下坚实基础。更多示例可参见官方文档的 streaming read 页面。
读时模式演进(Schema Evolution on Read)
在第 3 章我们讨论了写时的模式演进,用于在写入侧处理模式不匹配并确保后向兼容。但数据系统是动态的,有时必须进行更激烈、不后向兼容的模式变更,比如重命名、删除、修改甚至移动列(包括嵌套复杂类型中的列)。
Hudi 通过读时模式演进来满足此需求。启用配置 hoodie.schema.on.read.enable=true 后,你可以无需重写底层数据就完成这些复杂的模式变更。来看一个在启用该功能后重命名列的例子:
SET hoodie.schema.on.read.enable=true;
ALTER TABLE trips RENAME COLUMN driver TO driver_id;
Hudi 并不会启动一次大规模数据重写,而是执行一次 alter_schema 操作,在 .hoodie/.schema/ 目录下创建一个特殊的 .schemacommit 文件。该文件充当账本:为原始模式中的每个列分配唯一 ID,并记录变更(此处为该列 ID 的新名称)。
当查询引擎读取表时,它看到的是新模式;但底层的基准文件与日志文件仍以旧模式写入。Hudi 通过 .schemacommit 中的信息,在读取时将新模式映射回旧模式。这种仅元数据的方式高效,避免了昂贵的数据迁移。
警告
尽管强大,使用 ALTER TABLE 做不后向兼容的变更具有显著的运维风险:该命令会永久修改表模式,你必须确保上游写入与下游读取都能处理新结构。比如删除列后,仍按旧模式产出记录的写作业很可能失败;若其他团队的报表依赖旧模式,你需要与之协调以避免中断。因此,除非必要,Hudi 不建议进行不后向兼容的变更。
Hudi 还支持其他不兼容变更,如删除列、变更列类型与重排列顺序。完整支持列表与更多示例请参考官方文档。
使用 Rust 或 Python 进行读取(Read Using Rust or Python)
虽然 Hudi 起步于 JVM 世界,并与 Spark、Flink 等 JVM 引擎深度集成,其生态正在快速扩展。为支持与非 JVM 框架的原生集成,社区引入了 Hudi-rs(一个用 Rust 实现的新版本)。
Hudi-rs 的目标是标准化 Hudi 核心 API并扩大其在更广泛数据系统中的采用。它以 Rust 实现,并提供 Python 语言绑定,使得来自 Rust 与 Python 生态的引擎可以原生读取 Hudi 表。当前已与 Ray、Daft 等现代数据框架集成。
例如,你可以直接在基于 Rust 的查询引擎 Apache DataFusion 上对 Hudi 表执行快照查询:
let ctx = SessionContext::new();
let hudi = HudiDataSource::new("/base/path/to/the/table").await?;
ctx.register_table("trips", Arc::new(hudi))?;
let df: DataFrame = ctx.sql("SELECT * from trips where city = 'SF'").await?;
df.show().await?;
目前 Hudi-rs 重点提供读取支持;长期愿景是实现与 Java 实现的功能等价,从而在这些快速发展的生态中显著扩展 Hudi 的能力与用例。
生态集成(Ecosystem Integration)
Hudi 在数据生态中拥有广泛支持。下表列出了撰写时点对 Hudi 提供读/写支持的项目与产品。
表 4-3. Hudi 的生态集成
| 项目 / 产品 | 支持能力 |
|---|---|
| Onehouse.ai | 读、写 |
| Apache Spark | 读、写 |
| Apache Flink | 读、写 |
| Presto | 读 |
| Trino | 读 |
| Apache Hive | 读 |
| DBT | 读、写 |
| Apache Kafka, Kafka Connect | 写 |
| Apache Kafka | 写 |
| Apache Pulsar | 写 |
| Debezium | 写 |
| Apache Kyuubi | 读、写 |
| ClickHouse | 读 |
| Apache Impala | 读、写 |
| AWS Athena | 读 |
| AWS EMR | 读、写 |
| AWS Redshift | 读 |
| AWS Glue | 读、写 |
| Google BigQuery | 读 |
| Google DataProc | 读、写 |
| Azure Synapse | 读、写 |
| Azure HDInsight | 读、写 |
| Databricks | 读、写 |
| Vertica | 读 |
| Apache Doris | 读 |
| StarRocks | 读 |
| Daft | 读 |
| Ray | 读 |
随着 Hudi 不断积极扩展其生态集成,这份清单也会持续增长。请以官方“生态支持”页面为准。
总结
本章我们剖析了 Hudi 在读侧的丰富而灵活的能力,这些能力使其成为数据湖仓框架中的强大组件。我们首先介绍了 Hudi 如何与查询引擎集成,并借助数据目录提供必要的表模式与文件位置。该集成既插入到规划阶段(Hudi 会基于查询类型确定需要读取的精确文件切片),也覆盖执行阶段(支持对这些文件切片进行正确而高效的读取)。
在此基础上,我们回顾了 Hudi 支持的完整查询类型谱系:包括用于获取最新记录版本的快照查询,以及支持审计与调试、允许读取历史版本的时光回溯查询(Time Travel) 。我们还介绍了增量查询——构建级联数据管道、以高效方式传播变更的核心能力。增量查询同时支持latest-state(最新状态)与 CDC 两种模式,其中 CDC 能提供关于每次变更的丰富行级细节。
最后,我们强调了若干完善 Hudi 读能力的高级特性:流式读取与 Hudi 的“增量优先”设计天然契合;读时模式演进满足不向后兼容变更的需求;以及 Hudi-rs 项目将 Hudi 的触角延伸至 Rust 与 Python 生态。整体来看,这些特性共同构成了一套稳健而灵活的工具,帮助你从数据中持续提取价值。