1. 数据架构演进:从数据仓库到数据湖再到 Lakehouse
做数据架构这十几年,我亲眼看着数据平台换了三代。每次演进都不是凭空冒出来的,说白了都是被业务逼出来的。今天咱们就聊聊这条演进路线,以及为什么 Iceberg 成了 Lakehouse 的关键拼图。
1.1 数据仓库时代:结构化数据的黄金年代
90 年代末到 2010 年左右,数据仓库是的主流。那时候我还在做传统 BI 项目,每天跟 Teradata、Oracle 打交道。数据仓库的核心思路很简单:先把数据清洗、转换、加载(ETL)到关系型数据库里,再让分析师用 SQL 查。
数据仓库的核心特征:
- Schema-on-Write:写入数据前必须先定义好表结构
- ACID 事务:支持事务隔离,保证数据一致性
- 高性能查询:通过索引、物化视图等手段加速分析
- 强一致性:数据写入后立即可见
但数据仓库有个硬伤——它只擅长处理结构化数据。我记得 2012 年有个客户想把服务器日志、点击流数据也放进去做分析,结果光是 schema 设计就折腾了两周。日志里字段经常变,今天多一个参数明天少一个字段,传统数仓根本扛不住这种灵活性需求。
数据仓库的局限性:
- 存储成本高:扩展需要购买昂贵的硬件
- Schema 僵化:字段变更需要停机维护
- 数据类型受限:半结构化、非结构化数据支持差
- 计算与存储耦合:扩缩容不灵活
1.2 数据湖时代:一切皆可存
2011 年左右,Hadoop 生态开始崛起。数据湖的概念应运而生——把所有数据以原始格式存起来,需要的时候再处理。这个想法很诱人:不用提前设计 schema,什么数据都能往里扔。
我 2014 年参与过一个数据湖项目,用 HDFS 存了上百 TB 的日志、图片、传感器数据。刚开始大家都很兴奋,觉得终于摆脱了 schema 的束缚。但很快问题就暴露了。
数据湖的承诺:
- 低成本存储:基于廉价硬件
- Schema-on-Read:读取时再解析数据
- 支持任意数据类型:结构化、半结构化、非结构化
- 计算与存储分离:独立扩展
然而理想很丰满,现实很骨感。数据湖很快就变成了「数据沼泽」。为什么会这样?我总结了几点:
| 问题 | 表现 | 后果 |
|---|---|---|
| 缺乏事务支持 | 并发写入导致数据损坏 | 数据不可信,需要反复校验 |
| 无 Schema 管理 | 数据格式混乱,字段含义不明 | 数据难以被正确消费 |
| 性能不可控 | 小文件过多,查询慢如蜗牛 | 分析师抱怨,业务方不满 |
| 缺乏数据版本 | 覆盖写入后无法回滚 | 数据质量事故频发 |
我曾经遇到过一个真实案例:两个 ETL 任务同时往同一个 HDFS 目录写数据,结果文件被部分覆盖,导致下游报表跑出来数据对不上。排查了整整三天,最后发现是并发写入没有锁机制。这种问题在数据湖里太常见了。
1.3 Lakehouse 架构:融合之道
2017 年左右,Databricks 提出了 Lakehouse 的概念。它的目标很明确:把数据仓库的可靠性和数据湖的灵活性结合起来。说白了,就是既要数据湖的低成本存储和 Schema-on-Read 的灵活性,又要数据仓库的 ACID 事务和高效查询。
Lakehouse 的核心思想其实不复杂:
- 底层用对象存储(如 S3、OSS、HDFS)存所有数据
- 上层加一层元数据层,管理表结构、分区、版本
- 提供 ACID 事务,保证并发读写的一致性
- 支持多种计算引擎,Spark、Flink、Presto 都能查
但问题来了:怎么在对象存储上实现 ACID 事务?对象存储本身不支持事务,也不支持文件级别的原子操作。这时候就需要一个「中间层」来管理数据文件的元数据和版本信息。
这个中间层,就是 Iceberg、Delta Lake、Hudi 这类表格式(Table Format)要做的事。
1.4 为什么需要 Iceberg?
市面上有多个 Lakehouse 表格式,我为什么特别推荐 Iceberg?这得从它的设计哲学说起。
Iceberg 的核心优势:
- 快照隔离(Snapshot Isolation):每次写入生成一个新快照,读操作只看到已提交的快照
- Schema 演化(Schema Evolution):支持添加、删除、重命名、重新排序字段,不影响历史数据
- 分区演化(Partition Evolution):分区策略可以随时改,不需要重写历史数据
- 隐藏分区(Hidden Partitioning):用户不需要手动管理分区,Iceberg 自动处理
- 时间旅行(Time Travel):可以查询任意历史版本的数据
我 2020 年开始在生产环境用 Iceberg,最直观的感受是:再也不用担心并发写入把数据搞坏了。Iceberg 的乐观锁机制保证了多个写入任务可以安全地同时操作同一张表。
举个例子,以前用 Hive 表做增量更新,需要先找到要覆盖的分区,然后整个分区重写。如果两个任务同时操作同一个分区,后提交的那个会把前一个的修改覆盖掉。Iceberg 通过元数据层的原子提交解决了这个问题——每个提交都是一个独立的快照,冲突时自动重试。
Iceberg 与其他表格式的对比:
| 特性 | Iceberg | Delta Lake | Hudi |
|---|---|---|---|
| 开源协议 | Apache 2.0 | Apache 2.0 | Apache 2.0 |
| 元数据存储 | 独立元数据文件 | 事务日志(Delta Log) | 时间线(Timeline) |
| Schema 演化 | 原生支持,无副作用 | 支持,但有限制 | 支持,但较复杂 |
| 分区演化 | 原生支持 | 不支持 | 有限支持 |
| 引擎兼容性 | Spark、Flink、Trino、Hive 等 | Spark、Flink、Presto | Spark、Flink、Presto |
| 文件格式 | Parquet、Avro、ORC | Parquet | Parquet、Avro |
Iceberg 还有一个让我特别喜欢的设计:它不依赖任何特定的计算引擎。元数据层是纯文件形式的,任何能读 Parquet 文件的引擎都能访问 Iceberg 表。这意味着你可以在 Spark 里写数据,在 Trino 里查,在 Flink 里做流处理——完全没问题。
1.5 架构演进全景图
下面这张图展示了从数据仓库到 Lakehouse 的演进路径,以及 Iceberg 在其中的位置。
1.6 小结
数据架构的演进,本质上是在「灵活性」和「可靠性」之间找平衡。数据仓库太死板,数据湖太混乱,Lakehouse 试图两者兼顾。而 Iceberg 作为 Lakehouse 的核心组件,解决了数据湖最头疼的问题——如何在对象存储上实现可靠的事务和数据管理。
我个人觉得,Iceberg 最厉害的地方不是某个单一特性,而是它的设计哲学:开放、标准、不绑定任何引擎。这意味着你不需要被某个厂商锁定,可以自由选择最适合的工具。
嗯,这一章就到这里。后面的章节我们会深入 Iceberg 的架构细节,看看它的元数据是怎么组织的,快照是怎么工作的,以及怎么在生产环境中用好它。