惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Latest news
Latest news
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
博客园 - 司徒正美
B
Blog RSS Feed
AWS News Blog
AWS News Blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
Last Week in AI
Last Week in AI
NISL@THU
NISL@THU
博客园 - Franky
P
Proofpoint News Feed
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
S
Schneier on Security
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
WordPress大学
WordPress大学
The Hacker News
The Hacker News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
罗磊的独立博客
T
The Blog of Author Tim Ferriss
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
SecWiki News
SecWiki News
云风的 BLOG
云风的 BLOG
美团技术团队
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog
S
Security Affairs

博客园 - 小得盈满

NATS 的基本安装及使用 Doris 和 StarRocks 性能测试对比 ClickHouse 添加磁盘扩容存储 使用 Docker 部署 Elasticsearch 集群 Qdrant 单机和集群的安装方法 ClickHouse UDF 的基本用法 StarRocks 容器镜像构建 Kafka 线上性能调优 StarRocks 集群安装 Kafka 的分片和副本机制 Flink 自定义 ClickHouse Table Connector 的简单实现 Redis 性能优化实战 记一次 Go 调用系统命令出现的问题分析 APISIX proxy-cache 插件用法 etcd 历史版本回溯的方法 APISIX 简单的自定义插件开发步骤 Kafka 集群副本数量调整 使用 Docker Compose 安装 APISIX etcd 集群安装 Consul 集群安装
Hudi 时间旅行与增量查询的区别分析
小得盈满 · 2026-05-12 · via 博客园 - 小得盈满

当前文章整理关于 Apache Hudi 时间旅行(Time Travel)、增量查询(Incremental Query)、压缩(Compaction)和清理(Cleaner)的关键内容,重点说明它们的语义区别、底层关系,以及对历史版本可用性的影响。

1. 主要区别

时间旅行和增量查询都依赖 Hudi 的 Timeline、MVCC 和 File Slice 机制,但查询目标不同:

功能 核心问题 返回结果
时间旅行 某个历史时刻,整张表长什么样? 指定 instant 对应的完整快照
增量查询 某个时间范围内,哪些记录发生过变化? 指定 begin/end 范围内的变更记录

简单理解如下:

  • Time Travel = 查询历史快照
  • Incremental Query = 查询一段时间内的变化

2. 时间旅行

时间旅行通过 as.of.instant 指定一个历史 instant,查询表在该 instant 的逻辑状态。

代码示例:

val df = spark.read.format("hudi")
  .option("as.of.instant", targetInstant)
  .load(basePath)

典型用途如下:

  • 数据审计
  • 问题排查
  • 历史版本对比
  • 错误数据恢复
  • 合规留存查询

时间旅行返回的是目标 instant 下的完整表快照,而不是变化记录。

3. 增量查询

增量查询通过 begin/end instant 指定一个时间范围,只读取该范围内发生变化的数据。

示例代码:

val df = spark.read.format("hudi")
  .option("hoodie.datasource.query.type", "incremental")
  .option("hoodie.datasource.read.begin.instanttime", beginCompletionTime)
  .option("hoodie.datasource.read.end.instanttime", endCompletionTime)
  .load(basePath)

典型用途如下:

  • 增量 ETL
  • CDC 同步
  • 下游数据仓库同步
  • 流式或批式增量处理
  • 只处理新增、更新、删除的数据

增量查询默认通常返回增量范围内每个 key 的最新状态,不一定等同于完整 CDC 明细;如果需要完整 insert/update/delete 事件,需要结合 Hudi CDC 能力和对应配置。

4. Hudi 1.0.x 中的 requested time 与 completion time

在 Hudi 1.0.x 的 timeline layout v2 中,需要区分两个时间:

时间 含义 常见位置
requested time instant 被请求创建的时间 记录字段 _hoodie_commit_time 通常是这个值
completion time commit 真正完成的时间 timeline 中 HoodieInstant.getCompletionTime

关键点:

  • _hoodie_commit_time 适合展示和排查。
  • 增量查询的 begin.instanttime / end.instanttime 应优先使用 completion time。
  • 不建议直接把 _hoodie_commit_time 当成增量查询边界。

推荐通过 HoodieTableMetaClient 获取 completed timeline:

case class CommitTime(requestedTime: String, completionTime: String)

val commits = metaClient.getCommitsTimeline
  .filterCompletedInstants()
  .getInstants
  .asScala
  .map(instant => CommitTime(instant.requestedTime(), instant.getCompletionTime))
  .toSeq

5. Compaction 与查询功能的关系

Compaction 不是时间旅行,也不是增量查询。它是 MOR 表的 table service,用于把 log files 合并进新的 base parquet 文件。

关系可以理解为:

Timeline / MVCC / File Slices
├── Time Travel       按某个 instant 读取历史快照
├── Incremental Query 按 instant 范围读取变化
└── Compaction        合并 MOR 表的 base + log,生成新的 base file

Compaction 的主要作用:

  • 提升 MOR 表查询性能
  • 让 Read Optimized Query 能看到被压缩进 base file 的数据
  • 减少 snapshot 查询时需要合并的 log files
  • 改变物理文件布局,而不是改变数据的逻辑历史

6. COW 与 MOR 的底层差异

6.1 COW 表

COW 是 Copy-on-Write。更新时直接生成新的 parquet base file。

示例:

t1: base parquet v1: A=10
t2: update A=20
    生成 base parquet v2: A=20
    旧 base parquet v1 暂时保留

特点如下:

  • 没有 compaction。
  • 每次更新会生成新的 base file。
  • 旧 base file 是否保留,由 cleaner 参数决定。
  • 时间旅行依赖旧 file version 是否仍然存在。

6.2 MOR 表

MOR 是 Merge-on-Read。更新通常先写入 log file,后续可通过 compaction 合并成新的 base file。

示例:

t1: base parquet: A=10
t2: log file: A=20
t3: compaction
    生成新的 base parquet: A=20
    旧 base/log 暂时保留

特点如下:

  • 有 compaction。
  • 写入性能通常更好。
  • Snapshot Query 会合并 base + log。
  • Read Optimized Query 只读 base file,可能落后于最新写入。
  • 旧 base/log 是否清理,由 cleaner 参数决定。

7. Compaction 和时间旅行、增量查询的关系

7.1. Compaction 和时间旅行的关系

Compaction 通常不会影响时间旅行功能。

因为 Compaction 一般不是原地覆盖旧文件,而是生成新的 file slice。旧的 base/log 文件通常会暂时保留,用于支持 MVCC、长查询和历史版本读取。

例如:

t1: A=10
t2: A=20
t3: B=30
t4: compaction

Compaction 后可能形成:

旧 file slice:
  base/log 可以重建 t1/t2/t3 的历史状态

新 file slice:
  compacted base file,包含 t4 时刻的 A=20, B=30

所以此时:

as.of.instant = t1 -> 理论上仍可看到 A=10
as.of.instant = t4 -> 看到 A=20, B=30

所以真正可能让 as.of.instant = t1 不可用的是 cleaner 或 timeline/file retention,而不是 compaction 本身。

7.2. Compaction 和增量查询的关系

Compaction 通常不会改变增量查询的逻辑结果。

前面提到过 Compaction 会把文件变更合并到 base file,但是原来的文件并不会立即删除,所以增量查询仍然可以按照 timeline 的 instant 范围判断哪些记录发生变化。

但 compaction 可能影响读取的文件路径、查询性能以及 Read Optimized Query 的可见数据范围。

但是如果 cleaner 已经清理了相关的历史文件,那么增量查询就会出现问题。

8. Cleaner 参数决定历史版本保留问题

Cleaner 参数负责删除超过保留策略的旧文件版本。

所以 Compaction 和 Cleaner 的区分如下:

  • Compaction = 生成新的合并后版本
  • Cleaner = 删除过期旧版本

如果 cleaner 还没有清理支持 t1 的旧文件,那么指定 as.of.instant = t1 就可以查询历史版本。

如果 cleaner 已经清理了支持 t1 的旧文件,那么查询 t1 的版本就会出现失败。

COW 和 MOR 都受 cleaner 管理,对比如下:

表类型 是否有 compaction 是否有 cleaner 历史文件清理
COW cleaner
MOR cleaner

9. 时间线示例

假设有一张 MOR 表时间线如下:

t1 delta/base: A=10
t2 delta:      A=20
t3 delta:      B=30
t4 compaction: 生成新 base: A=20, B=30
t5 delta:      B=40

时间旅行结果为:

as.of t1 -> A=10
as.of t2 -> A=20
as.of t3 -> A=20, B=30
as.of t5 -> A=20, B=40

增量查询结果:

begin=t1, end=t3 -> 返回 t2~t3 范围内变化的数据
begin=t3, end=t5 -> 返回 t5 范围内变化的数据,例如 B=40

Compaction t4 的作用是在压缩前 Snapshot 可能需要读取 base + 多个 log,但是压缩后 t4 之后的读取可以更多依赖新的 compacted base file。

t4 之后 t1 的历史语义不会立即消失,t1 是否还能查,取决于文件是否仍在 cleaner 策略保留范围内。

10. 常见理解的问题

10.1 Compaction 后旧数据不一定立刻消失

Compaction 会生成新的合并后 base file,但旧 base/log 通常由 cleaner 后续按策略清理。

10.2 MOR 和 COW 的历史版本问题

COW 更新时也会生成新的 base file version,旧版本也可能暂时保留,因此也可以支持时间旅行。区别是 COW 没有 compaction。

10.3 增量查询的边界问题

在 Hudi 1.0.x 中不推荐使用 _hoodie_commit_time。因为 _hoodie_commit_time 通常是 requested time,而增量查询 begin/end 边界应优先使用 timeline 中的 completion time。

10.4 时间旅行不能查询任意历史版本

时间旅行能力受 cleaner、archival 和文件保留策略影响。旧文件被清理后,对应历史快照可能无法重建。

11. 生产环境配置

11.1 依赖时间旅行

如果依赖时间旅行比较强,需要配置足够的历史保留:

hoodie.cleaner.commits.retained
hoodie.keep.min.commits
hoodie.keep.max.commits

建议配置如下:

  • 根据审计和回溯需求设置保留周期。
  • 对关键版本使用 savepoint 或导出归档。
  • 不要只依赖默认 cleaner 策略。
  • 定期验证重要历史 instant 是否仍可查询。

11.2 依赖增量查询

如果对增量查询依赖比较多,需要管理好时间线参数,重点如下:

  • 时间线范围使用 completion time。
  • 增量消费者不要长时间停滞。
  • cleaner retention 必须大于最大消费延迟。
  • 下游处理逻辑保持幂等。
  • 如果需要完整 CDC 语义,开启并验证 Hudi CDC 配置。

11.3 如果使用 MOR 表

需要同时关注 compaction 和 cleaner,compaction 主要影响查询性能和 read optimized 可见性,cleaner 影响历史版本和增量查询可追溯性。

建议配置通过 compaction 控制 log 文件数量,通过 cleaner retention 控制历史可查时间。

注意不要把 compaction 误认为历史清理机制。

综上,对时间旅行和增量查询的主要区别总结如下。

  1. 时间旅行查询历史快照。
  2. 增量查询读取时间范围内的变化。
  3. Compaction 优化 MOR 表的物理文件布局。
  4. Cleaner 决定旧版本文件保留多久。
  5. COW 没有 compaction,但同样有 cleaner 和历史 file versions。
  6. MOR 有 compaction,也有 cleaner。
  7. 历史版本是否还能查,核心取决于 timeline 和所需文件是否仍在保留范围内。