ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

电商离线数仓四层模型:ODS到ADS的核心指标设计与实践

电商离线数仓四层模型:ODS到ADS的核心指标设计与实践

上一篇文章把电商分析平台最常见的数据接入链路跑通之后,我收到不少朋友问:原始日志攒了一堆,接下来到底怎么整理?为什么不能直接写SQL查?这篇是系列第2篇,我围绕离线数仓分层、核心指标定义和落地过程中踩过的坑展开讲。做平台不是装个调度、跑通几个脚本就完事,真正决定后期好不好用的,往往是你前面数据怎么组织、指标口径怎么定、离线链路怎么容错。这篇就顺着实际开发顺序,把电商分析平台离线数仓这部分讲透。

1. 为什么电商分析平台必须做数仓分层

1.1 原始数据直接用起来有多痛

先从最常见的情况说起。日志采集端把前端埋点、订单库binlog、商品库快照一股脑往Kafka里推,落进HDFS之后,如果你直接拿这些原始数据写分析SQL,前两周可能还爽,等到业务方要“最近30天各品类GMV同比”“大促期间新客首单转化漏斗”这类指标时,你会发现自己陷入三个麻烦:

第一个麻烦是源头字段一直在变。前端埋点今天加一个参数,明天改一个字段名,订单表结构也经常因为业务迭代新增字段。分析SQL每换一次字段就改一遍,整个平台的报表全部跟着碎掉。第二个麻烦是双方口径对不上。你统计的“支付金额”可能只是支付成功订单的实付金额,运营说的“GMV”可能包含下单未支付、包含了退款订单,甚至有些渠道还把取消订单也算进去。这个问题的根源在于原始数据没有经过统一语义层加工,每个人拿着原始字段各自理解。第三个麻烦是数据质量没人兜底。原始日志里有重复上报、时间字段缺失、枚举值越界,如果不做清洗,直接聚合出来的数字经常莫名其妙地多出一截。

所以我一直建议,电商分析平台第一件事不是急着写报表SQL,而是把原始数据到可分析数据之间的一整套加工过程拆成清晰的层次。这就是数仓分层。它本质上不是技术摆设,而是让“数据从产生到被消费”的过程有分工、有缓冲、有约束。

1.2 用备菜过程理解数仓分层

把数仓分层类比成厨房备菜比较直观。你从菜市场买回来的菜是“原始数据”:带泥、带根、大小不一,不能直接下锅。后厨把菜择干净、切好、分装成半成品,这一步是清洗加工;再根据菜单把半成品配成“鱼香肉丝料包”“番茄蛋汤料包”,这一步是公共主题汇总;最后客人点菜时后厨直接按料包下锅,快速上菜,这就是面向报表和应用的数据服务。

电商分析平台的离线数仓一般也这么分:ODS层放原始数据,DWD层做清洗和标准化,DWS层做公共汇总和指标加工,ADS层面向具体业务应用输出结果。每层只干一件事,上层依赖下层,但不跨层乱取数。这个结构的好处是:底层变化不影响上层,新增指标只在上层改,多处复用同一份加工结果,指标口径也有了唯一来源。

在电商场景里,数据链路通常是这样的:前端埋点和业务库binlog → Kafka → Flume落HDFS → Hive/Spark做ETL → 生成DWD明细层 → 进一步聚合到DWS层 → 最后写进MySQL、ClickHouse或OLAP引擎供报表查询。中间调度用DolphinScheduler或Airflow串起来。这套链路看着简单,但每一层怎么做、粒度怎么选、表怎么设计,都有不少细节。

2. 电商数仓的核心分层设计与表结构方案

2.1 四层模型怎么落地

先看ODS层。这一层基本不做业务加工,只做格式统一。Kafka里的数据落HDFS时需要按日期分区,比如/warehouse/ods/ods_order_info_di/dt=2025-06-01这样的目录结构。ODS表通常用外部表,因为它的元数据只是挂在HDFS目录上,删表不会删文件。表结构尽量和上游源表字段保持一致,但需要预留两个公共字段:etl_time记录抽取时间,source_type记录数据来源,方便以后排查链路问题。

DWD层是清洗后的明细层,也是整个数仓里最核心的一层。这里要做四类事情:字段拆解、枚举值标准化、脏数据过滤、维度退化。以订单表为例,ODS里的订单可能是个JSON字符串,或者一个字段同时塞了很多信息,DWD层要把它拆成结构化字段。用户终端类型,原始值可能是iphone、OPPO R11、pc_web,这层统一成iOS、Android、PC。时间字段也要统一格式,订单创建时间、支付时间、更新时间都要转成标准yyyy-MM-dd HH:mm:ss。这层还会把一些经常一起查的维度直接冗余进事实表,比如订单表里带上商品一级类目、二级类目、店铺ID,这样上层查询时不用反复关联十几张维表。

DWS层做公共汇总。电商平台日常看的核心指标,像GMV、支付金额、订单数、客单价、UV、PV,都要在这一层按统一口径预计算。比如“最近1天支付金额总量”“最近7天各一级类目支付金额”,这些结果存成宽表或者汇总表。DWS层的价值在于复用:同一个指标只加工一次,下游所有报表都引用这一层的结果。算过的人都知道,如果每个报表都从DWD层重新跑一遍大聚合,集群资源根本扛不住,而且多份代码算出来的数字很容易不一致。

ADS层是应用层的最终结果表,直接对接BI报表和接口。比如运营后台看到的大盘实时看板、商品分析页、用户分析页,背后查询的都是ADS层结果。这一层表不多,但对应的是一个个可以直接被前端消费的结果集,字段名、数据粒度都和最终展示对齐。

2.2 维度建模:一张星形模型解决大多数分析需求

电商数仓的建模我建议采用星形模型,不要一上来就搞雪花模型。星形模型的核心是事实表在中间,周围挂一圈维度表。事实表存度量值,比如订单金额、商品数量、优惠金额;维度表存描述信息,比如用户维度、商品维度、店铺维度。

在设计维度表时,最容易踩的坑是维度更新问题。用户会改昵称,商品会改类目,店铺会改经营类型。对于电商分析平台来说,我不建议把所有维度都做成缓慢变化维,那样查询要带一堆start_time、end_time、is_current条件,太繁琐。实际操作里,大部分维度采用拉链表,每天同步全量快照到DWS层;分析时如果只关心当前维度属性,直接关联最新快照;如果要做历史对比,再通过拉链表取对应时间点的状态。只有像商品类目这种历史追溯需求很强的维度,才需要真正做SCD2。

星形模型还有一个好处是SQL好写。业务分析师要查“6月华北地区手机类目销售额TOP10店铺”,只需要关联订单事实表、商品维度表、店铺维度表、区域维度表,过滤条件都落在维度表上,聚合函数落在事实表上,查询计划清晰,也容易走维度表过滤下推。

2.3 关键事实表怎么设计

电商平台最核心的事实表有三张:订单事实表、支付流水事实表、用户行为事实表。设计事实表时要先想清楚粒度,也就是每一行代表什么。订单事实表一行代表一条订单,如果一条订单买了多个商品,那粒度就变成订单+子订单。支付流水事实表一行代表一次支付操作,同一个订单可能分多次支付,也会出现一笔支付对应多个订单的情况。用户行为事实表一行代表一次用户行为事件,比如浏览、加购、收藏、下单。

字段上,事实表分三类。第一类是维度外键,用来关联维度表,比如user_id、sku_id、shop_id、category_id。第二类是度量字段,包括金额、数量、时长、次数等,这些是聚合分析的基础。第三类是退化维度字段,比如订单编号本身、下单时间、支付时间、所在省份,这些字段经常被用来过滤或分组,冗余到事实表里可以省掉大量join。

订单事实表我通常会加一个order_status状态字段,但这个字段在聚合时要小心:不同的业务场景对订单状态的要求不同。算“下单金额”可能包含未支付订单,算“支付GMV”必须限定已支付成功,算“退款率”又要把退款状态单独拎出来。所以设计时状态字段尽量保留原始枚举值,不要只存“有效/无效”这种拍平后的值,否则后面口径调整会非常痛苦。

3. 从数据接入到可分析的完整落地过程

3.1 Kafka Topic 与 HDFS 分区约定

很多团队在数据接入阶段没想清楚Topic怎么分,后面每个需求都要改采集配置,很被动。我的经验是Kafka的Topic按业务域分,而不是按数据类型分。比如订单域单独一个Topic,用户行为域单独一个Topic,商品域单独一个Topic。行为域内部再按事件类型用一个字段区分,而不是每种事件建一个Topic。Topic数量一多,消费者、分区、监控都会变得难维护。

数据从Kafka落到HDFS,我推荐用Flume或者原生Kafka Connect。如果公司已经上了Spark,也可以直接用Spark Structured Streaming做微批落地,但要注意至少每5到15分钟提交一次,别把落地任务做成实时流,否则HDFS上小文件会非常恐怖。落地时目录按dt和hour两级分区,比如/warehouse/ods/ods_user_trace_log_di/dt=2025-06-01/hour=10。这样即使当天数据迟到,也能通过补刷特定小时分区来修正,而不用重跑全天数据。

分区粒度是按天还是按小时,取决于业务对时效性的容忍度。对大促活动期间的实时大屏,可能需要5分钟级或1小时级数据,但这类实时场景我更建议单独走实时链路,不要混在离线数仓里。离线分析平台的数据延迟一般控制在T+1就已经满足95%的报表需求,只有针对特定高峰期才会临时加跑小时级聚合任务。

3.2 Hive ETL 清洗与去重实操

DWD层加工一般用Hive或者Spark SQL。我早期全用Hive,后来因为清洗逻辑越来越复杂,部分任务改成了Spark SQL,跑起来明显更快。给你一段常见的订单明细清洗SQL做参考:

INSERT OVERWRITE TABLE dwd.dwd_order_detail_di PARTITION (dt='2025-06-01') SELECT order_id, user_id, sku_id, shop_id, category_id, order_amount, pay_amount, coupon_amount, order_status, create_time, pay_time, CASE WHEN device_type IN ('ios', 'android', 'pc', 'h5', 'miniapp') THEN device_type ELSE 'unknown' END AS device_type, province_id, etl_time FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id, sku_id ORDER BY etl_time DESC) AS rn FROM ods.ods_order_detail_inc WHERE dt = '2025-06-01' ) t WHERE rn = 1 AND order_id IS NOT NULL AND sku_id IS NOT NULL AND create_time >= '2025-05-31 00:00:00' AND create_time < '2025-06-02 00:00:00';

这段SQL里做了三件事。一是把ODS层的多日数据做了窗口去重,每个订单明细只保留最新一条,避免上游重复推送造成数据翻倍。二是过滤掉关键字段为空的脏数据。三是把时间窗口做了前后各一天的冗余,这样次日凌晨跑批时,如果昨天最后几秒的订单因为日志延迟在当天才到达,也能被正确纳入前一天的统计,这是处理数据迟到问题的一个土办法。等后面调度稳定之后,可以把窗口收窄,但容错思路是一样的。

行为日志清洗更要注意事件枚举。如果不做白名单过滤,前端埋点里随便一个调试事件、测试账号事件都会进数仓。建议在DWD层拉一张事件字典表,只保留字典里的事件类型,另外把user_id为空、时间字段为空、或者请求时长小于0的异常记录全部丢进一个专门的问题数据表,方便后续反查。

3.3 DWS层聚合指标口径怎么定

DWS层的指标口径是电商分析平台的灵魂,这一步没对好,后面报表全白做。先列几个最容易打架的指标定义:

指标建议口径注意点
GMV用户提交订单且订单状态为“待支付/已支付/已完成”的商品总额不含已取消、已关闭订单
支付金额支付成功且支付时间小于统计周期末的实付金额同一订单多次支付只算一笔,退款后续单独扣减
订单数统计周期内生成的有效订单数量子订单要合并成父订单后再计数,否则件数和单数会混
支付买家数统计周期内产生至少一笔成功支付的去重用户数按user_id去重,不是按设备号
退款率统计周期内退款成功笔数 / 支付成功笔数周期归属以退款完成时间为准

DWS层的表设计可以分成两种。一种是“按维度的汇总宽表”,比如按天、一级类目、平台渠道、店铺ID,把订单数、支付金额、退款金额、买家数、件数全部塞在一行。这种宽表适合BI拖拽分析,避免每次查询都做多表join。另一种是“公共指标明细表”,比如把支付成功事实表加工成包含用户标签、商品标签、地域标签的明细宽表,供算法和深度分析团队使用。

实践里有个取舍:DWS层是尽量冗余宽表还是保持范式。我自己的体会是DWS层在电商数仓里可以大胆冗余,因为它的目标是“让查询快、让口径统一”,而不是“省存储”。宽表多占的那点存储,和每次报表查询省下的计算时间相比,性价比高得多。

3.4 结果导出与调度依赖

ADS层的数据通常要落到业务能直接访问的存储里,最常用的是MySQL和ClickHouse。MySQL适合放数据量在百万级以内的报表结果,比如每日TOP榜、运营看板;ClickHouse适合放明细查询、多维度即席分析,比如用户行为漏斗、商品流量分析。

写导出任务时一定要设计成“可重跑”。我见过很多团队傻乎乎地先DELETE再INSERT,结果任务失败后DELETE生效INSERT没生效,线上报表直接变空白。正确做法是:先把结果写入临时表,确认写入成功后再通过表名切换或者ALTER TABLE REPLACE的方式把老数据替换掉。MySQL里可以建ads_gmv_report_bak,写入完成后在事务里执行RENAME TABLE;ClickHouse里可以直接用INSERT INTO TABLE ... SELECT配合ALTER TABLE DELETE,但删除操作很重,更推荐按天分区用临时分区表再REPLACE PARTITION。

调度任务依赖也要考虑好。我习惯把DWD层的任务按业务域拆分,这样订单域的DWD任务失败不会影响用户行为域的加工。DWS层任务依赖DWD层完成,ADS层任务依赖DWS层完成,层与层之间通过DolphinScheduler的依赖节点控制。每个任务都要设置失败重试,我一般重试2次,间隔5分钟。还要给每个任务设一个超时报警阈值,超过预期运行时长及时告警,宁可早点介入排查,也不要让任务卡在某个stage上耗死集群资源。

4. 实操过程中最常见的几个坑及排查实录

4.1 时区与日期分区陷阱

离线数仓里最容易出诡异数据问题的就是时间。前端上报的日志可能是带时区的ISO时间,数据库里的create_time可能是北京时间,还有一些事件上报的是服务器本地时间。比如一个订单在2025年6月1日凌晨00:10支付,订单创建时间是2025年5月31日23:55,如果你按创建时间做日期分区,这个订单会被分到5月31日;但按支付时间分析时,它又属于6月1日。不同表对时间归属的定义不一样,最终的日报、月报对不上就是这么来的。

我的排查方式很简单:先看每张关键表的dt分区对应的业务时间字段到底是什么,用哪个时间字段控制分区,在表结构注释里写清楚。加工逻辑里时间转换统一用东八区,解析ISO字符串时用FROM_UNIXTIME(UNIX_TIMESTAMP(ts, 'yyyy-MM-dd'T'HH:mm:ss'), 'yyyy-MM-dd HH:mm:ss')这种写法,避免隐式转换。跨天数据归属问题,建议在DWD层加工时把订单的“业务日期”单独算一个字段出来,不要总依赖ODS层原始时间。

4.2 重复数据导致GMV翻倍

这是电商平台最常见的事故。有一次我们发现某天的支付金额比前一天突然涨了将近一倍,查了半天才发现是采集端在Kafka重推了一批订单binlog,消费程序没做幂等,导致同一批订单在ODS表里出现两份。很多团队以为“上游保证精确一次投递”就不会重复,实际上采集端重启、网络抖动、消费端手动提交位移都会造成重复。

解决重复不能只靠源头,必须在数仓链路里做兜底。最稳妥的方式是在DWD层加工时对业务主键做窗口去重,像我前面SQL里写的ROW_NUMBER() OVER (PARTITION BY order_id, sku_id ...)。对于订单类数据,主键就是订单号;对于支付流水,主键是支付流水号加订单号;对于用户行为日志,主键可以是user_id加事件时间加随机事件ID,这要求埋点侧尽量生成唯一的事件ID。如果数据源本身没有事件ID,就要在ODS落地时通过字段拼接生成一个代理主键,后续所有去重都依赖它。

4.3 HDFS小文件爆炸

小文件问题是离线数仓的隐形杀手。Kafka落HDFS时如果每5分钟写一个小文件,一天下来一个Topic就会有288个文件,多个Topic叠加,ODS层文件数量会非常惊人。Spark和Hive在读取大量小文件时,每个文件至少要起一个Task,文件一多,运行时间就会从10分钟变成40分钟甚至更久。我处理这个问题有几个固定手段。

第一,Flume落HDFS时把sink.rollInterval调大,比如每小时一个文件,同时设置hdfs.minBlockReplicas等参数,避免小于128MB的文件大量生成。第二,对ODS层的原始数据,如果保留周期比较长,可以在数仓固定的凌晨维护任务里对前一天分区执行小文件合并,比如用Hive的INSERT OVERWRITE ... SELECT把数据重新写一遍,并行度控制在spark.sql.shuffle.partitions=100以内。第三,DWD和DWS层写入时尽量用distribute by对分区字段做Hash分布,保证Reduce阶段每个分区文件数量可控。

4.4 数据倾斜拖垮整个任务

数据倾斜在电商场景里非常典型,尤其是聚合“头部店铺”“爆款商品”的时候。某头部商家的数据量可能是普通商家的几百倍,按shop_id做GROUP BY时,同一个Reduce任务处理特别多的数据,其他Reduce早就跑完了,整个Job就卡在那一个Task上。如果你在Spark UI里看到某个Stage有99%的任务都成功,但有一个任务一直跑很久,基本都是数据倾斜。

我的处理思路是两阶段聚合。先给原本的聚合键加一个随机前缀,比如shop_id后面拼一个0到9的随机数,先做一次局部聚合,然后再去掉随机前缀做二次聚合。SQL逻辑不复杂,比如:

SELECT shop_id, SUM(order_cnt) AS order_cnt FROM ( SELECT concat(shop_id, '_', cast(rand() * 10 AS int)) AS shuffle_key, shop_id, order_cnt FROM dws.dws_shop_trade_1d WHERE dt = '2025-06-01' ) t GROUP BY shuffle_key;

但两阶段聚合有个局限,它只适用于可加性指标的聚合,比如订单数、金额。如果你想在倾斜Key上做去重计数,比如统计头部店铺的独立访客数,双阶段聚合会重复计数,这时候需要改成两阶段COUNT(DISTINCT)或者用approx_count_distinct做近似去重。另一个更干净的办法是:把超过阈值的热点Key单独拎出来,走Map端过滤后分桶处理,普通Key正常聚合,最后结果合并。

4.5 指标口径不一致:业务线和数据线对不上

口径不一致是整个电商分析平台最让人头疼的坑。业务方在周会上说“这个月GMV是500万”,你的报表显示480万,两边谁都没错,只是定义不同。业务方把“下单即计入GMV”,你只统计“支付成功且未退款”,差出来的就是那一批下单未支付和已退款的订单。

从第一次接这种需求开始,我就坚持做一件事:指标字典。每个指标在DWS层加工时,必须在元数据里登记四样东西:指标名称、指标英文名、指标口径描述、对应的加工SQL位置。比如gmv描述写“统计周期内用户提交订单且订单状态为待支付/已支付/已完成(不含取消/关闭/退款)的实付商品总额”。这样新来的同学查指标时直接看字典,不会猜。业务方对数字有疑问时,也能拿着字典去对需求,而不是反复说“我们的GMV和你们算的不一样”。

我见过不少团队图省事,指标字典只写在PPT里,代码里没有,最后换个人就丢了。我的建议是把它做成数仓里的一张表,比如dws.dim_metric_defination,字段包括metric_code、metric_name、biz_definition、tech_definition、created_by,在建DWS表的同时同步维护这张字典表。人脑会忘,表不会。

5. 平台性能调优与资源复用

5.1 从文件格式到查询优化

电商数仓跑批任务跑得慢,很多时候不是集群不够大,而是文件格式和表设计拉胯。ODS层无论上游数据是什么格式,DWD和DWS层落地时一定要转成ORC或Parquet列式存储。列式存储的好处是查询只扫需要的列,比如你只算支付金额,没必要把一个十几字段宽表的全部数据读进来。ORC配合Hive的矢量化查询,扫描速度和压缩比都比TextFile好非常多。

表结构上,每个表都要设置合理的分区裁剪条件。我发现很多慢查询就是因为在SQL里没写dt分区条件,导致全表扫描。上游DWD表把dt写成2025-06-01这种格式,下游查询时也要统一用前缀匹配,避免dt in ('2025-06-01','2025-06-02')这种完全打散分区的情况。另外,如果一张DWS宽表经常按shop_id过滤,可以对shop_id做分桶,桶数选16或32,查询时直接定位到对应桶,减少大量扫描。

5.2 Spark参数调优不能照抄默认值

用Spark跑数仓任务,最忌讳的就是用默认参数跑所有任务。默认的spark.sql.shuffle.partitions是200,如果你的数据量不大不小,比如几亿行的DWD聚合,200个分区可能不够,导致每个Reducer处理数据量巨大;如果数据量很小,200个分区又会生成一堆小文件。

我自己的调法是这样:先估算任务输入的数据量,然后按每个Reduce任务处理300MB到500MB数据来算并行度。比如输入数据50GB,建议并行度设为50*1024/400≈128,那么spark.sql.shuffle.partitions可以设到150到200之间。spark.executor.memory和spark.executor.cores要配合着看,如果一个Executor分4个核,内存给8GB经常不够用,我一般按每个核2GB内存起步,任务重的话再往3GB调。开启动态资源分配后,还要设置最小的Executor数,避免凌晨没人跑任务时资源全部被抢占。

还有一个容易被忽略的点是spark.sql.autoBroadcastJoinThreshold。默认值是10MB,如果维表超过这个大小,Spark会把join转为sort merge join,速度会慢很多。电商维表比如商品维度表、用户维度表,往往都有几十MB甚至上百MB,这时候可以把阈值调大到512MB,同时确认维表文件格式是列式存储,能压得比较小。这样可以触发广播join,让每个Executor节点把维表加载到内存,省掉shuffle。

5.3 调度依赖与血缘管理

离线任务一多,最怕的是改了下游表结构,上游没改,或者DWD任务重跑了一遍,下游DWS没跟着刷新。DolphinScheduler这类调度系统里,我建议把工作流按“业务域 + 数据级别”来组织。比如订单域有一个DWD订单清洗工作流,它成功之后触发DWS订单聚合工作流,DWS成功之后再触发ADS报表导出工作流,每个工作流内部节点严格按顺序执行。这样哪天上游数据有问题,只需要重跑对应工作流,下游会因为依赖关系自动等待或重新触发。

血缘管理方面,很多团队用Excel记录表依赖关系,维护两天就乱。建议在Hive表属性里加上source_table和target_table字段,同时用脚本定期解析SQL里的来源表,自动生成血缘。刚开始不用搞得很复杂,至少能回答两个问题:某张ODS表被哪些DWD表消费了?某张ADS表出数异常影响哪几个报表?这两个问题能查清楚,半夜接到报警的时候就能快速定位影响范围。

6. 一些想提醒你的实操习惯

这些内容放到最后的用意很简单:工具和方案都好学,但真正让平台跑得稳的是开发习惯。我在带这个电商分析平台项目的过程中,有几个很小的习惯一直坚持,效果很明显。

第一个习惯是每个任务都输出执行日志摘要。Spark作业在跑完后把处理的总行数、异常行数、写入行数打印出来,DolphinScheduler的日志里就能看到。第二天对数据的时候,如果哪天的行数和前一天差太多,不用重新跑任务,直接看日志就能判断数据源是不是出了问题。别小看这个动作,它能帮你减少大量排查时间。

第二个习惯是设置“空结果报警”。很多任务失败是因为中间表被清空,导致下游INSERT OVERWRITE之后写入了0行,但任务没报错,线上报表直接变空白。我会在所有关键ADS任务后面加一个数据量校验节点,查询结果如果小于阈值,立即发报警并暂停后续流程。头几次会被误报打扰,但时间长了你会感激这个机制,因为它拦住了好几次生产事故。

第三个习惯是保持SQL的可读性。数仓SQL一定会很长,但拆分逻辑和合理的注释很重要。我会把“清洗规则”“指标口径”“去重逻辑”这三类关键点写成SQL注释,至少保证半年后自己回来看还能秒懂。团队里新同学接手时,也不用靠猜来理解这段SQL是为了解决什么问题。

我个人在实际开发中最深的体会是:电商分析平台的离线数仓,难点从来不在工具链本身,而是“把话说清楚”。数据结构理清楚、指标口径对清楚、依赖关系写清楚,平台自然就稳了。相反,如果一开始就急着写报表、抓数据,后面一定会花数倍的时间补课。这篇先把离线数仓分层和指标口径讲到这里,下一篇我会接着讲电商分析平台里的用户行为路径分析和漏斗模型,以及实时链路和离线链路如何配合。希望这些内容能帮正在做类似平台的朋友少踩几个坑,如果你在落地过程中遇到别的问题,也欢迎带着具体场景来聊,很多问题只有放到真实数据里才能看到解法。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进