写入性能对比:小文件合并、并发写入、分区策略
写入性能这块,说实话是很多团队选型时最纠结的地方。我见过不少项目,一开始选型拍脑袋,结果数据量一上来,写入就卡住了。今天咱们就掰开揉碎,看看 Iceberg 和 Delta Lake 在写入场景下到底差在哪。
小文件问题:两个表的“慢性病”
小文件多,是湖仓写入的头号杀手。你想想看,一个分区里几千个几 KB 的小文件,读起来要命,写起来也烦。
Delta Lake 的做法:它有个 OPTIMIZE 命令,能把小文件合并成大文件。默认情况下,Delta 写入时不会自动合并,你得手动跑或者设个定时任务。
-- Delta Lake 手动合并小文件
OPTIMIZE events_table
ZORDER BY (event_time)
我在项目中遇到过,有个业务每小时写入一次,每次就几百条数据。跑了三个月,分区里堆了 2000 多个小文件。查询从 3 秒变成了 30 秒。后来加了自动合并策略,才算稳住。
Iceberg 的做法:Iceberg 的合并机制更主动一些。它支持写入时自动触发 RewriteDataFiles,也能通过 WriteBuilder 控制目标文件大小。
-- Iceberg 设置目标文件大小
ALTER TABLE events_table SET TBLPROPERTIES(
'write.target-file-size-bytes' = '134217728' -- 128MB
);
核心差异:Delta Lake 的合并是“事后补救”,Iceberg 更倾向于“事前控制”。我个人习惯,如果写入频率高、数据量小,Iceberg 的自动合并能省不少运维精力。
| 特性 | Delta Lake | Iceberg |
|---|---|---|
| 自动合并 | 需手动 OPTIMIZE 或配置 Auto Compaction | 支持写入时自动触发 RewriteDataFiles |
| 目标文件大小控制 | 通过 spark.databricks.delta.targetFileSize | 通过 write.target-file-size-bytes 表属性 |
| 合并粒度 | 按分区或全表 | 支持分区、分桶、按文件组 |
| 并发合并 | 可能产生冲突,需重试 | 基于快照隔离,冲突较少 |
并发写入:谁更扛得住“抢”
并发写入,说白了就是多个任务同时往一张表写数据。这里两个表的实现思路完全不同。
Delta Lake 的乐观锁:Delta 用事务日志来保证并发安全。每个写入任务先读最新版本,然后尝试提交。如果别人先提交了,你就得重试。
我曾经踩过的坑:有一次 50 个并发流同时写 Delta 表,结果大量任务因为冲突重试,写入延迟从 2 秒飙到了 30 秒。后来加了写入分桶,才缓解了冲突。
Iceberg 的快照隔离:Iceberg 的并发模型更轻量。它基于快照隔离,每个写入任务只修改自己的文件,最后通过原子操作更新元数据。冲突概率比 Delta 低不少。
-- Iceberg 并发写入示例(Spark)
df.write
.format("iceberg")
.mode("append")
.option("check-location", "true")
.save("catalog.db.events_table")
为什么会这样?因为 Iceberg 的元数据是独立的 manifest 文件,写入时只追加新文件,不修改旧文件。Delta 的事务日志是单点,并发高了就容易“打架”。
我的建议:如果并发写入超过 20 个任务,优先考虑 Iceberg。Delta 在低并发下表现不错,但高并发场景需要额外做分区或分桶设计。
分区策略:写得好不好,全看分区
分区策略直接影响写入性能。分区太细,小文件爆炸;分区太粗,查询扫描量大。两个表的分区机制也有区别。
Delta Lake 的分区:Delta 支持静态分区和动态分区。静态分区你得自己指定值,动态分区会根据数据自动路由。
-- Delta 动态分区写入
df.write
.format("delta")
.mode("append")
.partitionBy("dt", "hour")
.save("/path/events_table")
Iceberg 的分区:Iceberg 支持隐藏分区(Hidden Partitioning)。你不需要在写入时指定分区值,Iceberg 会根据分区表达式自动计算。而且 Iceberg 的分区可以随时改,不用重写数据。
-- Iceberg 隐藏分区
CREATE TABLE events_table (
event_time TIMESTAMP,
event_data STRING
)
USING iceberg
PARTITIONED BY (hours(event_time), bucket(16, event_id))
关键区别:Delta 的分区是物理目录结构,改了分区策略得重写全表。Iceberg 的分区是逻辑的,改分区只是改元数据,数据文件不用动。这一点在业务变化频繁的场景下特别有用。
我个人的经验是,如果业务分区字段经常变,或者数据量在百 TB 级别,Iceberg 的隐藏分区能省下大量重写成本。Delta 的分区更直观,适合分区策略稳定的场景。
实战选型建议
说了这么多,到底怎么选?我整理了几个典型场景,你可以对号入座。
- 场景一:高频小批量写入(每分钟几百条)—— 选 Iceberg,自动合并能省心
- 场景二:大批量定时写入(每小时几 GB)—— 两个都行,Delta 的 OPTIMIZE 够用
- 场景三:高并发流写入(50+ 并发)—— 优先 Iceberg,冲突少
- 场景四:分区策略频繁调整—— Iceberg 隐藏分区优势明显
- 场景五:团队熟悉 Spark SQL—— Delta Lake 上手更快,生态成熟
一个小技巧:不管选哪个,写入前先评估数据量级和频率。我见过最惨的案例,每天写入 10 亿条数据,分区字段选错了,结果写入性能直接崩了。提前做个小规模压测,比啥都强。