湖仓一体架构解析:数仓架构选择(第48天)
最近在梳理数据架构演进相关的内容,正好走到"数仓架构选择"这个节点,想结合手头的一个实际项目聊聊湖仓一体(Lakehouse)这件事。这不是一个新鲜概念了,但真正能把"怎么选、怎么落地、踩过什么坑"讲清楚的并不多,我试着用自己的实际经历把这条线串一遍。
先说清楚背景。我之前在的那家公司,数据平台经历了从传统数仓到数据湖再到湖仓一体的完整迁移过程,我自己完整参与了第三个阶段。今天想写的这篇文章,核心就围绕一个决策:当数据规模、分析需求、实时性要求都上来了,老一套的架构撑不住的时候,到底该怎么选新的数仓架构?为什么最后选了湖仓一体而不是其他方案,以及落地过程中遇到的那些意料之外的坑。
这篇文章适合正在面临数仓架构升级、或者刚开始接触湖仓一体的朋友。如果你只是听说过这个概念、想知道它和传统数仓有什么区别、落地要花多大力气,那这篇应该能给你一个比较完整的参考。
1. 湖仓一体是什么,解决了什么问题
1.1 从一次真实的架构选型说起
事情起因很简单:当时业务方提了一个需求,说要把近一年的用户行为明细数据全部开放给算法团队做特征工程,同时数据产品那边还要求能在这份明细数据上跑一些即席分析查询,响应时间要求在秒级。这个需求放在五年前根本不算事,无非就是数据量大了点。但问题在于,当时的用户行为明细数据每天新增大概是2亿条左右,一年下来就是700多亿条,存到传统的MPP数仓里,存储成本和查询性能都撑不住。而如果放到数据湖里,虽然存储便宜,但查询性能又达不到产品侧秒级响应的要求。
这就是典型的"既要又要"的需求。再往前一步说,其实很多公司都会遇到类似的困境:数仓存明细数据太贵,数据湖查明细数据太慢。湖仓一体这个架构说白了就是奔着解决这个矛盾来的——用数据湖的低成本存储来接纳所有原始数据,再用数仓的查询引擎、治理能力和事务支持来保证分析性能和数据质量。
1.2 湖仓一体不是"数据湖+数仓"的简单拼接
很多人对湖仓一体有个误解,觉得就是把数据湖和数据仓库两套系统对接起来,平时数据放数据湖,查询的时候通过某种方式映射到数仓里。如果是这么简单,那这个问题十年前就能解决,根本不需要等湖仓一体这个概念出来。
真正的湖仓一体,核心在于存储层面只有一个湖存储,但是在这个存储之上直接构建了数仓级别的能力。具体来说,就是把事务支持、索引、元数据管理、权限控制这些原本属于数仓的能力,直接下沉到数据湖存储层。查询引擎直接读取湖上的数据文件,而不是先把数据拷贝到另一个存储系统里再查。少了这一道"搬运"的过程,才是湖仓一体和传统"湖+仓"拼接方案最本质的区别。
我记得当时调研阶段看到某个开源方案的时候,第一反应也是"这不就是一个数据库挂在对象存储上吗",后来真正理解了它的设计才明白,关键是它把原来数仓中最值钱的元数据管理和事务能力搬到了存储层,让多个不同引擎可以共享同一份数据,同时还能保证读的是一致的数据快照。这才是"一体"这两个字的含义。
2. 数仓架构演进路线与选型逻辑
2.1 传统数仓架构的痛点在哪里
传统数仓架构,和它配套的那套ETL流程、维度建模方法论,在很长一段时间里是数据分析的绝对主力。它的优势很明显:数据质量高、口径统一、查询性能稳定。但它有两个无法回避的问题。
第一个问题是扩展成本。传统MPP数仓在数据量到一定程度之后,扩容的成本几乎是线性上升的,而且很多MPP数仓的扩容操作伴随一定的业务停写窗口。业务量小的时候无所谓,数据量一旦上来,每次扩容都是一次伤筋动骨的大手术。
第二个问题是数据类型固化。传统数仓天生是为结构化数据设计的,往里面灌半结构化、非结构化的数据,虽然技术上能实现,但成本很高,查询性能也不理想。而这些年业务侧对非结构化数据的需求越来越多——日志文件、图片元数据、传感器数据、文档内容,都需要进入分析体系。逼着架构往更灵活的存储方向走。
2.2 数据湖的兴起和它留下的烂摊子
数据湖的出现,很大程度上就是为了解决传统数仓不够灵活、扩容太贵的问题。把数据以原始格式扔到廉价的分布式存储上,等要用的时候再按需解析。这个思路在存储成本上确实无敌,但也带来了新的麻烦。
最典型的问题是"数据沼泽"。存进去的东西越来越多,但没人知道里面到底有什么、质量怎么样、能不能信。因为没有统一的事务控制,可能出现"读到了写到一半的数据"这种恶心场景。数据管道跑着跑着失败了,留下的半成品文件既不能自动清理,也不能保证下一次读取时跳过。搞数据工程的朋友肯定都经历过"修补补丁式"的数据湖维护——今天修一个文件覆盖问题,明天补一个分区目录权限问题,后天再处理一个脏数据导致的查询报错。
数据湖本身没有对数据质量负责的意识。在实际使用中,有时候为了赶进度,直接把原始文件往湖里一扔、建个临时表就开始分析了,等分析完了发现数据有重复或者缺失,再回头排查的时候,要花费的时间远远超过当初直接走正规数仓流程的时间。
2.3 湖仓一体为什么能同时补上两边的短板
湖仓一体这个思路不算复杂:存储用数据湖那套,廉价、灵活、能存任意格式的数据;但在此之上,加上一套完整的元数据管理、事务控制、权限管理、索引优化能力。相当于让数据湖"长出了数仓的灵魂"。
我用一个比较粗糙的类比来说明。传统数仓像一个管理严格的图书馆,所有书必须按规范登记、编号、上架,借阅有记录,保管得很好,但藏书量受限于馆舍面积,增加书架很贵。数据湖像一个堆满货物的仓库,什么都能往里放,仓储成本便宜,但找东西基本靠记忆,货物质量没有保障,可能一箱货是完整的、隔壁一箱是碎的。湖仓一体则像一个用上了现代管理系统的智能仓库——货物随便放,但每一件都贴了电子标签、录入系统、有存取记录、库位自动管理,找货和管控都做得到。
这个类比能比较好地解释为什么湖仓一体在架构决策中会成为"最大公约数"方案:保留数据湖的灵活性,同时把数据治理做起来,业务团队既能低成本存数据,又能信任数据质量。
3. 架构选择的几个核心考量维度
3.1 数据规模和处理模式是决定性因素
如果数据量不大,每天新增几百万行、总量不超过几十亿行,那我建议老老实实待在传统MPP数仓里,没有必要为了追新概念去搞湖仓一体。湖仓一体的优势要在数据量足够大、需要低成本存储大量原始数据时才能体现出来。小体量场景下,引入这套架构只会增加维护成本,收益却微乎其微。
处理模式也很关键。如果业务主要是跑批处理,每天凌晨定时加工数据,对时效性要求不高,那传统数仓足够。如果业务需要对实时数据做分析,而且数据分析还要和全量历史数据做关联,湖仓一体的优势才真正放大。因为实时链路的数据可以直接以低成本落入湖存储,而查询引擎能从湖上同时读取历史全量数据和实时增量数据,不需要设计复杂的"实时数仓+离线数仓"双链路合并逻辑。
3.2 团队能力与运维成本要冷思考
这是很容易被忽视的一个维度。湖仓一体在技术概念上很美好,但落地运维的复杂度比传统数仓高一个级别。传统数仓基本上是一个"开箱即用"的黑盒,运维重点在SQL性能调优。湖仓一体则涉及存储、文件格式、表格式、查询引擎、事务机制多个层面的协同,出了问题要排查的环节多很多。
我们当时团队的情况是:三个人负责平台基础设施,两个数据工程师负责业务数据开发。说实话,湖仓一体引入初期,性能调优和问题排查的压力非常大。比如某个查询引擎对格式A的文件支持得很好,对格式B的支持还在实验阶段;比如某个表格式版本升级之后,老版本文件还可以读取但写入需要转型。这些细节散落在各种文档里,靠团队自己去趟。好在几个人都比较能扛,花了大概两个月时间把这些"隐藏知识"摸清了。
所以给一个建议:如果团队人数少于五个人、且没有专职的数据平台研发,那湖仓一体还是要谨慎评估,至少需要有一个人能愿意花大量时间吃透底层原理,不然上线之后遇到问题会非常被动。
3.3 业务场景的匹配度比技术潮流更重要
架构选型技术上再怎么分析,最后都要落到业务需求上。我总结了一套问题清单,每次选型前都会先过一遍:
- 业务方需要查询的数据,结构化的比例高还是非结构化的比例高?
- 数据时效性要求是分钟级、小时级还是天级?
- 是否需要对同一份数据同时跑批处理、流处理和即席查询?
- 数据的生命周期有多长,是否长期保留大量明细数据?
- 业务分析是否会经常修正口径,需要回刷历史数据?
这些问题如果答案主要集中在"非结构化比例高、需要廉价保留明细、需要多种计算模式",那湖仓一体基本是逃不掉的选择。如果答案都是"结构化、天级、批量跑批",那真的没必要折腾。
我见过一个失败的案例:某团队拍板上了一套看起来很前沿的湖仓一体方案,但实际业务需求就是每天跑一次常规报表,数据量也不大。结果不仅没有带来任何改进,反而因为链路复杂了,数据产出的稳定性下降了。这个事例能给我们的教训是——架构选型永远是为了适配业务,而不是为了技术而技术。
4. 实操过程与核心环节实现
4.1 存储层选型与布局规划
当时我们选定湖仓一体方案后,第一步是规划存储层的布局。因为底层的分布式存储直接决定了数据的可靠性和扩展能力,这一步选型选得好,后面能省很多心。
存储层选型主要考虑了几点:是否支持标准的对象存储接口,扩容是否平滑,是否有跨区域容灾能力,以及单位存储成本。对象存储基本是共识选择,但具体选哪个产品,不同公司有不同的考量,这里不展开品牌对比,关键是要注意一点——存储桶的目录规划要在初期就设计好。
湖仓一体架构下,存储目录的布局直接影响后续的数据管理和权限控制。我们当时的做法是把桶按照层级划分:raw层存放原始数据、clean层存放清洗后的数据、app层存放应用模型数据,每层之间不允许跨层读写。这个分层方式和传统数仓的ODS、DWD、ADS其实是异曲同工的——本质上都是将数据生命周期分成几个阶段,每层各司其职,便于追溯和治理。
操作层面有一个重要经验:文件分区策略要结合数据特征定。比如时间字段分区,先想清楚下游查询的过滤习惯是走天级还是小时级,分区粒度过细会导致小文件问题,过粗又会导致查询扫描数据量过大。我们最开始把用户行为数据按小时分区,结果因为每小时的文件太小,产生了大量小文件,后续不得不做合并操作。后来改成按天分区、再在表结构上用时间字段进行pruning,性能反而提升了不少。
4.2 表格式选择:这块要慎重
湖仓一体架构中,表格式是核心中的核心。常见的开源表格式主要有三种,各有侧重点。
一种格式在ACID事务和增量读取上做得比较完善,流读流写支持得很顺滑,适合构建实时的数据管道。它的优点是并发写控制做得比较好,多个任务同时写同一张表不会出问题;缺点是学习曲线有点陡,各种配置项很多,且与其他组件集成时偶尔有兼容性小坑。
另一种格式在数据文件管理上做了很多优化,比如文件的自动合并、列级统计信息管理,查询性能优化做得比较深入。它更偏向大数据量的分析场景,在离线批处理场景下表现稳定,但实时写入在早期版本中相对弱一些,需要依赖外部机制来支持增量处理。
还有一种比较轻量,主要作为前两者的补充存在,在一些特定场景下也有使用。
我们当时做了对比测试,选型标准和过程大致是这样的:先用同样的数据量分别装载到两种表格式下,然后跑同样的查询SQL,记录查询耗时;再进行并发写入压力测试,模拟多个数据管道同时写入之后查询数据的正确性。测试结果其实差别不算太大,最终决定性的因素是我们对实时数据管道的要求比较高——希望流式写入和批量写入可以同时操作同一张表而不产生冲突。所以选了事务支持更好的那一版。
说实话,表格式选型没有绝对的对错,关键看你的核心场景是偏实时还是偏批量。但如果让我重新选一次,我会考虑一个额外的维度:生态成熟度。不要只看单个组件的功能,要看它和周边生态系统的集成广度、社区活跃度、版本迭代稳定性。这一点的价值在使用过程中才能体现出来,初期评估很难感知到,但等你在生产环境遇到问题时,生态成熟的好处就变得非常明显了。
4.3 查询引擎的接入与调优
湖仓一体的存储层解决了"数据放在哪"的问题,查询引擎解决的是"数据怎么算"的问题。在架构中,可以做到存储与计算分离,一份数据可以由多种不同的引擎来访问,适配不同的计算需求。
我们在接入查询引擎的时候踩过一些坑,这里分享几个值得关注的细节。
第一个是文件格式的选择。虽然理论上查询引擎能读取存储上的各种格式,但实际性能差异非常明显。经过对比测试,对于大多数分析型查询,列式存储格式的性能优势极具压倒性。列式存储按列组织数据,分析查询只需要读取涉及到的列,IO量大幅减少。这一点在几百GB甚至上TB级别的表上体现得非常明显——可能差一个数量级。
第二个是统计信息采集。很多刚接触湖仓一体的开发者会忽略这个操作,觉得数据文件放上去就能查了。但如果没有统计信息,查询引擎的优化器就像蒙着眼睛做计划,经常选错执行策略。比如一个本来应该走Broadcast Join的查询,因为没有统计信息,优化器选择了Sort Merge Join,结果就是中间结果巨大、查询时间翻了几倍。采集统计信息是一个需要定期执行的维护操作,要像数仓里的Analyze Table一样养成习惯。
第三个是缓存策略。虽然存储与计算分离的好处是弹性伸缩,但每次查询都从远端存储拉数据,延迟和成本都扛不住。我们最后采用了分层缓存策略:把高频访问的热数据表缓存到本地节点,把中频访问的数据做部分文件缓存,冷数据不缓存直接读远端。从配置的JSON参数调整来说,其实并不复杂,但需要结合业务访问特征做一些反复实验和调优。
4.4 数据治理与权限管理的落地
湖仓一体解决了"能存、能算"的问题,但"能管"是另一个层面的挑战。尤其是数据湖原有的"野生"气质,如果不引入严格治理,湖仓一体最终还是会退化成一个有事务能力的"数据沼泽"。
我们当时在数据治理上做了三件事,可以说是最关键的三个动作。
第一件是建立统一的数据目录。所有入库的数据表都要注册到元数据管理组件中,包括表名、字段注释、负责人、数据来源、刷新频率、生命周期。强制规定:没有注册的表,不允许被业务查询。这个规定一开始被数据工程师吐槽"流程繁琐",但坚持了两个星期之后,效果就出来了——新人找数据不再需要到处问人,数据字典每个人都看得到。
第二件是血缘追踪。每次数据加工任务执行的时候,自动记录输入输出表之间的依赖关系。这个血缘信息在排查问题时特别有用——业务报表数据出了问题,可以沿着血缘往下游追踪,快速定位是源头数据出的异常,还是加工逻辑出了问题。血缘数据的维护成本其实不高,关键是架构设计时就把这套机制纳入考虑。
第三件是细粒度的权限管控。湖仓一体架构下,权限管理需要同时管到存储和表两个层级。单纯控制表权限是不够的,因为了解存储路径的人可能直接绕过表去读文件。我们当时的方案是:存储层做桶级别的权限隔离,表级别做行列级别的权限控制,两层权限同时校验,达到双重保险的效果。而且,权限策略要能支持按数据分类自动打标,比如敏感数据表自动限制只能由特定角色访问。
5. 常见问题与排查技巧实录
5.1 小文件问题,湖仓一体最大的隐形杀手
这是我第一次在生产环境被坑得最惨的问题,没有之一。湖仓一体的小文件问题比传统数仓要严重得多,因为底层是对象存储,文件写入天然是追加式的,而且实时任务频繁写入,会产生大量的碎片文件。
症状很典型:查询速度越来越慢,但是数据量并没有显著增长。我们用工具分析后发现,一张本来应该只有几百个文件的表,实际文件数高达二十多万个。查询引擎每次执行查询时,光扫描文件元数据就耗时很久,更别说真正读数据了。
解决办法无非是两种:一种是使用有自动合并能力的表格式,让系统在后台定期把小文件合并成大文件;另一种是手动合并,写一个定时任务,把小于某个阈值的文件读出来重新写一遍。两条路我们都走过,最终依赖表格式原生的优化能力来处理,手动合并作为兜底方案。
经验总结:新建表的时候一定要预估写入频率和文件大小,让文件大小控制在合理范围内。如果实在没法控制,那就接受"定期合并是一等公民"这个现实,别等到性能下降了再来处理。
5.2 流批一体"数据打架"的问题
湖仓一体的卖点之一是流批一体——流式任务和批式任务能同时处理同一份数据。但实际过程中,"打架"的情况时有发生。
最典型的一种场景:实时任务写入一张表,另一个批量任务也在同时清理这张表的过期分区,结果实时任务写了一半的数据被清掉了,数据对不上账。或者两个任务同时写同一张表,各自认为自己是对的了,结果产生了数据冲突。
这个问题的根源在于很多湖仓表格式在并发控制上并不像传统数据库那样强。我们最后的解法是严格控制写入模式:一张表只允许一种写入方式,要么全部是流式写入,要么全部是批量写入,避免混用。如果确实需要两个任务写一张表,那就把写入路径分开,先各自往中间表写,再通过一个整合任务做合并。
这里面还有个经验:不要过度迷信"流批一体"这个概念。它在设计上很美好,但在生产环境中要落地得平滑,需要付出额外的架构成本。对于大多数业务场景,我的建议是"能分开就分开,实在分不开才合"。
5.3 跨引擎查询的结果不一致
我们在实际使用中发现,同一个查询在不同引擎下执行,结果可能不一样,尤其在涉及浮点数运算、时间精度、字段空值处理这些细节时。这种不一致在单引擎的架构中根本不会出现,但湖仓一体天然多引擎,这个问题就暴露出来了。
排查思路很简单,就是在遇到结果对不上的时候,先对比两边的执行计划,看看是不是因为优化器的差异导致运算顺序不同。但治本的办法是:对核心指标口径做统一,在生成数据的时候就保证口径一致,让查询引擎只做简单的计算,不做复杂的隐式类型转换。
另外一个细节是时间字段的精度。不同引擎读取同一个时间戳字符串时,有的解析成秒级精度,有的解析成毫秒级精度,一对比就差了几个小时,看起来像数据错了,其实是精度截断的问题。解决方式是在建表规范里强制统一时间字段的格式和精度,写入端和读取端严格遵守。
6. 个人实操心得与后续规划
6.1 踩过坑之后形成的几条选型经验
这次湖仓一体架构落地,从决策到稳定运行大约花了四个月,中间踩过的坑、积累的教训不少。如果让我把核心经验浓缩成几句话,大概是这样的:
第一,架构选型不要太浪漫。《数据仓库》那本书里讲了各种理想架构,但实际落地时,业务、成本、团队、周期四个因素才是真正的约束条件。理想架构只有在这些条件都满足的情况下才是最优解,否则就要做妥协。
第二,数据治理要前置。湖仓一体的架构灵活性很高,但如果治理没跟上,灵活性就会变成混乱。我们因为初期把治理优先级放得比较低,后期补了很多课。现在回过头看,如果一开始就把数据目录、血缘、权限这三件事做好,后面能省一半的排查时间。
第三,选型测试不能省。我们花了两周时间做 PoC 测试,测试过程中发现的问题帮我们在架构设计阶段就避免了很多坑。比如某个存储库在某类并发场景下会概率性报错,遇到这类问题,测试就是唯一能提前暴露隐患的途径。
第四,团队学习曲线要提前安排。湖仓一体的技术栈跨度比较大,从存储、表格式到查询引擎,每一层都需要时间理解。如果团队之前没有底层的经验,建议先做一两个月的技术预研,别直接上生产。
6.2 湖仓一体的未来演进方向
这块是我个人的观察和一些设想,供参考。
湖仓一体的大方向不会变,但它会继续往"智能化"和"服务化"演进。智能化方面,自动化的数据治理会是一个趋势——比如依靠统计信息和查询日志自动识别"伪热表",把合并、压缩、缓存策略做成自动化决策,减少人工干预。服务化方面,湖仓一体会从平台能力走向数据产品能力,让业务分析师甚至业务运营人员可以直接通过自然语言交互来获取数据洞察,而不再依赖数据工程师写SQL、调管道。
我们目前已经在做的方向是:把报表开发中一些重复性的ETL逻辑沉淀成可视化组件,业务人员可以自助搭建数据管道,减少数据团队的日常工作量,把更多时间留给数据治理和模型优化。
6.3 最后分享一个小技巧
做湖仓一体性能调优的时候,最容易被忽略的检查项是——你的查询到底扫描了多少数据。很多时候SQL看起来写得没问题,但实际扫描的数据量是必要的几十倍。我建议在查询引擎的配置中开启执行计划可视化,先看每个查询的扫描分区数、扫描行数,再做针对性优化。
另外,遇到性能问题的时候,先看一眼底层文件的数量和大小分布,再回头看SQL。我们80%以上的性能问题其实都是存储文件层面的问题,而不是SQL写法的问题。处理好文件布局,比调SQL更一劳永逸。
这篇关于数仓架构选择的记录写到这里,基本把湖仓一体的选型思考、落地过程和踩坑经验都覆盖了。里面对架构选型的考量思路、技术细节的具体取舍,都是我个人的实践判断,如果你们团队的情况不同,具体决策可能也会不同。但有一点是一致的——任何技术架构都是为业务服务的,把业务需求理解透了,架构选型其实就成功了一半。