最近总有人问我湖仓一体都这么火了那些传统数仓的四层模型是不是可以直接拆了说实话“拆”这个字本身就用错了。湖仓一体和分层架构从来不是对立的。数据仓库分层的价值在于把混乱的业务数据整理成可分析、可复用的资产湖仓架构解决的是“在数据量快速增长、实时需求变多、数据形态越来越复杂的前提下把分层这件事做得更便宜、更灵活、更快”。这也是为什么我更倾向于把两者的关系看成“从分层架构走向数据湖仓架构”的演进而不是后者对前者的彻底替换。下面我会把整个演进链路一层层拆开讲清楚每一层为什么存在、在哪里发生了变化、具体落地时该怎么操作。1. 数据仓库为什么一定要“分层”从业务场景倒推设计逻辑1.1 分层不是架构师的洁癖而是被业务需求逼出来的很多刚接触数仓的人会觉得分层是文档里的固定套路背下来就行ODS贴源、DWD明细、DWS汇总、ADS应用。实际上分层是被现实逼出来的。业务系统的数据库是为交易设计的表结构遵循范式模型索引按主键和常用查询配置。分析需求恰恰相反经常要全表扫描、要跨表关联几十个维度、要按任意时间窗口做聚合。把这种查询直接扔到业务库上轻则一条SQL拖垮库重则把面向用户的交易链路都给锁死。所以必须把数据搬出来单独为分析场景建一套数据组织方式分层的雏形就这么出现了。拿生活化的事情来类比业务库像柜台上的记账本分析需求像做财务年度报表。正常人不会直接翻开正在使用的记账本边算边给客户办业务而是先把账目抄录归档、做分类汇总形成专门的分析台账。数仓分层做的就是这件事。1.2 每一层解决一个具体问题从ODS到ADS的职责划分传统数仓最常见的是四层架构每一层都有明确的“使命”ODS操作数据存储层解决“数据能不能回溯”的问题。这层原样保存源系统的数据包括字段、类型、删除标记甚至变更日志尽量不做复杂清洗。作用是在出问题的时候可以回到原始数据重新加工也为将来无法预知的分析需求保留一手资料。DWD明细数据层层解决“这行数据可不可信”的问题。这层做格式统一、编码统一、口径修正、去重降维把不同业务系统的数据整理成一个规范的明细事实。比如“订单金额是否含税”“会员等级按什么规则定义”都在这一层沉淀成统一口径。DWS汇总数据层层解决“指标能不能复用”的问题。按业务主题构建宽表和轻度汇总比如按天、按门店、按品类汇总的订单表。通过预聚合下游报表直接查汇总结果就行不用每次都扫一遍明细。ADS应用数据层层解决“报表好不好查”的问题。面向具体应用输出定制数据集市字段冗余度高查询路径短性能表现好。四层逻辑可以用一张简单的职责表来对照分层核心问题数据形态典型场景ODS能否回溯原样贴源数据数据追溯、重跑修复DWD是否可信规范化明细事实自助分析、指标口径统一DWS能否复用主题宽表、轻度汇总标准报表、多维分析ADS是否好查应用专项数据集数据大屏、接口服务1.3 分层不是免费的它也有代价任何一个设计都有成本分层也不例外。每一层都物理落表导致存储冗余非常高。同样一份订单数据ODS有一份、DWD有一份、DWS又汇总了一份如果按年保留全量存储会成倍增长。同时层与层之间是强依赖的ETL任务链路上游哪一层失败下游全部阻塞。为了保障数据一致性调度系统还要做复杂的重跑和回溯运维成本直接拉满。这就不难理解为什么有人开始怀疑分层链路变长、时效变差、成本变高是不是砍掉几层反而更快答案是分层依然必要但传统“物理表任务依赖”的实现方式需要升级。这也是数据湖仓架构登场的根本原因。2. 数据体量膨胀之后传统分层架构真正扛不住的是什么2.1 存储成本与冗余三层全量副本叠加的扩容压力传统数仓面临的第一座山是存储成本。举个例子某业务每天新增1TB的订单明细和日志数据保留180天。在ODS、DWD、DWS三层各保留一份全量数据的情况下光这三份就是1TB×180×3约540TB。如果再加上副本、备份、临时表和一些测试数据冲到1PB以上很常见。传统MPP数仓大多采用“计算存储一体”的集群架构扩容必须把存储和计算一起加。数据量涨了CPU和内存也跟着被迫扩容成本非常高扩容周期还长。数据湖的优势在于把数据放到廉价的对象存储或分布式文件系统上计算和存储可以各自伸缩这个瓶颈直接被解开了。2.2 计算时效T1调度链路和实时需求之间的矛盾第二个扛不住的是时效。一条传统ETL链路由多个任务串联源库抽取、质检、进ODS、清洗生成DWD、聚合生成DWS、输出ADS。如果每个环节平均耗时20分钟整条链路端到端延迟就要100分钟起步。一旦某个环节因数据质量问题失败重跑又要几个小时。对纯离线报表来说可以忍对实时看板、实时风控、大促监控来说这种链路根本无法满足要求。说到实时化还有一个绕不开的痛点很多团队为了做实时指标会绕过分层另外建一套实时计算链路。两套链路同时跑口径还不一致比如GMV一个按“下单时间”算一个按“支付时间”算业务方同时看两个数字自然会对不上。这种“实时与离线口径撕裂”的问题比单纯的性能问题难解决得多。2.3 半结构化数据和建模灵活性的挑战传统数仓擅长处理结构化关系模型但现在的数据源越来越复杂服务日志、移动端埋点、设备上报的JSON、外部接口返回的嵌套结构都要进数仓。传统做法要求先建模、再导入数据落地前就得定义好schema。问题在于数据探索阶段往往根本不知道未来需要什么字段硬要提前定义就会反复改表结构效率极低。数据湖“先存储、后解析”的模式更适合这种场景却由此带来了新的管理问题这就该引入湖仓架构了。3. 湖仓一体架构的底层变革文件、表格式和计算引擎如何重新分工3.1 数据湖先解决了“存得下”却掉进了数据沼泽数据湖的初衷很直接把各种格式的数据低成本地放到一起将来再想怎么用。但实践一段时间后大家发现一个毫无约束的数据湖很快会变成“数据沼泽”。文件堆了一堆却不知道哪份数据和哪份数据有关联多个任务同时写文件可能覆盖或读到半成品数据没有事务机制同一张表并发提交时很容易脏读表结构也没有统一约定下游消费全靠猜。这些问题决定了单纯把数据扔进湖里是走不远的必须给数据补上“表语义”。3.2 表格式是湖仓架构的灵魂湖仓一体核心的底层创新不在存储层而在存储之上新增的“表格式”Table Format这一层。表格式不是新的物理文件格式而是在Parquet、ORC这些数据文件之上增加一层元数据和事务管理。它要回答几个核心问题哪些文件属于哪张表的哪个快照表结构是怎么演进的哪些快照可以回滚哪些文件是增量提交的当这些问题都有了明确答案数据湖才真正拥有了接近数仓的“表能力”。做个类比没有表格式的数据湖像把照片直接堆在网盘文件夹里要找到某一张只能靠文件名有了表格式等于给照片加了一套带版本管理的相册每次增删改都有记录每一版相册状态都能还原。3.3 三种主流表格式的选型对比Delta、Iceberg、Hudi目前主流的表格式有三类Delta Lake、Apache Iceberg、Apache Hudi。很多团队会在选型上纠结我给一个相对客观的对比维度Delta LakeApache IcebergApache Hudi生态主导Databricks生态Spark集成最顺开源中立跨引擎支持广社区驱动偏实时增量更新元数据实现事务日志Transaction LogManifest清单结构Timeline时间线文件组ACID事务支持支持快照隔离支持基于时间线管理Schema演进支持支持支持增量读取支持支持增量视图最成熟典型优势与Spark深度结合大规模分析、统一底座流批一体、频繁upsert选型没有绝对标准答案主要看业务模式。如果团队深度绑定Spark和DatabricksDelta很顺如果要建一个跨Spark、Flink、Trino的公共数据底座Iceberg更中立如果核心诉求是分钟级更新、增量入湖、多版本更新Hudi的优势会明显。3.4 计算引擎与表格式的协作写一个增量入湖任务的真实样子很多人误以为引入湖仓就要把整套ETL重写其实不是。表格式兼容SQL和DataFrame API既有逻辑可以平滑迁移。以Spark写Hudi表为例// 使用Spark把清洗后的数据写入Hudi表 df.write .format(hudi) .option(hoodie.table.name, dwd_order_detail) .option(hoodie.datasource.write.recordkey.field, order_id) .option(hoodie.datasource.write.partitionpath.field, dt) .option(hoodie.datasource.write.precombine.field, etl_time) .mode(Append) .save(/warehouse/dwd/order_detail)这几个核心配置项要理解清楚recordkey是记录主键用来唯一标识一行partitionpath是分区字段控制数据落在哪个目录precombine是同一主键多条记录时的合并优先级字段比如取最新的etl_time。理解了这三点就基本掌握了数据入库的语义。读的时候同样简单以Iceberg为例SELECT dt, shop_id, order_id, order_amount FROM iceberg_catalog.prod.dwd_order_detail FOR SYSTEM_TIME AS OF 2025-04-01 00:00:00 WHERE dt 2025-04-01;这里读的是指定时间快照也就是时间旅行能力。传统数仓一旦数据被覆盖想看历史状态非常困难在湖仓里只是一条SQL的事。4. 湖仓时代的分层设计演进层级职责迁移与落地形态4.1 ODS层从“贴源表”到“入湖原样区”湖仓架构下ODS层的目标没变还是“原样保留、可回溯”但承载方式完全不同。传统ODS表要按源系统结构建库建表有明确的schema约束。湖仓的ODS则更像“原样入湖区”业务数据通过CDC写入Kafka再由Flink任务落到Hudi或Iceberg表尽量保留原始payload和关键元数据字段比如数据来源、采集时间、操作类型。后续真要重新解析随时可以基于原始数据再做一遍。这层落地时有一个很实用的细节给每个ODS表都加上“采集时间”和“操作类型”字段使这层存储天然支持流式增量写而不是每天全量覆盖。这样做的价值在于未来调整DWD口径时不需要回源系统重新抽数直接从ODS重放即可。4.2 DWD和DWS层从“物理固定表”到“增量构建视图”DWD层的核心变化是从“每日全量重刷”变为“增量upsert”。以前DWD表每天凌晨全量计算任务重、窗口长、失败后重跑代价大。湖仓场景下DWD表以业务主键比如order_id作为Hudi记录键每天只处理变化的数据新订单插入订单状态变更做更新。已经处理过的主键不需要再算一遍。任务耗时从几小时降到几十分钟效果非常明显。DWS层可以继续保留“物理汇总表”的模式每天增量更新一次也可以用物化视图的方式自动维护。如果报表分析比较灵活推荐把DWS设计成“基础汇总维度可扩展主题宽表”的组合基础汇总控制成本主题宽表提高灵活性。传统数仓里“物化视图”受限于引擎湖仓中表格式天然支持这类场景工程实现简单很多。4.3 实时分层流批一体让数据新鲜度从T1走向分钟级湖仓的分层不再把“离线”和“实时”分成两套体系而是通过流批一体把它们合到一张表上。比如用Flink流式写Hudi的MOR表每5分钟提交一批数据。下游要做“近乎实时”的报表直接读MOR表的读取优化视图看到的就是已提交的最新数据如果还要往下游的OLAP引擎同步状态则读增量视图只消费变更记录再做二次加工。整个过程可以提炼成一句话分层还在DWD还是DWD但已经不需要维护一条“实时中心”和一条“离线中心”了。4.4 一个可参考的完整湖仓分层落地形态结合前面几节的思路给出一套可参考的虚拟零售企业湖仓分层方案分区存储载体表格式时效性典型场景原始区对象存储分布式文件系统Hudi/Iceberg分钟级数据追溯、数据重放明细区数据湖目录Hudi MOR/Iceberg分钟-小时级自助分析、指标口径统一汇总区数据湖目录Hudi/Iceberg小时级标准报表、多维分析应用区Trino视图或者同步到OLAP引擎SQL视图/外部表秒级大屏、数据接口这只是一个常见形态不是唯一标准。比如应用区到底要不要同步到ClickHouse、Doris这类OLAP引擎取决于查询特征低频大范围分析放湖里没问题高频点查和复杂聚合还是交给专门引擎更稳。湖仓架构从来不排斥合适的专用组件。5. 从传统数仓迁移到湖仓的落地路线与实战经验5.1 迁移前先回答三个问题第一个问题到底想解决什么想降低存储成本、想提升时效、想统一口径、还是想支持更灵活的分析目标不同表格式和落地方案的选择完全不同。第二个问题团队掌握什么技能湖仓的日常运维离不开Spark、Flink如果团队只会写SQL迁移难度会大很多。至少要提前培养两三个能写Spark/Flink作业的工程师。第三个问题怎么才算迁移成功建议在项目启动前定义可量化的验收标准比如数据对账通过率必须达到100%、核心报表查询耗时下降多少、每日任务跑批耗时控制在多少分钟以内。没有验收标准就盲目迁移最后很可能两边都维护越来越乱。5.2 分四个阶段迁移的完整路线我实践下来比较稳妥的做法是分四步走每一步都有独立的验证点。第一阶段先把ODS层迁入湖仓。保留原有ETL逻辑只把目标存储从传统数仓表切换到Hudi或者Iceberg表同时保持旧链路并行运行。验证重点是比较两个链路写入后的数据条数和抽样字段是否完全一致。第二阶段改造DWD层增量更新。这步的关键是先给明细数据找到业务主键和增量时间字段比如订单表用order_id用户表用user_id加上updated_at。任务从全量扫描改成增量过滤只处理变化记录。跑通之后任务耗时会明显下降。第三阶段把DWS的计算逻辑下推到湖仓。原来的日任务里明细关联和聚合运算在结果表生成前集中完成改造后改为对湖表的新增分区执行聚合再按维度主键upsert进汇总表。这样既减少了重复扫描明细又避免了全表重建。第四阶段切换流量。低频报表先迁高频接口后迁。从传统数仓切到湖仓后要留出一到两周的新老并行对账期每天比对汇总值和异常记录数。连续一周稳定一致再逐步下线旧链路。5.3 那些文档里不会写的踩坑经验迁移和技术选型过程中有几个坑几乎每个湖仓项目都会遇到这里集中说一下。第一个坑是小文件问题。流式任务和频繁upsert会产生大量小文件导致文件系统元数据膨胀、查询性能下降。Hudi可以开启自动文件大小控制例如设置目标文件大小上限并定期执行Clustering或者Compaction任务合并小文件。Iceberg则可以通过rewriteDataFiles周期性整理。很多人一开始只顾着写数据忘了维护任务三个月后才发现表上挂了几十万个小文件查询慢到没法用。第二个坑是跨引擎读写时的元数据可见性。多个引擎共享同一张湖表时不是每次提交后其他引擎都能立刻看到新数据。像Trino这类引擎的元数据可能有一段时间的缓存需要配置合理的刷新频率或者在业务上错开大查询和写入高峰。否则会出现“明明任务跑成功了报表却看不到新数据”的情况。第三个坑是并发写冲突。Hudi和Iceberg对同一张表的多写者并发支持各有不同Hudi早期版本并发写经常冲突Iceberg依赖乐观并发重试机制。工程上的稳妥做法是同一张湖表尽量只保留一个写入任务如果必须要多任务写同一张表优先确保分区和主键范围不重叠。第四个坑是Schema变更对下游的影响。湖仓支持灵活的Schema演进但这不等于可以随意改类型、删字段。新增可空字段比较安全修改字段类型会影响历史快照删除字段更是会破坏下游所有消费方。实际项目里最好在ODS层保留最原始的字符串或JSON字段解析和类型转换放到DWD层做这样上游变更时不至于崩到下游。5.4 迁移这件事最后我的一点个人体会我个人在做这类迁移时有个坚持了多年的习惯新老两套链路并行跑至少两周起步每天做数据对账。不是简单看一眼总数而是按天、按业务维度、按关键指标交叉比对比如订单明细条数、GMV汇总、主键重复率、空值率这些都对上了再切流量。这套方法看起来很笨却是最可靠的。湖仓架构可以带来成本、时效、灵活性上的提升但它本质上还是数据工程分层逻辑不会消失只是换了一种更轻量、更实时的实现方式。谁能在“逻辑分层”和“物理实现”之间找到平衡谁就能真正吃到湖仓一体带来的红利。