【Hudi】 数据怎么存 — Parquet + Avro + 两种表类型
Hudi 没有发明新存储格式,它站在 Parquet 和 Avro 之上。这不是"技术选型",而是刻意为之。
为什么用两种格式
Parquet 是列式文件,读的时候只扫需要的列,跳过滤掉的不读。一张 100 列的表查 3 个字段,Parquet 只读 3% 的数据。
Avro 是行式文件,一行一行写,追加快。适合做"变更日志"——把每条更新按时间顺序写进 log 文件。
两种格式各司其职:查询走 Parquet(列式,快读),写入走 Avro(行式,快写)。Hudi 做的只是在上面加了一层组织逻辑:哪个文件是 data file,哪个是 log file,它们之间怎么合并。
两种表类型:COW 和 MOR
COW(Copy-on-Write)
COW 的逻辑很简单:每次写入都重写 Parquet 文件。写入时把受影响的 Parquet 文件读出来,和新的变更合并,再写回一个新 Parquet。读的时候直接读这份 Parquet,没有额外步骤。
特点:
- 读快:直接读 Parquet,不用合并
- 写慢:每次写入都要重写 data file,频率越高代价越大
- 适合低频写入:一天跑几批,一小时一两次
MOR(Merge-on-Read)
MOR 的逻辑是:写入走 Avro log 文件,读的时候再合并。新的变更先写进 Avro log 文件(快,不用重写整个 Parquet)。读的时候把 data file(Parquet)和 log file(Avro 里的增量)合并起来,返回最新结果。
特点:
- 写快:只追加 Avro log,不用重写 Parquet
- 读慢:每次读要合并,但可以 compact(把 log 合并进 data file)来降延迟
- 适合高频写入:近实时写入,分钟级甚至更频繁
对比
| COW | MOR | |
|---|---|---|
| 写代价 | 高(重写 Parquet) | 低(追加 Avro log) |
| 读代价 | 低(直接读 Parquet) | 中(合并 Parquet + Avro log) |
| 适用场景 | 批量 ETL | 近实时入湖 |
| 更新粒度 | 全文件 | 增量 log |
选型本质上是在读写之间做取舍:让写轻快就要让读多干活,反过来也一样。
实际怎么存
一张 Hudi 表在 HDFS/对象存储上的目录结构大致是这样:
orders/
├── 2024-01-01/
│ ├── filegroup1_uuid.parquet # data file
│ └── filegroup2_uuid.parquet
├── 2024-01-02/
│ ├── filegroup1_uuid.parquet
│ └── filegroup1_uuid.log # log file(MOR 才有)
└── .hoodie/
└── ... # Timeline 元数据
- 按分区路径组织(
2024-01-01/、2024-01-02/) - 每个分区里有多个 file group,用 UUID 区分
- COW 只有
.parquet,MOR 还有.log .hoodie/目录存 Timeline 元数据(下篇讲)
寒蝉 Hancic

