一次架构评审会上,有人提议“我们要不要上湖仓一体”,会议室里安静了几秒,然后大家开始各说各话:数仓负责人说“这不就是把数仓搬到数据湖上吗”,数据平台负责人说“我们已经在湖上做SQL了”,老板说“我关心这玩意儿到底能省多少钱”。这场面我后来见过很多次——湖仓一体(Lakehouse)这个词已经被讲了很久,但真正能把它的来龙去脉、技术组成、落地边界讲清楚的人,其实不多。
这篇打算作为系列的第一篇,把湖仓一体从头拆一遍:它到底补了数据仓库和数据湖之间的什么洞、底层靠什么技术撑起来、哪些业务适合现在迁、哪些业务最好别动,以及我在真实落地过程中踩过的那些不写进文档的坑。适合正在做数据架构选型的团队负责人、数仓工程师,还有那些被“湖仓一体”四个字搞得有点焦虑但还没想清楚要不要跟风的朋友。
1. 数据仓库和数据湖之间的裂缝:这个洞为什么一直没人补
1.1 仓库是精装房,湖是毛坯仓库,两者本来水火不容
先把这个问题的根源讲透。数据仓库这玩意儿,从上世纪八九十年代就有了一套成熟的方法论:先建模型、定口径、做ETL,然后把数据规规矩矩地装进结构化的表里,对外提供可信的报表和分析。它的好处是治理能力强、查询性能稳定、口径统一,坏处也很明显——存储和计算绑得死,扩展要加钱,存非结构化数据基本没戏。
数据湖是后来大数据生态成熟以后的产物。把原始日志、图片、文本、传感器数据一股脑丢进HDFS或者对象存储,存储成本极低,格式随便放,想用什么引擎来处理都行。听着挺美,但真实跑过的人都知道,数据湖用久了就变成数据沼泽:没有事务,多个人同时往里写数据经常互相覆盖;没有强Schema约束,字段类型说变就变,下游作业说挂就挂;数据文件越写越乱,查一条记录可能要扫几百个文件。
这两个体系的底层哲学是拧着的。数据仓库是“先建模再存数”也就是schema-on-write,数据湖是“先存数再定义”也就是schema-on-read。前者严谨但死板,后者灵活但容易失控。
1.2 过去十年解决这个矛盾的方式,代价都很大
在湖仓一体这个概念被炒热之前,业界想出来的解决办法无非两条路。
第一条路是搞两套平台——核心报表走数仓,探索性分析和原始数据走数据湖,中间用同步任务把数据拷来拷去。听着简单,实际操作起来全是泪:两套计算引擎、两套存储、两套权限体系,数据从湖到仓的同步链路一旦出问题,两边口径就对不上。我曾经见过某公司月底对账,数仓那边的订单量和湖里的订单原始记录差了万分之一,就这么个差异,数据组和业务组互相扯皮了整个星期。
第二条路就是Lambda架构,把一条数据链路拆成“批”和“流”两条并行通道,批通道保证准确,流通道保证时效,最后在查询层合并。这个架构能跑,但维护成本真的不低——同一套业务逻辑要在批和流两套框架里各写一遍,出问题的时候你还得想清楚到底是批那边错了还是流那边错了。很多团队后来把所有流任务干脆改成微批,本质上就已经在往简化方向走了。
这两条路的共同问题就是:架构复杂、人力消耗大、数据口径难统一。大家其实早就在盼着有一种方案,能把数仓的治理能力和数据湖的存储优势捏到一块,而不用在中间堆那么多胶水代码。
1.3 湖仓一体不是横空出世,是基础设施成熟后的必然产物
湖仓一体能在这几年落地,不是因为它突然被发明出来了,而是底层的基础设施终于跟上了。分布式计算引擎越来越成熟,表格式(Table Format)事务能力越来越稳,云上对象存储的带宽和延迟大幅提升,这些条件都到位以后,把数仓的“账本能力”下沉到数据湖存储层,才真正变得可行。
要理解湖仓一体,最朴素的一句话就是:给数据湖这个大仓库,装上货架系统和出入库登记簿。数据还是放在便宜的大仓库里,但每一笔写入都要记账、分区要管理、表结构要校验,读数据的时候能走货架索引快速找到东西,而不是翻遍整个仓库。
搞清楚这个定位之后,下面就得聊聊它底层到底靠什么把“账本”和“货架”这两件事做起来的。
2. 湖仓一体的底层逻辑:事务、元数据与开放格式怎么咬合
2.1 把数据库的事务能力“降维”到廉价文件系统上
湖仓一体最核心的技术挑战,是怎么在对象存储或者分布式文件系统这种“没有事务概念”的地方,让数据写入具备ACID能力。你往数据库里插一条记录,数据库自己就能保证并发安全;但在数据湖的存储层,文件写完就是写完了,没有回滚也没有隔离。
表格式(Table Format)就是为了解决这个问题而生的。它的思路不是加锁,而是用写时快照加日志的方式管理数据文件。每一批写入都会提交一个事务日志,记录“这次写了哪些文件、删了哪些文件、变更后的元信息是什么”。读数据的时候,只需要看最新的事务日志,就能确定当前应该读到哪些文件。如果事务失败,日志里没有对应记录,那批数据对读者来说就相当于不存在。
这种设计具体到实现上,大多走的是乐观并发控制——写之前先拿当前版本号,提交的时候检查版本有没有被别人动过,要是动过就重试。跟数据库的锁机制相比,这种方式牺牲了一点点冲突即时性,但换来了极大的吞吐弹性。在大数据场景下,几十上百个任务同时往一张表里写,用行级锁根本不现实,乐观并发就成了唯一可行的路线。
注意:事务能力不等于锁。很多刚接触湖仓一体的同学以为它跟MySQL一样并发安全,其实它在高频并发写同一张表时,依赖的是“检测冲突后重试”,所以写入任务的调度不能太野蛮。
2.2 元数据与表结构:从“目录即表”到“日志即表”
传统数据湖里,“表”其实就是一个目录约定,比如/data/orders/dt=2024-01-01/下面丢一堆Parquet文件。谁也不知道这个目录里到底有哪些列、字段类型是什么、文件是不是完整的,全靠下游引擎去猜。这就是“目录即表”的粗糙之处。
湖仓一体的表格式,把表结构、数据文件列表、分区信息、统计信息全部抽象成一组可版本化的元数据,并且把这组元数据跟事务日志绑定。表的每一次结构变更,比如加列、改类型、删除字段,都会作为一次事务记录在案。这样带来的直接好处就是Schema演进变得可控,加了列老文件不用改写,旧列删了历史快照还能读。
这个特性在实际工作中的价值太大了。没上湖仓一体之前,业务方加一个字段,数仓组得协调上下游做全量重刷,一不小心直接搞挂生产链路。上了之后,表结构演进的兼容规则由表格式统一管理,新写数据按新Schema,老文件按老Schema读,查询引擎自动做字段映射,很多过去要人肉协调的动作就省略了。
再加一个很有意思的能力:时间旅行(Time Travel)。因为每次写入都会产生新快照,你随时可以查“这张表三天前是什么样子”,哪怕数据已经被UPDATE或者DELETE过。这相当于给数据湖装上了时光机,对做数据回溯和审计来说简直是救命功能。过去想在普通数据湖上手动实现这个需求,得靠定时把全表快照备份到另一个目录,费时又费钱。
2.3 三个主流方案的共性,与一个很少有人讲的选择逻辑
目前市面上主流的开源表格式方案大致有三条技术路线,我在这里不展开一个一个介绍——那是后面几篇要写的内容,第一篇先把它们的共性讲清楚。
三条路线的共同点都是上面说的这套逻辑:事务日志、快照隔离、Schema管理、文件级粒度操作。它们的差异更多体现在并发模型、更新频率、生态兼容和工具链成熟度上。不同团队选型,不能光看GitHub star数和网上的性能测评,更要看你自己现有的计算引擎是哪些、团队熟悉什么语言、存算分离的要求有多高。
我见过一个团队,花了两个月把三套方案都跑了遍Benchmark,最后选了个网上评分最高的,结果一接入发现他们最常用的某套BI工具插件版本太老,连表都读不了。所以选型这件事,评估标准第一条永远是“跟你已有的生态吻不吻合”,性能反而是其次。
2.4 查询速度怎么保障:别让湖仓一体沦为“慢查询苦主”
湖仓一体把数据放到了廉价存储上,但随之而来的疑问必然是:查询性能能不能打?
说实话,纯靠表格式事务机制并不能带来查询加速,它解决的是数据管理和一致性问题。真正让湖仓一体查询变快的,是它上面还叠了好几个加速层。最基础的是统计信息与文件裁剪能力——表格式维护了每个数据文件的值范围和分区边界,查询引擎可以根据过滤条件下推,把扫描范围缩到极小。再往上走,还可以配合物化视图和智能缓存:对高频查询的中间结果做预计算,对热数据做多级缓存,把热点查询的延迟从分钟级拉回秒级。
我自己的体会是,湖仓一体在大多数分析场景下的查询速度已经可以跟传统数仓掰手腕了,但前提是你得会调。不会调的话,一个小文件膨胀就能把所有优化打回原形——这个问题后面专门讲。
3. 别急着上架构,先搞清楚你的数据资产长什么样
3.1 真正适合湖仓一体的三张“画像”
湖仓一体现阶段不是万能药,它适合的数据场景其实相当明确。我在跟团队交流的时候,一般会看三个条件。
第一个条件是数据种类杂、量大,但又有明确的治理诉求。比如一个平台既有结构化订单表,又有用户行为日志、接入设备的传感数据、客服的文本记录。这些数据用传统数仓装不下或者太贵,用纯数据湖管理又容易乱成一锅粥,这时候湖仓一体的“以湖为底、以仓为用”就特别对症。
第二个条件是想做流批一体,把实时和离线两条链路合并。从架构演进角度看,湖仓一体天然就是流批一体的载体——实时写入的数据落在表格式上,离线批任务处理的也是同一套表,两边共享同一份元数据,口径不再分裂。过去很多团队的实时报表和离线报表经常相差一个数量级,问题就出在两条链路的数状不一样,而流批一体在存储层就解决了这个矛盾。
第三个条件是团队规模不大,但又要支撑很大的数据底座。传统数仓需要维护模型、维护ETL、维护调度、维护查询加速,样样都要专人。湖仓一体把很多管理能力内置进存储层之后,小团队也能撑起不小的数据平台,不用为平台本身的运维付出太多人力。
3.2 什么样的情况,说实话别硬上
湖仓一体也不是越早用越好。有些情况我反而建议别急着上。
如果你的核心场景就是高并发的在线事务处理,比如订单交易、用户积分扣减,对单行读写延迟的要求是毫秒级,那湖仓一体完全不是菜,它做不到也不该做这件事,那是OLTP数据库的地盘。
如果公司数据量很小,几张Excel表就能装完,或者一个单机数仓就足够支撑所有报表,那湖仓一体引入的存储层、表格式、事务日志这些复杂度纯属自找麻烦。一个人维护东西越多,出问题的概率越大。
如果团队里没有人理解数据分区、文件格式、查询下推这些底层原理,那上线湖仓一体很容易变成把一个“能说会道的新平台”变成一个“每天深夜跑不完任务的祖宗”。平台永远替代不了人的判断力。
这里我必须说句扎心但真实的判断:湖仓一体解决的是“数据架构复杂度”的问题,不是“数据治理意识缺失”的问题。团队本来就没有数据Owner,没有统一的命名规范,上了湖仓一体只会把混乱放大得更有科技感。
3.3 数据资产成熟度自检清单
在决定上不上湖仓一体之前,我建议你带着团队回答下面几个问题:
- 当前的日增数据量是否已经超过数仓处理成本的忍耐阈值?
- 是否有多种数据类型(日志、文本、半结构化)急需统一分析,但数仓装不下?
- 实时和离线数据口径是否已经在互相打架,甚至业务方因此不信任报表?
- 团队是否愿意花至少一到两个Q的时间做底座改造,而不是下个月就要上线新报表?
- 现有计算引擎(如Spark、Flink等)是否已经基本稳定,不想再换血?
如果以上答案大部分是肯定的,那湖仓一体值得认真调研;如果都是否定的,那你大概率只是被行业热词感染了,还是先把当前的一亩三分地种好吧。
4. 落地湖仓一体时那些不踩一遍不会懂的坑
4.1 小文件膨胀,第一个把湖仓一体拖垮的元凶
这个坑几乎每个落地湖仓一体的人都会撞上,而且撞得相当痛。
小文件问题的本质是:表格式的事务管理是文件级别的,它要记录每个文件的元信息和统计信息,文件越多,事务日志越大,查询时要做文件裁剪也越吃力。而日常ETL任务一旦没控制好分区粒度或者写入频率,就会产生海量的小文件——几百KB一个,一晚上能写几万个。
我这里有个很典型的案例,某做在线教育的团队,把用户行为日志按秒级窗口做流式写入,分区按天,一天下来表里多了超过四十万个文件。结果就是:一周之后,原来秒级返回的报表查询变成了十几分钟起步,集群的NameNode压力直接拉满。
解决小文件问题有几个常规手段,首先是合理设计分区粒度,不要为了查询方便随意加分区列,分区粗细要在“裁剪效率”和“文件规模”之间取平衡;其次是使用自动合并(Compaction)机制,定期把大量小文件合并成合理大小的大文件,降低文件数量和元数据开销;最后是写入侧尽量做攒批,避免频繁低量级提交。
4.2 并发写冲突:任务多就像一群人抢同一支笔
湖仓一体用乐观并发控制,这意味着同一时间对同一张表发起多个写事务时,大概率有一批任务会因为版本冲突而失败重试。这个坑在数据平台上的表现就是:白天任务一切正常,一到夜间调度高峰,各种写入冲突的报错接踵而至,重试几次甚至直接卡死。
问题的根源往往不是表格式的缺陷,而是你压根不应该让那么多任务同时写一张表。很多团队的调度作业是你一个我一个,谁也没跟谁商量,全压在同一张业务大表上,能不冲突吗。
我的建议是把调度窗口错开,对同一张表尽量合并写入任务,如果一定要高频写,那就把表拆成逻辑隔离的分区,避免事务之间的粒度碰撞。还有个小技巧:任务失败之后不要无限自动重试,步骤化退避、最多重试两三次,剩下的留给人去查,比让系统盲目重试更可靠。
4.3 版本兼容矩阵,最容易被人忽略的隐性地雷
湖仓一体化的大盘子里,除了表格式之外还牵扯到查询引擎、写入框架、元数据服务,这几个组件的版本必须互相兼容。这块最坑的地方在于,报错往往不直接说“版本不兼容”,而是一堆看不懂的异常堆栈,或者是更恶劣的情况——平时没事,某天一个不起眼的语法的行为变了。
曾经有个团队升级了他们的查询引擎,结果发现历史分区里有些特殊字符的字段读出来是乱码,排查了两天才发现是老引擎在旧版本对某些类型做了隐式转换,新版本把这种行为改了,导致旧数据按新逻辑解析全部错位。
所以版本兼容不是“升级前随便看一眼”,而是要建立一张兼容性基线表,明确当前生产环境引擎版本、表格式版本、元数据服务版本三者之间的对应关系,任何升级都要先做小范围验证再全量推广。这块偷懒,后面迟早加倍偿还。
4.4 成本账,别只盯着存储单价
很多团队上湖仓一体的首要理由就是省存储成本。没错,对象存储确实比传统数仓的存储便宜很多,但湖仓一体的成本结构没那么简单。
存储成本省下来的钱,通常会流到计算、网络和元数据服务那边去。查询引擎跑的每一次任务都要付计算账单;湖仓一体的事务日志和元数据频繁更新,对应的服务资源也在吃钱;还有跨区域数据同步的带宽费用、备份快照的存储成本,七七八八加起来,总成本可能并不比原来低多少。
核算方式应该是算总持有成本(TCO),也就是把基础设施费用、运维人力、故障损失、业务迭代速度全部折算进去,然后再判断上湖仓一体到底划算不划算。不要被“存储成本降了80%”这种宣传话术带节奏,要看整体成本曲线和平台给业务带来的增量价值。
4.5 团队技能断层:平台先进了,人会跟不跟得上
这个坑不好意思写进项目总结,但真实发生的概率非常高。湖仓一体的运维方式和传统数仓完全不同——文件格式、事务机制、Compaction策略这些新概念,不是随便拉一个SQL工程师就能直接搞定的。
我见过不止一个团队,平台上线倒是很顺利,但真正出了性能问题,还是只有一两个核心同学能看明白事务日志和文件列表,其他组员只能在群里“帮忙艾特”。一两个核心同学一休假,平台运维就抓瞎。
所以落地之前,培训和知识转移必须排在任务清单的前面。至少要让每位平台运维同学理解事务快照、小文件代价、文件扫描和裁剪这几个核心概念,而不是停留在“表上写SQL”的旧有认知里。
5. 从零到湖仓一体,三步迁移路径供你参考
5.1 第一步:选一个业务域做试点,别一上来就全量迁移
不管是技术选型还是业务迁移,我都不建议搞“国庆大阅兵”式的一刀切换。挑一个数据链路相对独立、影响面可控、数据质量基础不错的业务域做试点,是风险最低的开局方式。
比方说你可以选“用户行为事件分析”这个场景,它的数据是独立的埋点日志,链路上下游都是自己团队能控制的,不会牵一发而动全身。先把这一条链路完整搬到湖仓一体的底座上,从采集、写入、合并、查询到报表输出全流程跑通,拿到明确的性能数据和成本数据,再拿这些数据去跟老板汇报“值不值得继续投”。
试点最大的价值不在于“上线成功”本身,而在于让团队以最低成本把从存储、表结构、调度到查询调优的整套操作手感建立起来。这个阶段踩的坑都是学费,全量迁移阶段要是还踩同样的坑,那就变成事故了。
5.2 第二步:双跑与对账,用数据说服所有人
试点跑通之后,不要马上把旧平台下线。新旧两套平台并行跑至少两到四周,把相同的分析任务分别在两边执行,然后逐表、逐指标比对结果。
这一步表面上是在对数据,本质是在对“口径”。很多时候你会发现,两边的数据差异并不来自于平台,而是来自于过去一直没有暴露的语义理解偏差——同样的“活跃用户”,一个平台按设备ID去重,另一个平台按用户ID去重,最后结果肯定不一样。这种问题过去被淹没在多层ETL链路里,双跑对账反而能帮你把口径梳理干净。
建议做一个对账清单,每个核心指标都标明两边任务启动时间、执行时间、结果行数、关键汇总值,一旦出现差异就停下来追踪到底,千万不要用“差不多”糊弄过去。双跑阶段,宁可慢一点,也要把所有差异消除在迁移之前。
5.3 第三步:逐步迁移,顺序比速度更重要
正式迁移的顺序,我建议按“离线批量优先、准实时其次、实时最后”的原则来做。
先把离线数仓里那些日批任务迁过去,因为它们对延迟不敏感、重跑成本低,就算出了小问题也不影响线上业务。接着把准实时的链路搬过去,比如每隔十几分钟调度一次的明细宽表计算,这类任务对一致性有一定要求,正好能检验湖仓一体的事务机制到底稳不稳。最后再考虑实时写入链路,因为实时链路的并发写压力最大,对事务冲突和延迟最敏感,适合等团队已经积累了足够操作经验之后再做。
每一步迁移完成之后,定一个观察期,至少等一个完整业务周期(通常是一周)的稳定运行数据,再启动下一步。稳扎稳打,比追求“三周完成全量迁移”这种表面速度靠谱得多。
结尾的一点个人体会
湖仓一体这些年在行业里被谈论得多,落地得也不少,但我最想提醒同行们的是:它不是买一套软件装上去就完事的“产品”,而是一种把数仓治理能力下沉到存储层的架构思路。上了湖仓一体之后,数据仓库并不会消失,只是它原先承担的那部分“强制治理”职责,被巧妙地转移到了更底层的开放格式里。真正需要重头适应的,其实不是技术,而是团队过去靠人肉堆出来的那些规范,现在要靠底座来自动约束了。
我自己做过好几个湖仓一体的落地项目,回头再看,最值钱的从来不是选用了哪家的方案,而是团队在迁移过程中把数据口径、分区策略、文件治理这些事情重新理了一遍。这个系列后面我打算接着写表格式方案的横向对比、常见查询引擎的适配经验,以及实时链路接入湖仓一体的具体踩坑记录。如果你正准备给自己的数据平台做一次这样的底层升级,先把这一篇消化透了,会比急着看新方案更有用。