【Hudi】 数据怎么存 — Parquet + Avro + 两种表类型

24 阅读 1143 字 · 约 4 分钟

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)来降延迟
  • 适合高频写入:近实时写入,分钟级甚至更频繁

对比

COWMOR
写代价高(重写 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 元数据(下篇讲)