数据湖仓技术全解析:Delta Lake、Iceberg、Hudi 与 Paimon

数据湖仓技术全解析:Delta Lake、Iceberg、Hudi 与 Paimon

引言

数据湖仓(Lakehouse)是近年来大数据领域最重要的架构演进之一。它试图将数据湖的灵活性、低成本与数据仓库的 ACID 事务、数据治理能力融合为统一平台,从而终结长期以来 Lambda 架构下"批"与"流"分离、"湖"与"仓"割裂的困境。

在这一赛道中,Apache Iceberg、Apache Hudi 和 Delta Lake 被业界并称为"数据湖三剑客",它们各自从不同角度解决了数据湖的核心痛点------缺乏事务支持、更新困难、元数据管理低效等。而 Apache Paimon 作为后起之秀,凭借与 Flink 的深度绑定和原生流式设计,正在实时湖仓场景中快速崛起。

本文将系统讲解这四大湖仓框架的核心架构、关键特性、代码实战与选型建议,帮助你全面理解数据湖仓技术版图。

从数据湖到湖仓:为什么需要表格式?

传统数据湖的困境

传统数据湖基于 HDFS 或对象存储(S3/ADLS/GS),以 Parquet/ORC 等文件格式存储数据,存在以下根本性问题:

  • 无事务保证:写入中途失败会留下脏数据,无法回滚
  • 更新困难:Parquet/ORC 是只追加格式,不支持行级更新和删除
  • 元数据瓶颈:Hive Metastore 的分区级元数据在大表场景下性能堪忧
  • 批流割裂:同一份数据需要批处理和流处理两套系统,数据一致性难以保证

湖仓表格式的作用

湖仓表格式(Table Format)是介于计算引擎和存储系统之间的中间层,它定义了数据文件如何组织、元数据如何管理、事务如何保证。核心价值包括:

能力 说明
ACID 事务 保证写入的原子性,支持并发读写的一致性
行级更新/删除 通过 Copy-on-Write 或 Merge-on-Read 策略实现
时间旅行 查询历史版本数据,支持审计和回滚
模式演进 安全地添加、删除、重命名列
元数据管理 文件级别的细粒度元数据,替代 Hive 的分区级元数据
流批统一 同一存储层同时支持批处理和流处理

Delta Lake

概述

Delta Lake 由 Databricks 开发,于 2019 年开源并捐赠给 Linux 基金会。它是最早将 ACID 事务引入数据湖的表格式之一,与 Apache Spark 深度集成,是 Databricks Lakehouse 平台的核心存储层。

  • 最新版本:3.3.x(兼容 Spark 3.5.x)
  • 开源协议:Apache 2.0
  • 主导方:Databricks / Linux 基金会
  • 官方文档https://docs.delta.io/

核心架构

Delta Lake 的架构围绕事务日志(Transaction Log)构建:

1. 数据文件

实际数据以 Parquet 格式存储在数据目录中,每个文件是一个不可变的 Parquet 文件。

2. 事务日志(_delta_log/

事务日志是 Delta Lake 的核心创新,存储在 _delta_log 目录下:

  • 每次对表的写操作生成一个按序编号的 JSON 文件(如 00000000000000000001.json
  • 每个日志文件记录了该次操作新增/删除的数据文件、模式变更、元数据修改等
  • Spark 会定期将小日志文件合并为 Checkpoint 文件(Parquet 格式),加速日志读取

3. MVCC 与乐观并发控制

Delta Lake 使用 MVCC(多版本并发控制)实现快照隔离:

  • 读取操作基于某个日志版本获取一致的数据快照
  • 写入操作通过乐观并发控制检测冲突:提交时比较版本号,若版本已变更则重试

关键特性

特性 说明
ACID 事务 完整的原子性、一致性、隔离性、持久性保证
时间旅行 VERSION AS OF / TIMESTAMP AS OF 查询历史版本
Merge(Upsert) 强大的条件更新/插入/删除语义
Change Data Feed 3.x 新增,订阅表的增量变更
OPTIMIZE / Z-Order 小文件合并与数据布局优化
模式演进 mergeSchema / overwriteSchema 自动演进
Delta Sharing 开放协议实现跨组织数据共享

代码示例

环境准备

bash 复制代码
pip install delta-spark==3.3.0 pyspark==3.5.5

创建与操作 Delta 表

python 复制代码
from delta import DeltaTable, configure_spark_with_delta_pip
from pyspark.sql import SparkSession

builder = SparkSession.builder \
    .appName("DeltaLakeDemo") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog")

spark = configure_spark_with_delta_pip(builder).getOrCreate()

# 写入 Delta 表
df = spark.createDataFrame([("Alice", 25), ("Bob", 30)], ["name", "age"])
df.write.format("delta").save("/tmp/delta/users")

# 读取
users = spark.read.format("delta").load("/tmp/delta/users")

# Merge(Upsert)
deltaTable = DeltaTable.forPath(spark, "/tmp/delta/users")
updates = spark.createDataFrame([("Alice", 26), ("Charlie", 35)], ["name", "age"])

deltaTable.alias("t").merge(
    updates.alias("s"), "t.name = s.name"
).whenMatchedUpdate(set={"age": "s.age"}) \
 .whenNotMatchedInsert(values={"name": "s.name", "age": "s.age"}) \
 .execute()

# 时间旅行
df_v1 = spark.read.format("delta").option("versionAsOf", 1).load("/tmp/delta/users")

# Change Data Feed
spark.sql("""
    ALTER TABLE delta.`/tmp/delta/users` SET TBLPROPERTIES (
        delta.enableChangeDataFeed = true
    )
""")
changes = spark.read.format("delta") \
    .option("readChangeDataFeed", "true") \
    .option("startingVersion", 2) \
    .load("/tmp/delta/users")

# OPTIMIZE 与 Z-Order
deltaTable.optimize().executeCompaction()
deltaTable.optimize().executeZOrderBy("name")

# Vacuum
deltaTable.vacuum(retentionHours=168)

使用 delta-rs(无需 Spark)

python 复制代码
from deltalake import DeltaTable, write_deltalake
import pandas as pd

# 写入
df = pd.DataFrame({"name": ["Alice", "Bob"], "age": [25, 30]})
write_deltalake("/tmp/delta/users_rs", df)

# 读取
dt = DeltaTable("/tmp/delta/users_rs")
print(dt.to_pandas())

# 时间旅行
dt_v1 = DeltaTable("/tmp/delta/users_rs", version=1)
print(dt_v1.to_pandas())

Apache Iceberg

概述

Apache Iceberg 由 Netflix 于 2017 年开发,后捐赠给 Apache 基金会并于 2020 年毕业为顶级项目。Iceberg 的设计哲学是引擎无关------它不绑定任何特定计算引擎,而是定义了一套开放的表格式规范,任何引擎都可以通过 API 访问 Iceberg 表。

2025 年,Iceberg 发布了 V3 表格式规范,进一步巩固了其作为"事实标准"的地位。Snowflake、BigQuery、Dremio 等主流平台均已原生支持 Iceberg。

  • 最新版本:1.7.x(V3 规范)
  • 开源协议:Apache 2.0
  • 主导方:Apache 软件基金会 / Netflix / Apple / Tabular
  • 官方文档https://iceberg.apache.org/

核心架构

Iceberg 的架构设计是其最大的技术亮点,采用三层元数据结构:

1. 元数据层(Metadata Layer)

复制代码
metadata/
├── v1.metadata.json          # 表元数据文件
├── v2.metadata.json
└── snap-xxx.avro             # 快照文件(Manifest List)
manifest/
├── manifest-1.avro           # Manifest 文件
└── manifest-2.avro
data/
├── partition-data-1.parquet  # 数据文件
└── partition-data-2.parquet
  • Metadata File:表的顶层元数据,记录当前快照、模式、分区规范等
  • Manifest List(Snapshot):快照文件,记录该版本包含的所有 Manifest 文件
  • Manifest File:记录一组数据文件的路径、统计信息(min/max/null_count)、分区值等

2. 快照与版本管理

每次写操作生成一个新的快照(Snapshot),快照之间形成版本链:

  • 读取操作基于某个快照获取一致的数据视图
  • 快照过期(Expiration)机制自动清理历史快照
  • 支持分支(Branch)和标签(Tag),V3 规范进一步增强

3. 分区演进

Iceberg 的分区信息存储在元数据中而非数据路径中,这意味着:

  • 分区规则变更不需要重写数据
  • 查询引擎通过元数据自动过滤无关文件,无需用户感知分区布局

关键特性

特性 说明
引擎无关 Spark、Flink、Trino、Presto、Hive、Doris 等均原生支持
三层元数据 Metadata → Manifest List → Manifest File,高效过滤
隐藏分区 分区逻辑在元数据中,用户无需感知物理分区
分区演进 无需重写数据即可变更分区策略
快照隔离 MVCC,读写互不阻塞
分支与标签 V3 支持 Git-like 分支和标签
行级删除 通过 Delete File(Position/Equality Delete)实现
数据压缩 后台 Compaction 合并小文件

代码示例

环境准备

bash 复制代码
pip install pyspark==3.5.5
# Iceberg 运行时 JAR 通过 Spark 配置自动下载

Spark SQL 操作 Iceberg 表

python 复制代码
from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("IcebergDemo") \
    .config("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions") \
    .config("spark.sql.catalog.local", "org.apache.iceberg.spark.SparkCatalog") \
    .config("spark.sql.catalog.local.type", "hadoop") \
    .config("spark.sql.catalog.local.warehouse", "/tmp/iceberg/warehouse") \
    .getOrCreate()

# 创建 Iceberg 表
spark.sql("""
    CREATE TABLE local.db.users (
        id INT,
        name STRING,
        age INT,
        created_at TIMESTAMP
    )
    USING iceberg
    PARTITIONED BY (bucket(16, id))
    TBLPROPERTIES ('format-version' = '2')
""")

# 写入数据
spark.sql("INSERT INTO local.db.users VALUES (1, 'Alice', 25, CURRENT_TIMESTAMP())")
spark.sql("INSERT INTO local.db.users VALUES (2, 'Bob', 30, CURRENT_TIMESTAMP())")

# 批量写入
df = spark.createDataFrame([(3, "Charlie", 35), (4, "Diana", 28)], ["id", "name", "age"])
df.writeTo("local.db.users").append()

# 行级更新与删除(需 format-version = 2)
spark.sql("UPDATE local.db.users SET age = 26 WHERE name = 'Alice'")
spark.sql("DELETE FROM local.db.users WHERE name = 'Bob'")

# Merge Into
spark.sql("""
    MERGE INTO local.db.users t
    USING (SELECT 1 as id, 'Alice' as name, 27 as age) s
    ON t.id = s.id
    WHEN MATCHED THEN UPDATE SET *
    WHEN NOT MATCHED THEN INSERT *
""")

# 时间旅行
spark.sql("SELECT * FROM local.db.users VERSION AS OF 1")
spark.sql("SELECT * FROM local.db.users TIMESTAMP AS OF '2025-01-01 00:00:00'")

# 快照管理
spark.sql("SELECT * FROM local.db.users.snapshots")
spark.sql("SELECT * FROM local.db.users.history")
spark.sql("CALL local.system.expire_snapshots('db.users', TIMESTAMP '2025-06-01 00:00:00', 10)")

# 数据压缩(Compaction)
spark.sql("CALL local.system.rewrite_data_files(table => 'db.users')")

# 分区演进(无需重写数据)
spark.sql("""
    ALTER TABLE local.db.users REPLACE PARTITION FIELD bucket(16, id) WITH bucket(32, id)
""")

Flink 操作 Iceberg 表

python 复制代码
from pyflink.table import EnvironmentSettings, TableEnvironment

env_settings = EnvironmentSettings.in_streaming_mode()
t_env = TableEnvironment.create(env_settings)

# 配置 Iceberg Catalog
t_env.execute_sql("""
    CREATE CATALOG iceberg WITH (
        'type' = 'iceberg',
        'catalog-type' = 'hadoop',
        'warehouse' = '/tmp/iceberg/warehouse'
    )
""")

# 从 Kafka 流式写入 Iceberg
t_env.execute_sql("""
    INSERT INTO iceberg.db.users
    SELECT CAST(id AS INT), name, CAST(age AS INT), CURRENT_TIMESTAMP
    FROM kafka_source
""")

使用 PyIceberg(Python 原生)

python 复制代码
from pyiceberg.catalog import load_catalog
from pyiceberg.table import Table

# 连接 Catalog
catalog = load_catalog("local", **{
    "type": "hadoop",
    "warehouse": "/tmp/iceberg/warehouse"
})

# 读取 Iceberg 表为 Pandas DataFrame
table = catalog.load_table("db.users")
df = table.scan().to_pandas()
print(df)

# 查看快照
for snapshot in table.snapshots():
    print(f"Snapshot ID: {snapshot.snapshot_id}, Time: {snapshot.timestamp_ms}")

Apache Hudi

概述

Apache Hudi(Hadoop Upserts Deletes and Incrementals)由 Uber 于 2016 年开发,2019 年成为 Apache 孵化项目,2020 年毕业为顶级项目。Hudi 是最早专注于增量处理的湖仓格式,其核心设计目标是支持近实时的数据摄入(Ingestion)和更新,特别适合 CDC(Change Data Capture)场景。

2024 年,Apache Hudi 1.0 正式发布,标志着项目进入成熟阶段。

  • 最新版本:1.0.x
  • 开源协议:Apache 2.0
  • 主导方:Apache 软件基金会 / Uber / Onehouse
  • 官方文档https://hudi.apache.org/

核心架构

Hudi 的架构围绕两种表类型和两种写入策略展开:

1. 表类型

表类型 说明 适用场景
CoW(Copy-on-Write) 每次更新重写整个 Parquet 文件 读多写少,查询性能优先
MoR(Merge-on-Read) 更新写入 Avro Log 文件,读取时合并 写多读少,写入性能优先

2. 核心概念

  • Timeline:Hudi 的核心,记录所有操作的时间线,每个操作包含 Instant Time、State(requested/inflated/completed)、Action(commit/deltacommit/compaction/clean/rollback 等)
  • FileGroup:逻辑分组,由 FileSlice 组成
  • FileSlice:CoW 表中是一个 Base File;MoR 表中是一个 Base File + 若干 Log File
  • Compaction:将 MoR 表的 Log File 合并到 Base File,变更为 CoW 格式

3. 索引机制

Hudi 内置多种索引来定位记录所在 FileGroup,避免全表扫描:

  • Bloom Filter Index:基于布隆过滤器的索引,写入时构建
  • Simple Index:基于主键的简单索引
  • HBase Index:使用 HBase 存储索引
  • Bucket Index:1.0 新增,基于哈希分桶的索引,写入性能更优

关键特性

特性 说明
行级更新/删除 CoW 和 MoR 两种策略
增量查询 查询自某次 Commit 以来的变更数据
近实时摄入 MoR 表支持分钟级延迟的数据可见性
自动 Compaction 后台自动将 Log File 合并为 Base File
自动 Clean 自动清理旧版本文件
多种索引 Bloom Filter / Simple / HBase / Bucket 索引
点查询优化 基于索引的高效点查询
CDC 支持 Hudi 1.0 原生支持 CDC 输出

代码示例

环境准备(Spark)

bash 复制代码
# Hudi 与 Spark 捆绑包
# 下载 hudi-spark3.5-bundle_2.12-1.0.0.jar
pip install pyspark==3.5.5

Spark 操作 Hudi 表

python 复制代码
from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("HudiDemo") \
    .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer") \
    .config("spark.sql.extensions", "org.apache.spark.sql.hudi.HoodieSparkSessionExtension") \
    .config("spark.sql.catalog.hudi_catalog", "org.apache.spark.sql.hudi.catalog.HoodieCatalog") \
    .config("spark.sql.catalog.hudi_catalog.spark.sql.defaultCatalog", "hudi_catalog") \
    .getOrCreate()

# 写入 Hudi 表(CoW)
hudi_options = {
    "hoodie.table.name": "users",
    "hoodie.datasource.write.recordkey.field": "id",
    "hoodie.datasource.write.partitionpath.field": "partition_path",
    "hoodie.datasource.write.precombine.field": "updated_at",
    "hoodie.upsert.shuffle.parallelism": 2,
    "hoodie.insert.shuffle.parallelism": 2
}

df = spark.createDataFrame([
    (1, "Alice", 25, "2025-01-01", "2025-01-01 10:00:00"),
    (2, "Bob", 30, "2025-01-01", "2025-01-01 10:00:00")
], ["id", "name", "age", "partition_path", "updated_at"])

df.write.format("hudi") \
    .options(**hudi_options) \
    .mode("overwrite") \
    .save("/tmp/hudi/users")

# Upsert
updates = spark.createDataFrame([
    (1, "Alice", 26, "2025-01-01", "2025-01-02 10:00:00"),
    (3, "Charlie", 35, "2025-01-02", "2025-01-02 10:00:00")
], ["id", "name", "age", "partition_path", "updated_at"])

updates.write.format("hudi") \
    .options(**hudi_options) \
    .mode("append") \
    .save("/tmp/hudi/users")

# 读取
users = spark.read.format("hudi").load("/tmp/hudi/users/*/*")

# 增量查询(查询自某次 Commit 以来的变更)
spark.read.format("hudi") \
    .option("hoodie.datasource.query.type", "incremental") \
    .option("begin.instanttime", "20250101100000") \
    .load("/tmp/hudi/users")

# 时间旅行
spark.read.format("hudi") \
    .option("as.of.instant", "20250101100000") \
    .load("/tmp/hudi/users")

# 删除
deletes = spark.createDataFrame([(2, "2025-01-01")], ["id", "partition_path"])
deletes.write.format("hudi") \
    .options(**{**hudi_options, "hoodie.datasource.write.operation": "delete"}) \
    .mode("append") \
    .save("/tmp/hudi/users")

Flink 流式写入 Hudi

sql 复制代码
-- Flink SQL
CREATE TABLE users_hudi (
    id INT PRIMARY KEY NOT ENFORCED,
    name STRING,
    age INT,
    partition_path STRING,
    updated_at TIMESTAMP(3)
)
WITH (
    'connector' = 'hudi',
    'path' = '/tmp/hudi/users_flink',
    'table.type' = 'MERGE_ON_READ',
    'hoodie.datasource.write.recordkey.field' = 'id',
    'hoodie.datasource.write.partitionpath.field' = 'partition_path',
    'hoodie.datasource.write.precombine.field' = 'updated_at'
);

-- 从 Kafka CDC 流式写入
INSERT INTO users_hudi
SELECT id, name, age, partition_path, updated_at FROM kafka_cdc_source;

MoR 表与 Compaction

python 复制代码
# 创建 MoR 表
hudi_mor_options = {
    "hoodie.table.name": "users_mor",
    "hoodie.table.type": "MERGE_ON_READ",
    "hoodie.datasource.write.recordkey.field": "id",
    "hoodie.datasource.write.partitionpath.field": "partition_path",
    "hoodie.datasource.write.precombine.field": "updated_at",
    "hoodie.compact.inline": "true",
    "hoodie.compact.inline.max.delta.commits": "5"
}

df.write.format("hudi") \
    .options(**hudi_mor_options) \
    .mode("overwrite") \
    .save("/tmp/hudi/users_mor")

# 读取优化视图(Read-Optimized View,仅读 Base File)
spark.read.format("hudi") \
    .option("hoodie.datasource.query.type", "read_optimized") \
    .load("/tmp/hudi/users_mor/*/*")

# 触发 Compaction
spark.sql("CALL run_compaction(op => 'schedule_and_execute', table => 'users_mor')")

Apache Paimon

概述

Apache Paimon(原名 Flink Table Store)最初由阿里 Flink 社区于 2022 年孵化,2024 年从 Apache 孵化器毕业成为顶级项目。Paimon 的设计理念是原生流式湖仓------它从第一天起就为实时数据更新而设计,与 Flink 深度绑定,在分钟级甚至秒级延迟的流式摄入场景中表现出色。

2025 年,Apache Paimon 1.0 正式发布,标志着项目进入生产就绪阶段。

  • 最新版本:1.2.x
  • 开源协议:Apache 2.0
  • 主导方:Apache 软件基金会 / 阿里巴巴 / Ververica
  • 官方文档https://paimon.apache.org/

核心架构

Paimon 的架构设计兼顾流式写入和批式查询:

1. 表类型

表类型 说明 适用场景
Primary Key Table 支持行级更新/删除,基于 LSM-Tree 结构 需要实时更新的业务表
Append-only Table 仅追加,不支持更新 日志、事件流等不可变数据
Aggregation Table 自动聚合同一主键的记录 预聚合场景(如 PV/UV 统计)

2. Primary Key Table 的 LSM-Tree 结构

Paimon 的主键表采用 LSM-Tree(Log-Structured Merge-Tree)存储结构:

  • Level 0:新写入的数据,以小文件形式存在
  • Level 1+:经过 Compaction 合并的大文件
  • Changelog File:存储变更日志,供下游 Flink 任务消费

写入流程:数据先写入内存 Buffer → Flush 为 Level 0 文件 → 后台 Compaction 合并到更高层级

3. Changelog 机制

Paimon 原生支持 Changelog 产出,这是其区别于其他表格式的核心优势:

  • 写入操作自动生成 Changelog 文件
  • 下游 Flink 任务可以直接消费 Changelog,实现端到端的增量处理
  • 无需额外的 CDC 工具或 Change Data Feed 配置

4. 文件布局

复制代码
warehouse/db.table/
├── snapshot/
│   ├── snapshot-1
│   └── snapshot-2
├── manifest/
│   ├── manifest-list-1
│   └── manifest-file-1
├── bucket-0/
│   ├── data-0.parquet
│   ├── changelog-0.parquet
│   └── ...
└── bucket-1/
    └── ...

关键特性

特性 说明
原生流式设计 从底层为流式写入优化,秒级快照提交
LSM-Tree 存储 主键表基于 LSM-Tree,写入性能优异
Changelog 产出 自动生成变更日志,Flink 直接消费
Flink 深度集成 作为 Flink 原生 Catalog,零配置集成
多引擎读取 Spark、Trino、Hive、StarRocks 等均可读取
自动 Compaction 后台自动合并小文件,支持 Full / Incremental Compaction
Lookup Cache 读取时缓存热点数据,加速维度表关联
分区与桶 支持多级分区和桶(Bucket),优化数据布局

代码示例

Flink SQL 操作 Paimon(推荐方式)

sql 复制代码
-- 创建 Paimon Catalog
CREATE CATALOG paimon WITH (
    'type' = 'paimon',
    'warehouse' = '/tmp/paimon/warehouse'
);

USE CATALOG paimon;

-- 创建主键表
CREATE TABLE users (
    id INT PRIMARY KEY NOT ENFORCED,
    name STRING,
    age INT,
    updated_at TIMESTAMP(3)
) WITH (
    'bucket' = '4',
    'changelog-producer' = 'full-compaction'
);

-- 创建 Append-only 表
CREATE TABLE events (
    event_id STRING,
    event_type STRING,
    event_time TIMESTAMP(3),
    payload STRING
) WITH (
    'bucket' = '8'
);

-- 创建聚合表
CREATE TABLE user_stats (
    user_id INT PRIMARY KEY NOT ENFORCED,
    login_count BIGINT,
    last_login TIMESTAMP(3)
) WITH (
    'merge-engine' = 'aggregation',
    'fields.login_count.aggregate-function' = 'sum',
    'fields.last_login.aggregate-function' = 'max'
);

-- 流式写入
INSERT INTO users
SELECT id, name, age, updated_at FROM kafka_cdc_source;

-- 批式查询
SET 'execution.runtime-mode' = 'batch';
SELECT * FROM users WHERE age > 25;

-- 流式读取(消费 Changelog)
SET 'execution.runtime-mode' = 'streaming';
SELECT * FROM users /*+ OPTIONS('scan.mode'='from-snapshot') */;

Spark 读取 Paimon 表

python 复制代码
from pyspark.sql import SparkSession

spark = SparkSession.builder \
    .appName("PaimonRead") \
    .config("spark.sql.catalog.paimon", "org.apache.paimon.spark.SparkCatalog") \
    .config("spark.sql.catalog.paimon.warehouse", "/tmp/paimon/warehouse") \
    .getOrCreate()

# 批式读取
df = spark.sql("SELECT * FROM paimon.db.users")
df.show()

# 时间旅行
df_v1 = spark.sql("SELECT * FROM paimon.db.users VERSION AS OF 1")
df_v1.show()

# 写入 Paimon 表
spark.sql("""
    INSERT INTO paimon.db.users VALUES
    (1, 'Alice', 25, TIMESTAMP '2025-01-01 10:00:00'),
    (2, 'Bob', 30, TIMESTAMP '2025-01-01 10:00:00')
""")

Flink CDC 实时同步到 Paimon

sql 复制代码
-- 使用 Flink CDC 同步 MySQL 到 Paimon
-- 需要 flink-connector-mysql-cdc 和 paimon-flink-connector

CREATE TABLE mysql_users (
    id INT PRIMARY KEY NOT ENFORCED,
    name STRING,
    age INT,
    updated_at TIMESTAMP(3)
) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = 'localhost',
    'port' = '3306',
    'username' = 'root',
    'password' = 'password',
    'database-name' = 'mydb',
    'table-name' = 'users'
);

-- 同步到 Paimon
INSERT INTO paimon.db.users
SELECT * FROM mysql_users;

-- 或者使用 Paimon 的同步工具(整库同步)
-- flink run \
--   /path/to/paimon-flink-action.jar \
--   mysql_sync_database \
--   --warehouse /tmp/paimon/warehouse \
--   --database mydb \
--   --mysql-conf hostname=localhost \
--   --mysql-conf username=root \
--   --mysql-conf password=password \
--   --mysql-conf database-name=source_db

Compaction 与文件管理

sql 复制代码
-- 自动 Compaction(建表时配置)
CREATE TABLE users (
    id INT PRIMARY KEY NOT ENFORCED,
    name STRING,
    age INT
) WITH (
    'compaction.min.file-num' = '4',
    'compaction.max.file-num' = '16',
    'compaction.async' = 'true'
);

-- 手动触发 Compaction(Flink SQL)
CALL sys.compact('db.users');

-- 使用 Spark 触发 Compaction
spark.sql("CALL paimon.sys.compact(table => 'db.users')");

-- 过期快照
CALL sys.expire_snapshots('db.users', 2);

-- 回滚到指定快照
CALL sys.rollback_to('db.users', 1);

四大框架深度对比

核心能力对比

维度 Delta Lake Apache Iceberg Apache Hudi Apache Paimon
发起方 Databricks Netflix Uber Alibaba / Ververica
最新版本 3.3.x 1.7.x (V3) 1.0.x 1.2.x
设计理念 Spark 原生湖仓 引擎无关开放格式 增量处理优先 原生流式湖仓
事务日志 JSON + Checkpoint 三层元数据(Metadata → Manifest List → Manifest) Timeline(Instant-based) Snapshot + Manifest
行级更新 Copy-on-Write Position/Equality Delete File CoW / MoR LSM-Tree
流式写入 Spark Structured Streaming Flink / Spark Streaming Flink / Spark Streaming Flink(原生最优)
增量查询 Change Data Feed Incremental Scan 原生增量查询 Changelog 消费
引擎生态 Spark 为主,Trino/Presto 读取 Spark/Flink/Trino/Presto/Hive/Doris Spark/Flink/Trino/Presto/Hive Flink 原生,Spark/Trino/StarRocks 读取
元数据管理 _delta_log Catalog(Hive/Glue/REST/Nessie) Hive Metastore / Timeline Catalog(Hive/FileSystem)
分区演进 有限支持 完整支持 有限支持 支持
分支/标签 不支持 V3 支持 不支持 不支持
索引 无内置索引 无内置索引 Bloom/Simple/HBase/Bucket Bucket + Lookup Cache

写入性能对比

场景 Delta Lake Iceberg Hudi Paimon
批量写入 ★★★★ ★★★★ ★★★ ★★★
流式写入 ★★★ ★★★ ★★★★ ★★★★★
行级更新 ★★★ ★★★ ★★★★★ ★★★★★
Upsert 吞吐 ★★★ ★★★ ★★★★ ★★★★★
小文件合并 OPTIMIZE Rewrite Data Files Auto Compaction Auto Compaction

查询性能对比

场景 Delta Lake Iceberg Hudi Paimon
批量扫描 ★★★★ ★★★★★ ★★★ ★★★
点查询 ★★★ ★★★ ★★★★★ ★★★★
增量读取 ★★★ ★★★ ★★★★★ ★★★★★
时间旅行 ★★★★ ★★★★★ ★★★ ★★★★
多引擎查询 ★★★ ★★★★★ ★★★ ★★★

适用场景推荐

场景 推荐方案 理由
Databricks 平台用户 Delta Lake 原生集成,零配置,生态最完整
多引擎开放湖仓 Apache Iceberg 引擎无关,生态最广,V3 规范成标准
CDC 近实时摄入 Apache Hudi 增量处理原生支持,索引优化点查询
Flink 实时湖仓 Apache Paimon Flink 深度绑定,LSM-Tree 写入性能最优
批流一体 + Spark 为主 Delta Lake Spark 原生,Merge 语义强大
跨组织数据共享 Delta Lake Delta Sharing 开放协议
大规模分析 + Trino 查询 Apache Iceberg Trino 集成最成熟,元数据过滤高效
MySQL CDC → 湖仓 Apache Paimon / Hudi 流式摄入性能最优

技术选型建议

决策树

复制代码
你的主要计算引擎是什么?
├── 主要是 Flink
│   └── 实时性要求高?
│       ├── 是 → Apache Paimon(原生流式,LSM-Tree)
│       └── 否 → Apache Iceberg(引擎无关,生态广)
├── 主要是 Spark
│   └── 是否使用 Databricks?
│       ├── 是 → Delta Lake(原生集成)
│       └── 否 → 需要多引擎查询?
│           ├── 是 → Apache Iceberg
│           └── 否 → Delta Lake 或 Iceberg
└── 多引擎混合
    └── Apache Iceberg(引擎无关,生态最广)

实际考量因素

1. 团队技术栈

选择与团队最熟悉的引擎深度绑定的方案,降低学习成本。Spark 团队优先考虑 Delta Lake 或 Iceberg,Flink 团队优先考虑 Paimon。

2. 实时性需求

  • T+1 批处理:四个框架均可
  • 小时级延迟:Iceberg / Hudi / Delta Lake
  • 分钟级延迟:Hudi / Paimon
  • 秒级延迟:Paimon

3. 更新频率

  • 低频更新(每天几次):Iceberg / Delta Lake
  • 中频更新(每小时):Hudi / Iceberg
  • 高频更新(持续流式):Paimon / Hudi

4. 查询引擎多样性

如果需要 Spark、Trino、Flink、Hive 等多引擎同时访问同一张表,Iceberg 的生态支持最广泛。

5. 运维复杂度

  • Delta Lake:最简单,Spark 原生,但引擎绑定
  • Iceberg:中等,需维护 Catalog,但引擎灵活
  • Hudi:较复杂,MoR/CoW 选择、Compaction 策略、索引类型需调优
  • Paimon:中等,Flink 原生,但 Spark 生态支持相对较弱

互操作与统一趋势

UniForm:Iceberg 兼容层

Databricks 推出了 UniForm(Universal Format),使 Delta Lake 表可以同时以 Iceberg 格式暴露元数据,实现 Delta Lake 与 Iceberg 的互操作:

python 复制代码
# 创建支持 UniForm 的 Delta 表
spark.sql("""
    CREATE TABLE users_uniform (
        id INT,
        name STRING,
        age INT
    )
    USING DELTA
    TBLPROPERTIES (
        'delta.universalFormat.enabledFormats' = 'iceberg',
        'delta.columnMapping.mode' = 'name'
    )
""")

XTable(原 OneTable)

Apache XTable(原名 OneTable)是一个开源的互操作框架,支持在 Delta Lake、Iceberg 和 Hudi 三种格式之间进行元数据转换,无需重写数据文件:

bash 复制代码
# 将 Delta Lake 表转换为 Iceberg 元数据
java -jar xtable-0.1.0.jar \
    --sourceFormat delta \
    --tablePath /tmp/delta/users \
    --targetFormat iceberg \
    --outputPath /tmp/iceberg/users

统一 Catalog

Nessie / Gravitino 等项目正在构建统一的元数据目录,支持跨表格式管理:

  • Project Nessie:类似 Git 的数据目录,支持分支/标签/回滚
  • Apache Gravitino:统一元数据管理,支持 Iceberg/Hudi/Hive/MySQL 等

官方文档与资源

Delta Lake

资源 链接
官方文档 https://docs.delta.io/
快速入门 https://docs.delta.io/latest/quick-start.html
GitHub https://github.com/delta-io/delta
delta-rs https://github.com/delta-io/delta-rs
Delta Sharing https://github.com/delta-io/delta-sharing
版本兼容矩阵 https://docs.delta.io/latest/version-compatibility.html

Apache Iceberg

资源 链接
官方文档 https://iceberg.apache.org/
快速入门 https://iceberg.apache.org/spark-quickstart/
GitHub https://github.com/apache/iceberg
V3 规范 https://iceberg.apache.org/spec/
PyIceberg https://github.com/apache/iceberg-python
Nessie Catalog https://projectnessie.org/

Apache Hudi

资源 链接
官方文档 https://hudi.apache.org/
快速入门 https://hudi.apache.org/docs/quick-start-guide
GitHub https://github.com/apache/hudi
Flink 集成 https://hudi.apache.org/docs/flink-quick-start-guide
1.0 发布说明 https://hudi.apache.org/releases/release-1.0.0

Apache Paimon

资源 链接
官方文档 https://paimon.apache.org/
快速入门 https://paimon.apache.org/docs/master/quick-start/
GitHub https://github.com/apache/paimon
Flink 集成 https://paimon.apache.org/docs/master/engines/flink/
Spark 集成 https://paimon.apache.org/docs/master/engines/spark/

互操作工具

资源 链接
XTable (OneTable) https://github.com/apache/incubator-xtable
Apache Gravitino https://github.com/apache/gravitino
Project Nessie https://github.com/projectnessie/nessie

总结

数据湖仓表格式是现代数据基础设施的关键组件。Delta Lake、Apache Iceberg、Apache Hudi 和 Apache Paimon 各有侧重:

  • Delta Lake:Spark 生态的首选,Databricks 平台的原生格式,Merge 语义强大,适合以 Spark 为核心的团队
  • Apache Iceberg:引擎无关的开放标准,生态最广,V3 规范正在成为事实标准,适合多引擎混合架构
  • Apache Hudi:增量处理先驱,索引机制丰富,近实时摄入场景成熟,适合 CDC 和高频更新场景
  • Apache Paimon:原生流式设计,Flink 深度绑定,LSM-Tree 写入性能卓越,适合 Flink 实时湖仓

没有"最好的"表格式,只有"最适合的"。选择时需要综合考虑团队技术栈、实时性需求、更新频率、查询引擎多样性和运维复杂度。随着 UniForm、XTable 等互操作技术的发展,不同格式之间的壁垒正在逐步消融,未来有望实现真正的"开放湖仓"。

无论选择哪种方案,湖仓架构的核心价值是一致的:将数据湖的灵活性与数据仓库的可靠性融合,让企业用一套存储同时服务批处理、流处理和交互式分析,从根本上简化数据架构。