2. 架构对比

聊到数据湖的架构设计,我习惯先看一个核心问题:数据到底是怎么写进去、又怎么读出来的? 这个问题看似简单,但Hudi和Delta Lake给出了完全不同的答案。

说白了,Hudi走的是“存储层优化”路线,而Delta Lake走的是“事务日志驱动”路线。这两种思路各有千秋,咱们今天就把它们掰开揉碎了讲清楚。

Hudi的两种存储模式:COW与MOR

Hudi最让我印象深刻的一点,就是它给了你两个选择——Copy-on-Write(COW) 和 Merge-on-Read(MOR)。我记得第一次接触时还纳闷:为什么一个引擎要搞两套写入模式?后来在项目中踩过坑才明白,这其实是针对不同场景的精准设计。

Copy-on-Write(COW):写一次,读最优

COW的逻辑非常直接:每次写入数据时,直接把包含该数据的所有文件重写一遍。新数据来了,旧文件就作废,查询时永远只读最新文件。

核心特点:

  • 写入时做合并,读取时零开销
  • 文件版本清晰,每个commit对应一组完整文件
  • 适合写少读多的场景

我曾经在一个实时报表项目里用过COW。业务方要求查询延迟在秒级,但数据更新频率不高,每天就几个批次。COW完美匹配——查询时直接扫Parquet文件,连Merge都不用做,爽得很。

避坑指南: 我曾经在数据频繁更新的场景下硬上COW,结果每次写入都要重写整个分区文件,小文件越积越多,写入延迟从分钟级飙升到小时级。后来我学乖了——COW不适合高频更新,这是血泪教训。

Merge-on-Read(MOR):读时合并,写更轻量

MOR的思路正好相反。它把数据分成两套文件:Base文件(存基础数据)和 Log文件(存增量更新)。写入时只追加Log,查询时再把Base和Log合并。

你想想看,这相当于把合并操作从写入阶段推迟到了读取阶段。写入变快了,但读取变慢了——典型的“空间换时间”思路。

对比维度 COW MOR
写入性能 慢(需要重写文件) 快(只追加Log)
读取性能 快(直接读最新文件) 慢(需要合并Base+Log)
数据延迟 分钟级 秒级
适用场景 批处理、报表 实时摄取、CDC

我的建议: 如果你拿不准选哪个,先问自己一个问题——你的查询对延迟敏感吗? 如果敏感,选COW;如果写入压力大、查询可以接受几秒延迟,选MOR。我在做实时数仓时,通常把MOR用在数据摄入层,COW用在服务查询层。

Delta Lake的事务日志架构

Delta Lake的架构思路跟Hudi完全不同。它不搞两套存储模式,而是用一套事务日志(Transaction Log)来管理所有数据变更。

嗯,这里要注意:Delta Lake的日志不是简单的操作记录,它本身就是数据的“真相来源”。每次写入、更新、删除,都会在 _delta_log 目录下生成一个JSON文件,记录这次操作改了哪些文件、版本号是多少。

事务日志的核心机制

我习惯把Delta Lake的事务日志想象成一个“账本”:

  • 每个commit对应一个日志文件,编号递增(000001.json、000002.json...)
  • 日志里记录了新增、删除、修改的文件列表
  • 查询时,引擎读取最新日志,确定要扫描哪些文件
# Delta Lake事务日志示例(简化版)
{
  "commitInfo": {
    "timestamp": 1700000000000,
    "operation": "WRITE",
    "operationParameters": {
      "mode": "Append"
    }
  },
  "add": {
    "path": "part-00000.snappy.parquet",
    "size": 123456,
    "partitionValues": {"date": "2024-01-01"},
    "modificationTime": 1700000000000
  },
  "remove": {
    "path": "part-00000.snappy.parquet",
    "deletionTimestamp": 1700000000000
  }
}

为什么Delta Lake要搞这么复杂的日志机制?说白了,是为了ACID事务。Hudi的COW/MOR虽然也能保证一致性,但Delta Lake的日志架构天然支持并发控制——多个写入者同时操作时,日志的版本号就是天然的锁机制。

关键区别: Hudi的架构重心在“如何存储数据”,Delta Lake的架构重心在“如何记录变更”。前者优化的是文件布局,后者优化的是事务语义。

两种架构的核心差异

咱们把两种架构放在一起对比,你会发现它们的出发点完全不同:

维度 Hudi(COW/MOR) Delta Lake(事务日志)
数据一致性 依赖文件替换 + 时间线 依赖事务日志 + 乐观并发控制
写入模式 COW:重写文件;MOR:追加Log 追加新文件 + 日志记录
读取模式 COW:直接读;MOR:合并读 根据日志确定文件列表
并发控制 基于时间线的乐观锁 基于日志的乐观并发控制
版本回滚 支持(通过时间线) 支持(通过日志回放)
小文件处理 内置Clustering机制 需要OPTIMIZE命令

我个人觉得,Hudi的架构更“务实”——它直接针对不同场景给出不同方案。而Delta Lake的架构更“优雅”——一套日志机制搞定所有问题。但优雅的代价是,日志文件本身也会成为瓶颈,尤其是当commit数量达到百万级别时。

架构对比的SVG框架图

下面这张图是我自己整理的,把两种架构的核心流程画出来了。你看一眼就能明白它们的本质区别:

Hudi vs Delta Lake 架构对比 Hudi 架构 COW 模式 写入时合并文件 MOR 模式 Base + Log 分离 时间线(Timeline)管理版本 Delta Lake 架构 事务日志(Transaction Log) 日志驱动文件列表 乐观并发控制 核心差异 • Hudi:存储层优化,通过COW/MOR两种模式适配不同读写比 • Delta Lake:日志层驱动,一套事务日志统一管理所有变更 • 共同点:都支持ACID、版本回滚、时间旅行

我的选型建议

讲完架构,咱们聊聊实际选型。我经历过几个项目,总结出几条经验:

  • 如果你对写入延迟极度敏感(比如CDC场景),Hudi的MOR模式是首选。我做过一个实时同步项目,用MOR把写入延迟压到了秒级,Delta Lake的日志机制在这种场景下反而成了瓶颈。
  • 如果你需要强事务保障(比如金融场景),Delta Lake的日志架构更靠谱。它的乐观并发控制机制经过Spark生态多年验证,我在银行项目里用它做对账,从来没出过数据不一致的问题。
  • 如果你既要又要(既要低延迟写入,又要强一致性),嗯,那就得做取舍了。我个人习惯是:用Hudi的MOR做写入层,再用Delta Lake做查询层,中间通过流式ETL同步。虽然架构复杂了点,但各取所长。

一个小技巧: 不管选哪种架构,一定要做好小文件治理。Hudi的COW模式容易产生大量小文件,Delta Lake的日志文件也会随着时间膨胀。我一般会设置定时任务,每天凌晨做一次文件合并(Clustering或OPTIMIZE),不然半年后你的NameNode会哭的。

最后说一句:架构没有银弹。Hudi和Delta Lake的底层设计哲学不同,但都能解决实际问题。关键是你得清楚自己的场景——是写多读少,还是读多写少?是追求极致性能,还是追求事务安全? 想清楚这些,选型就不难了。