ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

数仓分层从入门到实战:ODS、DWD、DWS、ADS核心架构与落地指南

数仓分层从入门到实战:ODS、DWD、DWS、ADS核心架构与落地指南 1. 为什么非要做数仓分层前因后果与核心价值在数据开发这个圈子里数仓分层这四个字几乎每天都挂在嘴边。但说实话真正能把分层讲清楚、把每一层的边界划明白、把分层的价值落到实处的团队并没有想象中那么多。很多人只是照着别人的模板画了三层或者五层图实际开发的时候还是各写各的最后数据链路乱成一团。先说个场景。某个业务方跑过来跟你说我需要看一下最近三个月每天的新增用户数、活跃用户数和订单转化率。听起来很简单对吧你打开原始日志表然后开始写 SQL。刚写了不到五十行你发现要对 user_id 做去重要去掉测试账号要过滤掉爬虫流量要把 iOS 和 Android 的埋点口径统一要join 订单表算出转化率。写完这条 SQL 花了半天上线之后发现第二天数据对不上了因为上游埋点又加了一个新的事件类型你的过滤条件没覆盖到。这就是不分层的后果。每一个需求过来你都要从头开始面对这些脏活累活每一张报表、每一个指标背后都重复着一套口径复杂、逻辑混乱的加工过程。数据仓库分层本质上是把这种无序的、重复的、口径不统一的数据加工过程变成一条有序的、职责清晰的、可复用可追溯的数据流水线。这篇文章适合谁看刚接触数据仓库、被领导要求搭建数仓体系的新手写了两年 SQL 但没系统思考过分层逻辑的开发以及团队里需要推动数仓规范落地的技术负责人。读完你能搞清楚数仓为什么分层、经典的分层架构每层到底干什么、具体落地的时候有哪些细节坑以及小型团队怎么在自己有限的资源下做合理分层。2. 经典数仓分层架构每一层到底在做什么2.1 ODS 层源系统数据的忠实镜像ODS 层也就是操作数据存储层是整个数仓的源头。这一层做的事情很简单把各个业务系统、第三方平台、日志采集端的数据尽可能原封不动地同步到数仓的存储引擎中比如 Hive、Iceberg、Hudi 或者 ClickHouse 里。ODS 层的数据一般分成两类。一类是业务库数据通常通过 DataX、Flink CDC、MaxCompute 的同步任务把 MySQL、PostgreSQL、Oracle 里的表结构完整地搬过来。另一类是日志数据比如用户行为日志、服务器访问日志通过 Flume、Logstash、Kafka 或 SLS 等工具收集落成 Hive 表或者存成列式文件。ODS 层的核心原则是忠实同步、不做或少做加工。为什么要有这一层有三个原因。源系统的数据库不能随便查询你直接连生产库跑一个大 SQL可能影响人家的线上业务。数据格式和语义在源端是不稳定的今天埋点的字段叫 item_id明天可能改成 product_id你必须有地方先存一份原始快照才能追溯口径变化的历史。数据同步过程本身会有延迟、会有重复、会有失败这些都需要一个缓冲层来做容错和重试。实际操作中ODS 层的表用每天全量快照还是增量分区取决于源表的数据量、变更方式和业务需求。小表直接做全量每天一个分区简单粗暴。大表要做增量用 insert_time 或 update_time 来识别新增和修改的数据。这里有一个经验增量表最好额外加一个分区字段一个分区跑当日新增另一个分区跑当日更新这样后期排查数据对不上时能节省大量时间。另外每次同步任务跑完要记录 row_count 到一张调度日志表里这是之后数据质量校验的重要依据。2.2 DWD 层清洗与明细数据的战场DWD 层是明细数据层也经常被叫做数据清洗层。这一层是整个数仓分层里工作量最大、最考验功底的部分。DWD 做的事情是对 ODS 层的原始数据做标准化、清洗、脱敏、维度退化然后重新组织成面向业务过程的明细事实表。很多人会问DWD 和 ODS 的区别到底在哪里ODS 是源表的搬运工DWD 是源表的加工者。在 DWD 层你至少要做这几件事把公共字段统一比如日期格式全部转成 yyyy-MM-dd金额单位统一成元性别枚举值统一成 0/1/2做数据清洗过滤掉明显为空的订单号、金额为负数或者超出合理范围的异常值做完用户标识统一把匿名用户 ID、注册用户 ID、设备 ID 之间的映射关系理顺生成一个统一的全局用户标识。字段级别的脱敏也基本在这一层做比如把手机号、身份证号替换成脱敏字符串避免敏感数据继续往下游扩散。到这里“明细”两个字怎么理解在 DWD 层一行数据应该对应一个真实业务过程的最小粒度比如一条订单记录对应一笔订单的一次购买行为一条日志记录对应用户的一次点击。DWD 层不建议做汇总聚合一旦做了聚合明细就丢了下游想从别的维度分析就没有抓手了。当然如果你用的是星型模型会在 DWD 层按事实表和维度表分别组织事实表存业务过程的度量值维度表存描述性信息。DWD 层还有一个常被忽视的作用口径固化。这里说口径固化指的是把业务上容易混淆的统计口径通过代码和结构定型成一种固定的计算方式。比如有效订单怎么定义是支付成功且未取消的订单还是支付成功就算不同团队如果各写各的最后两张报表对不上业务方就要来质问。在 DWD 层统一完成清洗和过滤之后下游不管谁做分析拿到的都是一套已经加工好的明细数据。2.3 DWS 层主题与汇总的核心DWS 层服务数据层也有人叫汇总层、公共汇总层。这一层基于 DWD 层的明细数据按照业务分析的主题域做多维度的汇总统计。简单理解DWD 是数据的最细粒度DWS 是面向分析场景的轻度汇总结果。为什么要有这一层因为业务方看数据的时候大多数人不会说“我要每天每笔订单每个商品的明细”他们说“我要看看最近三十天每天的销售额趋势”、“每个省份的订单量排名”、“每个品类的退货率”。这些需求无一例外都是聚合查询。如果每次都从 DWD 层原始明细去跑聚合一次还好十张报表、二十个指标同时跑对整个集群的算力消耗是巨大的查询响应时间也会非常难看。DWS 层的价值就是把这种高频的聚合查询结果提前算好、固化下来。这一层的粒度通常是“维度组合 时间周期”的粒度。最典型的做法是建一张按天、按用户维度汇总的宽表包含用户当天的 PV、UV、下单数、支付金额、加购数等指标。再比如按天、按商品维度汇总的销售宽表包含销量、销售额、库存周转等指标。这样下游报表直接查 DWS秒级出结果。DWS 层的设计要跟着业务问问题的方式走。业务经常按用户维度分析你就建用户汇总表按地区维度分析就建地区汇总表按商品维度分析就建商品汇总表。这里有一个容易犯的错误试图让明细和汇总互相替代。汇总表替代不了明细表因为现场出了运营问题你需要钻取到具体哪条订单出了问题明细表也替代不了汇总表因为老是从明细跑聚合性能扛不住。职责不同各司其职不要合在一起。2.4 ADS 层应用数据层让报表和数据产品直接消费ADS 层应用数据层也叫数据应用层。这一层直接对接下游的数据消费方比如业务报表、数据大屏、用户画像系统、推荐系统的特征数据、算法模型训练样本等。ADS 是距离用户最近的一层也是需求变动最频繁的一层。ADS 层的数据形态比较自由经常按应用场景单独建表。比如针对一个经营管理驾驶舱你可能需要一张大宽表把日活、新增、订单量、GMV、转化率、客单价、退款率等几十个核心指标都放在同一行记录里。这种表字段非常多但每个字段都是DWS已经算好的结果ADS 只是做组合和排列查询时直接 select 出来效率极高。这一层还有一个特点就是容忍一定的冗余。为了减少查询时的 join 次数ADS 表经常会把指标值和对应的维度名称直接冗余在同一张表里甚至可以存在 Redis 或者 Elasticsearch 这类OLTP或检索引擎中而不是全部放在数仓引擎里。在实时场景中Flink 计算的实时指标结果也经常会落到 ADS 层的存储中供前台页面秒级刷新。ADS 层的命名要和应用场景强关联比如一句话需求标题是“管理层经营驾驶舱”ADS 表名可能就叫 ads_management_dashboard_daily。表名跟着场景走后期定位和维护会轻松很多。2.5 辅助层DIM 维表层与 TMP 临时层除了 ODS、DWD、DWS、ADS 这四条主线之外实际落地时几乎每个数仓还会有 DIM 维表层和 TMP 临时层。DIM 维表层专门管理维度数据比如用户维度表、商品维度表、门店维度表、日期维度表。维度表和事实表是星型模型的两个核心组成部分事实表存放的是度量值和关联维度表的外键维度表存放的是描述性属性。维表通常使用缓慢变化维策略来处理历史变化比如用户所在的城市从上海变成了北京你要决定是直接覆盖(SCD1)还是保留历史并新增一行SCD2。日常开发里90% 的维度用 SCD1 就够了只有像用户会员等级、地址信息这类需要追溯历史的场景才用 SCD2。TMP 临时层是给开发人员放中间结果的不需要长期保留。比如你在开发一个复杂的多层 SQL 任务中间步骤产生了很多中间表这些表生命周期短数据量大定期清理即可。很多团队会把 TMP 表用特殊前缀标识方便调度系统统一清理。小型数仓可以不单独建 TMP 层但大型团队最好有否则开发者会顺手把临时表放在 DWD 或 ADS 里破坏分层的纯净性。2.6 数据流转的完整链路和各层衔接关系把上面几层串起来一条数据从源端产生到最终被业务消费流程大概是下面这样业务数据库/日志 → 同步任务 → ODS 原始数据表 ODS → 清洗、标准化、维度退化 → DWD 明细事实表 DIM 维度表 DWD/DIM → 按主题聚合计算 → DWS 公共汇总表 DWS → 按应用组装、组合 → ADS 应用数据表 ADS → 被 BI 报表、数据大屏、画像服务、算法特征读取每层之间的衔接靠调度系统来保障。ODS 层的同步任务完成后DWD 层的清洗任务才能启动。DWD 完成后DWS 聚合任务才会运行。这就是为什么在调度平台上每条任务都要显式声明依赖上游任务而不是靠时间定时。如果只是定时前面同步延迟严重时后面任务用到的数据还是旧的整条链路上的报表都会出错。数据仓库分层搭配上严格的调度依赖才是真正可靠的体系。3. 分层落地实践从需求到建模再到开发全流程3.1 业务需求调研分层设计的第一步很多人设计数仓上来就画分层图画完就开始建表。这是本末倒置的。分层的最终目的是服务业务需求所以第一件事是搞清楚业务方到底要什么。这里分享一个我实际踩过坑后的标准动作。先收集业务方现在在用和未来半年想用的报表清单至少包含这些信息报表名称、使用部门、访问频率、需要哪些指标、指标的业务口径、支持哪些维度筛选、数据延迟的要求是 T1 还是实时。把这些信息整理成一张需求矩阵后再开始做主题域划分。主题域划分是什么意思就是把业务拆成几个大领域比如一家电商公司可以分成用户域、交易域、商品域、营销域、物流域、售后域。每个主题域对应一组业务流程和相关的事实表、维度表。这个划分直接决定了 DWD 和 DWS 层表的组织方式也决定了后续数据团队的协作分工。主题域不要划得太细一般中小型公司 5 到 8 个主题域就够太细反而会让表和表之间的关系变得更复杂。需求调研还有一个副产品就是确定指标的原子口径和衍生口径。比如“GMV”是所有支付成功的订单金额还是包含未支付订单的金额这就是原子口径必须在 DWD 层固化为统计条件。“客单价”是 GMV 除以支付用户数还是支付订单数这就是衍生口径应该在 DWS 或 ADS 层固定下来。口径统一这件事拖到后面肯定炸一定要在最开始就花时间理清楚并且沉淀成数据字典文档随代码一起维护。3.2 维度建模方法星型模型的实际应用分层架构确定后具体到每一层内部的表设计核心方法论就是维度建模。你不需要把 Kimball 那本书背下来但有几个核心概念必须掌握事实表、维度表、粒度、度量值。事实表记录业务过程比如订单事实表、支付流水表、用户行为日志表。粒度是事实表每行记录代表的最小业务单位比如订单事实表的粒度是“一个订单的一个商品子项”也就是说如果一个订单包含三件不同商品那这个订单会拆成三行不是一行。粒度定得越细事实表能分析的维度越丰富但数据量也越大查询也越慢。日常开发里事实表粒度要尽量贴合业务的最小原子事件。维度表提供描述性属性比如商品维度表包含商品名称、所属类目、品牌、上架时间等。维度表设计时要注意所有可能用于分析过滤、分组、下钻的字段都可以放在维度表里。但也不要一股脑塞几百个字段那些没人用的属性字段只会增加存储成本和维护成本。这里给一个实用技巧对事实表做宽表化设计的时候可以把常用维度的主键直接退化到事实表中。比如订单事实表只存 user_id但业务上 80% 的需求都要用户性别、用户城市、用户会员等级那在 DWD 层就直接把性别、城市、等级这几个属性字段冗余进来避免下游每次聚合都要 join 用户维表。这种提前冗余的代价是存储空间变大收益是查询性能和开发效率大幅提升在数仓场景里非常值得。实际上很多互联网公司的 DWD 层都是以宽表为主的这是规模化数据开发的标准做法。3.3 开发实操从 ODS 到 DWS 的 SQL 示例与细节看文字讲原理容易泛直接上一个实际开发中经常写的 SQL 过程更有说服力。以一个电商订单数据为例演示一条数据从 ODS 进到 DWS 的完整加工逻辑。ODS 层同步过来的订单表 ods_order_info包含字段 order_id、user_id、sku_id、order_amount、pay_amount、order_status、create_time、pay_time、province_id、is_test_flag。这是源系统里的样子没有做任何标准化处理。DWD 层写一条清洗 SQL要点如下INSERT OVERWRITE TABLE dwd_order_detail_di PARTITION(dt ${bizdate}) SELECT order_id, user_id, sku_id, CAST(pay_amount / 100 AS DECIMAL(10, 2)) AS pay_amount, -- 金额单位从分转成元 order_status, create_time, pay_time, province_id, CASE WHEN order_status finish AND pay_time IS NOT NULL THEN 1 ELSE 0 END AS is_valid_order, -- 固化有效订单口径 etl AS etl_source FROM ods_order_info WHERE dt ${bizdate} AND is_test_flag 0 -- 去除测试订单 AND pay_amount 0 -- 过滤金额异常 AND order_id IS NOT NULL;这是一条非常典型的 DWD 清洗逻辑统一金额单位、过滤测试数据、剔除异常记录、用 case when 把业务口径固化成布尔或标志字段。注意这里不做什么聚合保持一条订单一行的粒度。这是 DWD 的核心要求。DWS 层按用户日汇总的 SQL 长这样INSERT OVERWRITE TABLE dws_user_daily_aggr_di PARTITION(dt ${bizdate}) SELECT user_id, COUNT(DISTINCT order_id) AS order_cnt, COUNT(DISTINCT CASE WHEN is_valid_order 1 THEN order_id END) AS valid_order_cnt, SUM(CASE WHEN is_valid_order 1 THEN pay_amount END) AS valid_pay_amount, COUNT(DISTINCT sku_id) AS sku_cnt FROM dwd_order_detail_di WHERE dt ${bizdate} GROUP BY user_id;这条 SQL 按照用户维度做了按天汇总产出用户当天的下单数、有效订单数、有效支付金额和购买商品数。这张 DWS 表下游可以直接接报表也可以继续被 ADS 层用来做跨维度的过滤和排序。你发现没有到了 DWS聚合才开始出现这就是分层给开发节奏带来的最大好处每一层只关心自己职责范围内的事逻辑清晰bug 定位也块。3.4 调度依赖与数据质量校验分层体系的保障机制开发完各层表的任务只是完成了一半另一半是调度和监控。数仓分层最终能不能稳定运行取决于调度依赖是不是正确配置以及每层数据跑完后有没有自动校验。调度配置的核心原则是强依赖也就是只有上游任务成功下游任务才会启动。具体来说ODS 同步任务完成后立刻触发 DWD 清洗任务DWD 清洗任务成功后DWS 汇总任务开始执行DWS 完成后ADS 可以开始组装。在调度平台比如 DolphinScheduler、Airflow、DataWorks 中每条任务都要显式声明父任务 ID不建议使用定时调度代替依赖调度因为源系统同步的耗时并不是每天固定的。数据质量校验应该嵌入到每一层的调度流程中。ODS 同步完成后校验源表行数和目标表行数是否一致不一致直接报警。DWD 清洗完成后校验主键是否重复、核心指标字段是否有空值、金额合计是否在合理范围内。DWS 汇总完成后校验汇总值与明细去重后的值是否对得上这一步叫做明细对账是很多数据口径争议的终极裁决手段。建议在调度任务里增加一个质检步骤专门执行这些校验 SQL校验失败时阻止下游继续执行防止脏数据进入下一层。定期清洗 TMP 表和归档过期分区持续控制数仓整体存储成本。3.5 数仓分层常见问题与排查技巧实录在实际项目的推进过程中有几类分层相关问题我反复遇到这里统一整理一下方便你快速对照排查。第一类是 OD 层和 DWD 层职责不分的混乱。很多人开发时会顺手把清洗逻辑写进 ODS 同步 SQL或者在 ODS 就做了一些聚合动作。短期看省了一点时间长期看历史数据没法追溯因为源头已经被污染了。排查方法是看 ODS 表的加工逻辑如果出现 join、group by、case when 清洗等非搬运逻辑就该把逻辑挪到 DWD 层。第二类问题是 DWS 层过度汇总导致下钻能力丢失。比如 DWS 只做了按天汇总但业务临时想按小时分析你发现源数据的明细已经在 DWD 能查到但 DWS 没有小时粒度重新跑一批数据非常耗时。解决思路是多保留一份小时粒度的汇总或者按需增量计算。这是设计层面对业务需求理解不充分导致的。第三类问题是同一个指标在 DWD 和 DWS 各算各的口径对不上。比如有效支付金额DWD 层定义为一个字段DWS 又用了一版逻辑结果两张报表数值不同。发现这类问题的核心手段是明细对账对账的常规做法是从 DWS 汇总表随机抽几个维度组合用 DWD 明细重新聚合计算一遍比较两边结果是否一致。存档口径定义文档并跟代码一起走评审是预防这个问题最有效的手段。第四类问题是依赖关系配置不当。明明是下游任务却每晚固定时间启动上游数据同步一故障下游拿到的是上一批次数据还不报错。排查方式是看调度实例的启动状态与上游任务状态的先后顺序要求所有任务严格声明上游依赖。3.6 小型数仓怎么合理配置分层很多刚起步的团队一听到五层架构就直接被劝退了觉得自己的数据量又不大好像没必要搞这么复杂。这里我要说一个自己的真实看法小型数仓更要做分层但可以做简化版一般是 ODS、DWD、DWS、ADS 四层并且 ODS 直接使用源表同步DWD 只做必要的清洗和统一DWS 承接核心通用报表的汇总ADS 只针对特定应用场景建单个结果表。其实在这一步你已经跑通了整套分层体系。小型数仓不要做的不要一上来就建几十张 DWS 汇总表维度组合永远算不完做最常用的用户、商品、地区三个维度就够不要纠结缓慢变化维的复杂策略用 SCD1 覆盖足够不要搞一套看起来很重的元数据管理系统用 Excel 或者 Notion 维护一张数据字典就好。小团队最重要的是先把链路跑通把口径固化后面数据量涨了再慢慢丰富细节。我见过很多小团队一开始贪大求全结果运维成本把产出效率拖垮了分层的好处一点没享受到反而丧失了灵活敏捷的优势。根据团队实际资源量做取舍这才是分层落地的正确心态。4. 后续扩展与个人经验分享4.1 实时数仓场景下的分层设计从数据时效性这个角度看实时数仓的分层逻辑和离线数仓不太一样但整体思路是完全一致的。实时数仓也会分 ODS、DWD、DWS、ADS 这几层只是底层的存储和计算引擎换成了 Kafka、Flink、Doris、ClickHouse 这类具备流处理能力的组件。实时场景里Kafka 往往承担了 ODS 的角色原始埋点数据先进入 Kafka 的 topic Flink 做清洗和 join 之后落入 DWD 层的流式明细表。DWS 层由 Flink 的窗口聚合产出秒级或分钟级汇总结果最后在 ADS 层对接大屏、实时报警和在线服务。实时数仓不是要推翻离线数仓而是和离线数仓互为补充两条链路尽量复用同一套口径定义。实际操作中建议用一套统一的指标定义文件离线 SQL 和实时 Flink 作业都从这套文件生成这样能极大降低口径冲突的概率。4.2 我踩过的分层设计坑最后分享几个我亲测有效的小经验。第一个是不要在 ODS 之上再建一层备份表数据冗余只会增加存储成本数据重建靠原同步任务重新执行就可以实现。第二个是命名规范要发布当天就开始强制执行建表时省五分钟不写注释三个月后你可能要花五小时去猜这字段是干嘛用的。第三个是定期清理 DWD 层的无效数据分区不要出现一个日分区表结果三年前的分区还存在存储成本白白消耗。还有一个容易被忽略的细节是分层表和数据权限的联动。ODS 和 DWD 属于明细层数据敏感程度最高建议只有数据开发的核心成员可以访问。DWS 和 ADS 作为汇总层可以开放给数据分析师和业务人员。很多企业在这一块没有做区分导致用户手机号、身份证号这些敏感信息被到处访问。分层不仅是逻辑上的分层也应该成为物理权限的天然边界。建表以前提前规划好每个表的访问控制列表后续的权限治理会轻松得多。数仓分层这件事本质上就是通过合理的职责划分把复杂的数据加工过程拆解成多个可控的阶段让每一层都能被独立维护、独立优化、独立追溯。不管团队大小、数据量多少这套方法论都值得认真落地。希望这篇基于实际项目经验的分享能帮你少踩几个坑也欢迎在实际落地中多做一些适合自己的调整。
返回列表