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

一、业务场景设定

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

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

模拟以下操作序列:

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

二、COW 表文件布局演进

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

复制代码
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 条记录状态

复制代码
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

复制代码
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:清理旧版本

复制代码
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 相同)

复制代码
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 条

复制代码
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

复制代码
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

复制代码
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:清理旧版本

复制代码
hudi_orders_mor/
├── .hoodie/
│   └── ... (所有timeline instants保留)
│
└── dt=2024-01-01/
    ├── fg001_0_20240101150000.parquet           ← 干净的最新Base
    ├── fg002_0_20240101150000.parquet
    └── fg003_0_20240101150000.parquet
相关推荐
库拉大叔1 小时前
大模型AI横评实测:GPT-4与Claude 3.5三大维度对比,落地选型怎么选?
大数据·人工智能
ModelHub XC信创模盒1 小时前
压力之下,重构赛道:从中美AI博弈到信创生态的深层跃迁
大数据·人工智能·重构·开源·信创·范式
keruilai07691 小时前
工业降温从细微之处优化升级 小小改动大幅提升舒适体验
大数据
TG_yunshuguoji1 小时前
亚马逊云代理商:如何用 CloudWatch+Lambda 打造自动化告警系统
大数据·运维·自动化·云计算·aws
电商API_180079052471 小时前
高可用采集架构:分布式定时抓取淘宝商品详情项目设计
大数据·分布式·架构·数据挖掘·网络爬虫
是有头发的程序猿2 小时前
AI Agent自动化实战!基于OpenClaw淘宝商品详情API,实现无人化商品采集与分析
大数据·人工智能·自动化
腾视科技AI2 小时前
安全驾驶 智在掌控|腾视科技ES06车载智能终端,为车辆运营赋能
大数据·人工智能·科技·安全·ai·边缘计算·车载智能终端
SEO_juper2 小时前
2026 五大高毛利细分赛道:关键词挖掘、建站模板、内容布局完整方案
大数据·人工智能·seo·geo·谷歌优化·2026·毛利
SLD_Allen2 小时前
同花顺Skill广场,为金融AI实战注入新动能!
大数据·人工智能·金融