Hudi文件布局:COW与MOR表案例解析

一、业务场景设定

假设有一张用户订单表,按天分区,记录用户的订单状态变更:

vbnet 复制代码
表名: hudi_orders
Record Key: order_id
Precombine Key: update_ts
Partition Key: dt (订单日期)
目标文件大小: 128MB

模拟以下操作序列:

vbnet 复制代码
t1: 批量写入 1000 条新订单 (Insert)
t2: 更新其中 200 条订单状态为"已支付" (Upsert)
t3: 再写入 500 条新订单 + 更新 100 条为"已发货" (Upsert)
t4: 触发 Compaction (仅 MOR)
t5: 触发 Clean

二、COW 表文件布局演进

1.初始写入 t1:Insert 1000 条记录

less 复制代码
hudi_orders/
├── .hoodie/
│   ├── hoodie.properties
│   └── 20240101120000.commit                    ← t1 commit 完成标记
│       (instant: 20240101120000, action: commit)
│
└── dt=2024-01-01/
    ├── file_group_1/
    │   └── fg001_0_20240101120000.parquet       ← 600条记录, ~76MB
    └── file_group_2/
        └── fg002_0_20240101120000.parquet       ← 400条记录, ~51MB
  • 1000 条记录按目标文件大小拆分为 2 个 File Group
  • 每个 File Group 有且仅有一个 Base File(Parquet)
  • 文件名格式:[fileId]_[writeToken]_[instantTime].parquet

2.第二次写入 t2:Upsert 更新 200 条记录状态

scss 复制代码
hudi_orders/
├── .hoodie/
│   ├── 20240101120000.commit
│   └── 20240101130000.commit                    ← t2 commit
│
└── dt=2024-01-01/
    ├── file_group_1/
    │   ├── fg001_0_20240101120000.parquet       ← 旧版本 (600条)
    │   └── fg001_0_20240101130000.parquet       ← 新版本 (600条,含120条更新) 
    └── file_group_2/
        ├── fg002_0_20240101120000.parquet       ← 旧版本 (400条)
        └── fg002_0_20240101130000.parquet       ← 新版本 (400条,含80条更新) 

COW 核心行为:

3.第三次写入 t3:Insert 500 + Update 100

sql 复制代码
hudi_orders/
├── .hoodie/
│   ├── 20240101120000.commit
│   ├── 20240101130000.commit
│   └── 20240101140000.commit                    ← t3 commit
│
└── dt=2024-01-01/
    ├── file_group_1/
    │   ├── fg001_0_20240101120000.parquet       ← 旧版本v1
    │   ├── fg001_0_20240101130000.parquet       ← 旧版本v2
    │   └── fg001_0_20240101140000.parquet       ← 最新版本 (600+300=900条) 
    │                                              (含60条update + 300条insert写入此FG)
    ├── file_group_2/
    │   ├── fg002_0_20240101120000.parquet       ← 旧版本v1
    │   ├── fg002_0_20240101130000.parquet       ← 旧版本v2
    │   └── fg002_0_20240101140000.parquet       ← 最新版本 (400+200=600条含40条update)
    │
    └── file_group_3/                            ← 新建FileGroup (Insert溢出)
        └── fg003_0_20240101140000.parquet       ← 新文件 (0条旧数据, 纯新insert)
  • Small File Handling 机制:500 条 Insert 优先填充未满的 fg001 和 fg002
  • 当已有文件组都达到目标大小后,创建新的 fg003
  • Update 的 100 条通过索引定位到对应 File Group,触发整文件重写

4.Clean 后 t5:清理旧版本

sql 复制代码
hudi_orders/
├── .hoodie/
│   ├── 20240101120000.commit
│   ├── 20240101130000.commit
│   ├── 20240101140000.commit
│   └── 20240101150000.clean                     ← Clean action
│
└── dt=2024-01-01/
    ├── fg001_0_20240101140000.parquet           ← 只保留最新版本
    ├── fg002_0_20240101140000.parquet           ← 只保留最新版本
    └── fg003_0_20240101140000.parquet           ← 只有一个版本

Clean 规则:根据hoodie.cleaner.commits.retained(默认保留最近 10 个 commit 的文件版本),超出部分被清理。

三、MOR 表文件布局演进

1.初始写入 t1:Insert 1000 条(与 COW 相同)

arduino 复制代码
hudi_orders_mor/
├── .hoodie/
│   ├── hoodie.properties
│   └── 20240101120000.deltacommit               ← 注意是 deltacommit
│
└── dt=2024-01-01/
    ├── fg001_0_20240101120000.parquet           ← Base File, 600条
    └── fg002_0_20240101120000.parquet           ← Base File, 400条

MOR 表的 Insert 首次写入也是生成 Parquet Base File(与 COW 相同),区别在于后续 Upsert。

2.第二次写入 t2:Upsert 更新 200 条

arduino 复制代码
hudi_orders_mor/
├── .hoodie/
│   ├── 20240101120000.deltacommit
│   └── 20240101130000.deltacommit               ← t2 deltacommit
│
└── dt=2024-01-01/
    ├── fg001_0_20240101120000.parquet           ← Base File 不变!
    ├── .fg001_20240101130000.log.1_0-1-0        ← Log File (120条更新) 
    ├── fg002_0_20240101120000.parquet           ← Base File 不变!
    └── .fg002_20240101130000.log.1_0-1-0        ← Log File (80条更新) 

MOR 核心行为:

3.第三次写入 t3:Insert 500 + Update 100

sql 复制代码
hudi_orders_mor/
├── .hoodie/
│   ├── 20240101120000.deltacommit
│   ├── 20240101130000.deltacommit
│   └── 20240101140000.deltacommit               ← t3
│
└── dt=2024-01-01/
    │
    │── (FileGroup 1: fg001)
    ├── fg001_0_20240101120000.parquet           ← 原始Base (600条)
    ├── .fg001_20240101130000.log.1_0-1-0        ← t2的log (120条update)
    ├── .fg001_20240101140000.log.1_0-2-0        ← t3的log (60条update + 200条insert) 
    │
    │── (FileGroup 2: fg002)
    ├── fg002_0_20240101120000.parquet           ← 原始Base (400条)
    ├── .fg002_20240101130000.log.1_0-1-0        ← t2的log (80条update)
    ├── .fg002_20240101140000.log.1_0-2-0        ← t3的log (40条update + 150条insert)
    │
    │── (FileGroup 3: fg003 - 新建)
    └── .fg003_20240101140000.log.1_0-2-0        ← 纯insert的log (150条) 
                                                   (注: 无Base File!)
  • MOR 表的 Insert 可以直接写入 Log 文件(无需 Base File 先存在)
  • 同一 File Group 的不同 commit 产生不同的 Log 文件
  • 此时 fg001 的完整数据 = Base(600) + log_t2(120 update) + log_t3(60 update + 200 insert) = 实际 800 条有效记录

4. Compaction 执行 - t4

scss 复制代码
hudi_orders_mor/
├── .hoodie/
│   ├── 20240101120000.deltacommit
│   ├── 20240101130000.deltacommit
│   ├── 20240101140000.deltacommit
│   ├── 20240101150000.compaction.requested      ← 调度
│   ├── 20240101150000.compaction.inflight       ← 执行中
│   └── 20240101150000.commit                    ← 完成(注意变为commit)
│
└── dt=2024-01-01/
    │
    │── (FileGroup 1: fg001) - Compaction后
    ├── fg001_0_20240101120000.parquet           ← 旧Base (待Clean)
    ├── .fg001_20240101130000.log.1_0-1-0        ← 旧Log (待Clean)
    ├── .fg001_20240101140000.log.1_0-2-0        ← 旧Log (待Clean)
    ├── fg001_0_20240101150000.parquet           ← 新Base! (800条完整数据) 
    │
    │── (FileGroup 2: fg002) - Compaction后
    ├── fg002_0_20240101120000.parquet           ← 旧Base (待Clean)
    ├── .fg002_20240101130000.log.1_0-1-0        ← 旧Log (待Clean)
    ├── .fg002_20240101140000.log.1_0-2-0        ← 旧Log (待Clean)
    ├── fg002_0_20240101150000.parquet           ← 新Base! (570条完整数据)
    │
    │── (FileGroup 3: fg003) - Compaction后
    ├── .fg003_20240101140000.log.1_0-2-0        ← 旧Log (待Clean)
    └── fg003_0_20240101150000.parquet           ← 新Base! (150条)

5. Clean 后 t5:清理旧版本

ini 复制代码
hudi_orders_mor/
├── .hoodie/
│   └── ... (所有timeline instants保留)
│
└── dt=2024-01-01/
    ├── fg001_0_20240101150000.parquet           ← 干净的最新Base
    ├── fg002_0_20240101150000.parquet
    └── fg003_0_20240101150000.parquet
相关推荐
BizObserver6 小时前
传统制造企业官网升级:2026数字化品牌阵地建设与服务商能力评估
大数据·人工智能·制造
leoZ2316 小时前
Git 集成实战完全指南(三):自动化 Commit 与 PR
大数据·git·elasticsearch
TDengine (老段)6 小时前
TDengine 函数完整参考 — 聚合、时序、字符串、时间、数学
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
初晴融雪-快雪时晴6 小时前
动产与不动产全景分析:从法律定义到资产配置
大数据·人工智能
AC赳赳老秦7 小时前
时间开销自动统计:OpenClaw 记录工作任务时长、分析时间分配、给出优化建议
java·大数据·开发语言·python·自动化·deepseek·openclaw
码农学院7 小时前
GEO效果维持的持续迭代机制与内容老化监控技术
大数据·人工智能
TDengine (老段)7 小时前
昆仑数智选择 TDengine TSDB,提升页岩气生产运行可视化与精细化管理能力
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
TMT星球7 小时前
金山办公持续落子AI办公:灵犀专业版、WPS Comate同日发布
大数据·人工智能·wps
陆水A7 小时前
【问数系统】SQL跑对了图表却空了?打通问数系统最后1公里的3个API坑
大数据·数据库·自然语言处理·big data·etl工程师
名侦探7227 小时前
构建 Agent 的五大难点与解决方案
java·大数据·人工智能