从 0 到 1:我用 MaxCompute + Hologres + DataWorks 搭 AI 经营分析数据中台
先把话说死:经营分析难的不是 SQL,是口径。GMV 含不含退款、订单算下单还是算支付、跨天订单算哪一天、门店券和平台券算谁的成本------这些没对齐,后面模型再漂亮也是错的。数仓要干的,就是把口径钉死,把数据按时、按分区、按质量交出来。AI 只是这层之上的消费者。
三个产品的分工,我直接给结论:
- MaxCompute:离线仓。存得起、算得起、扫得起。ODS / DWD / DWS / DIM 主要落在这里。
- Hologres:在线仓。给 BI、给 API、给 Agent 问数。热数据和高频指标放这里。
- DataWorks:人干活的地方。同步、开发、调度、质量、服务,全在这一套里编排。
别把 Hologres 当 Hadoop,也别拿 MaxCompute 去扛 200 QPS 的看板刷新。混了,账单和延迟会一起爆炸。
如果你也在做类似项目,可以把我这篇当成一条可复制的路线:
开通并对齐账号 → 分层和命名钉死 → 业务库进 ODS → MaxCompute 做清洗汇总 → 该加速的用外表、该服务的导入 Hologres → DataWorks 把 DAG 和质量规则挂上 → 用数据服务把指标交给应用。
目录
- 背景:经营分析中台到底要什么
- 三个产品各自干什么,别混
- 账号、地域、工作空间,开工前先对齐
- 分层、命名、生命周期,先写进规范再建表
- 数据接入:业务库怎么进 MaxCompute
- MaxCompute 离线加工:从 ODS 到 DWS
- Hologres:外表加速 vs 内部表服务
- DataWorks 调度:把链路变成每天能跑的 DAG
- 数据质量:没规则就别发布
- 数据服务:给 AI 和报表一个稳定出口
- 权限、成本、监控
- 我踩过的坑
- 建议的实施顺序
1. 背景:经营分析中台到底要什么
我这套中台服务的场景很具体:老板和运营要看生意,算法和 Agent 要取数。两边问的问题不一样,底层表最好是一套。
业务侧常见的问题:
- 昨天全渠道实收多少,比上周怎么样
- 哪个门店、哪个渠道、哪类商品在拖后腿
- 投放花了钱,ROI 算得清不清楚
- 库存周转和滞销,别等月底财务才发现
AI 侧常见的用法:
- 自然语言问数(最终还是打到指标表 / API)
- 异常检测(日指标偏离基线)
- 归因和拆解(维度下钻,不是让大模型自己编数字)
- 给推荐 / 预测提供离线特征
所以中台要同时满足三件事:
| 诉求 | 谁来扛 | 时效 |
|---|---|---|
| 历史可回溯、口径可复算、成本可控 | MaxCompute | T+1,个别小时级 |
| 看板、问数、点查,秒级返回 | Hologres | 秒到亚秒 |
| 任务能排、出错能追、表能管 | DataWorks | 贯穿全程 |
第一版我没上 Flink。不是 Flink 不好,是经营分析 80% 的指标,T+1 或 10 分钟调度就够用。实时大屏、风控点查以后再加。这不是「理论上最优」,而是交付速度、口径稳定、运维人数三者平衡后的结果。
2. 三个产品各自干什么,别混
2.1 MaxCompute 是仓,不是服务
MaxCompute(以前叫 ODPS)做三件事就够了:存、算、分区管理。
适合放这里的:
- 业务库每日全量 / 增量快照
- 明细清洗后的 DWD
- 按天、按店、按品类滚起来的 DWS
- 维度表、码表
- 给算法用的离线宽表、样本表
不适合放这里的:
- 前端直接查、QPS 不稳定的接口
- 「点一下就出图」的自助分析(可以跑,体验很差)
- 需要主键更新、行级点查的在线业务
计费心里要有数:存储按量,计算按 CU 或按 SQL 扫描。分区裁剪没写好,一次 SELECT * 能把当天预算打穿。
2.2 Hologres 是服务层,顺便能加速仓
Hologres 走 PostgreSQL 协议,BI、JDBC、DataWorks 数据服务都能接。它和 MaxCompute 是亲兄弟,这点是我选它而不是单独再上一套 OLAP 的原因。
两种用法必须分开:
| 用法 | 数据在哪 | 适合什么 |
|---|---|---|
| 外表(Foreign Table) | 还在 MaxCompute | 即席分析、对账、偶尔下钻,QPS 低 |
| 内部表(Internal Table) | 拷进 Hologres | 看板、API、Agent 问数,要稳、要快 |
外表查询有缓存,MaxCompute 表更新后,Hologres 侧元数据有延迟(文档写的是大约 10 分钟内)。导入内部表之前,先 IMPORT FOREIGN SCHEMA 刷一下,别拿着旧分区当新数据。
内部表才有分布键、聚簇、bitmap 这些索引。外表没有。拿外表去扛高并发,是我见过最常见的误用。
2.3 DataWorks 是工位,不是引擎
引擎是 MaxCompute 和 Hologres。DataWorks 负责:
- 数据集成(离线同步、整库实时、全增量)
- 数据开发(ODPS SQL、Hologres SQL、Python、检查点)
- 调度运维(周期、依赖、补数据、告警)
- 数据质量(规则挂在节点上,失败能阻断)
- 数据服务(把 SQL 封成 API)
- 数据地图 / 血缘(后期治理用)
新项目资源组我直接买 Serverless 资源组,同步、调度、数据服务共用,按 CU 分配。旧的独享资源组还能用,官方已经不推荐新购了。
3. 账号、地域、工作空间,开工前先对齐
这一节最无聊,也最容易返工。地域选错、RAM 没授对,后面全是联不通。
3.1 开通顺序
按这个顺序来,少走弯路:
- 确认阿里云主账号,地域选定(后面 MaxCompute、Hologres、DataWorks 必须同一地域)。经营数据一般选离业务库近的,比如业务 RDS 在华东 2,大数据也放华东 2。
- 开通 MaxCompute。新项目建议打开 三层模型 (Project / Schema / Table)和 2.0 数据类型。Decimal、TIMESTAMP 这些,同步和 Hologres 外表都依赖 2.0。
- 开通 Hologres 实例。规格按「先能跑、再压测」买,别一上来就上很大的计算组。开发、生产尽量两个实例,至少两个 Database。
- 开通 DataWorks。标准版够用日常开发;要质量规则阻断、数据服务生产发布,专业版更省心。创建工作空间时绑定刚才的 MaxCompute 项目和 Hologres 实例。
- 买 Serverless 资源组,绑定工作空间,再绑 VPC。资源组要能访问 RDS / PolarDB 所在 VPC,否则同步任务连通性测试直接红。
3.2 RAM 别图省事
至少拆三个角色,别所有人用主账号:
| 角色 | 干什么 | 不要给什么 |
|---|---|---|
| 数据开发 | DataWorks 开发环境写 SQL、提交 | 生产表 DROP、RAM 管理 |
| 运维 / 值班 | 运维中心重跑、补数据、看日志 | 改表结构随便发生产 |
| 只读分析 | 查 Hologres 分析库、查数据地图 | 写任务、改同步 |
MaxCompute 用 ACL 或 Policy 控到 Schema 一级。Hologres 用 PG 的 GRANT SELECT ON SCHEMA ...。DataWorks 工作空间成员和引擎账号不是一回事,两边都要加。
Hologres 加速 MaxCompute,需要 Hologres 实例账号对 MaxCompute 项目有读权限。漏了这一步,外表一查就是权限错误,排查能浪费半天。
3.3 工作空间建议标准模式
DataWorks 工作空间用 标准模式(开发 / 生产隔离):
- 开发环境:
mc_biz_dev,随便试、随便 overwrite - 生产环境:
mc_biz_prod,只能通过发布过去
简单模式省事,三个月后一定后悔。任务直接在生产改,口径一改,昨天的报表和今天的对不上,你都不知道谁发的。
4. 分层、命名、生命周期,先写进规范再建表
表一旦铺开,改名比改代码疼。我现在的习惯是:规范先写成一页纸,建第一张表之前全员对齐。
4.1 分层怎么切
经营分析我用五层,不多不少:
| 层 | 放什么 | 加工原则 | 主要引擎 |
|---|---|---|---|
| ODS | 贴源,字段尽量原样,可加 etl 时间、源表名 | 不改业务含义 | MaxCompute |
| DIM | 商品、门店、渠道、用户、日历 | 缓慢变化按需拉链,大部分日全量即可 | MaxCompute |
| DWD | 一笔订单、一次支付、一条退款,事实已对齐口径 | 清洗、去重、统一编码、关联维度键 | MaxCompute |
| DWS | 按天 × 店 × 渠道 × 品类 的轻度汇总 | 可加可回滚,禁止在这一层发明新口径 | MaxCompute,热表同步 Hologres |
| ADS | 直接给报表 / API / Agent 的宽表或指标表 | 面向应用,允许适度冗余 | Hologres 为主,MaxCompute 留底 |
有人喜欢再拆一层 STG、一层 MID。团队小于 5 个数据开发,别拆那么细,调度依赖会把人拖死。
Hologres 官方也说过:查询引擎够快的时候,不必为每个报表堆一张 ADS。我认同,但有前提------DWS 粒度对、Hologres 资源够、查询模式可预期。经营看板里那 20 个核心指标,我还是物化成表。View 改口径快,高峰期扛不住。
4.2 命名,写死一套就别换
库 / Schema:
ods / dim / dwd / dws / ads / tmp
MaxCompute 三层模型下:
mc_biz_prod.ods.ods_trd_order_di
mc_biz_prod.dwd.dwd_trd_order_di
mc_biz_prod.dws.dws_trd_shop_nd
mc_biz_prod.ads.ads_ai_biz_overview_1d
表名约定:
{层}_{数据域}_{业务过程}_{刷新周期}
| 后缀 | 含义 |
|---|---|
_di |
增量,一天一份增量分区 |
_df |
全量快照,每天一份全量分区 |
_nd |
轻度汇总,按天分区 |
_1d / _td |
最近一天 / 截至当天累计 |
数据域我按经营分析常用的切:
trd交易usr用户itm商品mkt营销投放inv库存fin财务(毛利、费用,口径要跟财务一起签)chn渠道 / 门店
字段:
- 主键、外键用
xxx_id - 金额统一
DECIMAL(18,2),单位人民币元,别有的表分、有的表元 - 业务日期分区一律
ds STRING,格式yyyyMMdd - 技术字段:
gmt_create、gmt_modified、is_deleted、etl_time
分区字段用 STRING,别用 BIGINT。DataWorks 的 ${bizdate} 是字符串,Hologres 分区也更认文本 / DATE。类型一拧巴,同步映射、外表、补数据全遭殃。
4.3 生命周期,建表那天就带上
MaxCompute 分区表的生命周期按 整表 设,回收按 分区 走。某天分区过期被删,表还在。
我用的默认值(按你们合规要求改,别照抄年限):
| 层 | LIFECYCLE | 理由 |
|---|---|---|
| ODS 明细 | 90 ~ 180 天 | 源库自己有备份,仓里留够回溯和重跑 |
| DWD | 365 天或更长 | 口径对齐后的明细,重算成本高 |
| DIM 日快照 | 180 天 | 太久的快照很少有人翻 |
| DWS | 730 天 | 经营同比至少看两年 |
| ADS 底表 | 按应用,通常 365+ | 给算法的样本另算 |
| tmp | 3 ~ 7 天 | 不设的话,临时表会把存储吃满 |
sql
CREATE TABLE IF NOT EXISTS ods.ods_trd_order_di
(
order_id STRING COMMENT '订单号',
user_id STRING COMMENT '用户ID',
shop_id STRING COMMENT '门店ID',
sku_id STRING COMMENT 'SKU',
order_status STRING COMMENT '订单状态源值',
pay_amount DECIMAL(18,2) COMMENT '实付金额-元',
gmt_create DATETIME COMMENT '源库创建时间',
gmt_modified DATETIME COMMENT '源库修改时间',
is_deleted BIGINT COMMENT '源库删除标记',
etl_time DATETIME COMMENT '入仓时间'
)
COMMENT '交易订单-增量贴源'
PARTITIONED BY (ds STRING COMMENT '业务日期yyyyMMdd')
LIFECYCLE 180;
Hologres 内部表用 time_to_live_in_seconds,单位是秒。热数据留 90 天:90 * 86400 = 7776000。历史下钻走 MaxCompute 外表,别让 Hologres 变成第二个冷仓。
5. 数据接入:业务库怎么进 MaxCompute
5.1 先盘源,再开同步
经营分析最小闭环,我建议先吃这几张表,不要一上来整库 200 张一起进:
| 源表(举例) | 进仓表 | 同步策略 |
|---|---|---|
order / order_item |
ods_trd_order_di、ods_trd_order_item_di |
日增量,条件用 gmt_modified |
payment / refund |
ods_trd_pay_di、ods_trd_refund_di |
日增量 |
sku / category |
ods_itm_sku_df |
日全量 |
shop / channel |
ods_chn_shop_df |
日全量 |
user |
ods_usr_user_df |
日全量或增量,看量 |
| 投放日报(文件或 API) | ods_mkt_ad_cost_di |
日全量覆盖 |
维表日全量,事实表日增量。维表不大,全量省心,不用处理迟到变更。事实表走增量,条件必须是 源库有索引的时间字段。
增量 SQL 条件在 DataWorks 里写成:
sql
gmt_modified >= '${bizdate} 00:00:00'
AND gmt_modified < '${bizdate1} 00:00:00'
${bizdate} 是调度业务日期,默认 T-1,格式 yyyyMMdd。${bizdate1} 要自己在节点参数里算第二天,或者用 DATEADD。千万别写 gmt_modified >= NOW() - 1,补数据、重跑全部错。
有物理删除、源表又没有可靠 gmt_modified 的,别硬做增量,改日全量,或者上整库实时(Binlog)。
5.2 数据源和连通性
DataWorks → 管理中心 → 数据源:
- 加 MySQL / PolarDB,填 VPC 内网地址,不要填公网。
- 加 MaxCompute,绑定工作空间默认项目。
- 加 Hologres,填实例、库名、开发 / 生产分别配。
- 选 Serverless 资源组,点连通性测试。红了先查:安全组、白名单、RDS 是否允许资源组网段、跨 VPC 有没有对等连接。
RDS 白名单要放资源组弹性网卡的地址段。资源组扩容后 IP 可能变,用 安全组放行资源组所属交换机 比写死几个 IP 稳。
5.3 离线同步:一张表怎么配
数据集成 → 新建同步任务,单表离线即可覆盖第一期。
来源 MySQL,去向 MaxCompute。字段映射逐列看一遍,不要全靠自动:
- 源
tinyint(1)当布尔,进仓用 BIGINT,后面 SQL 里is_deleted = 1 - 源
datetime进DATETIME,不要先转 STRING 再转回来 - 金额字段确认单位,源库如果是分,ODS 可以原样留,DWD 再
/ 100。我更倾向 ODS 原样、DWD 统一成元,方便对账
目标表分区字段 ds,赋值 ${bizdate}。写入模式:覆盖分区 (insert overwrite 语义)。增量任务也覆盖当天分区,不要 insert into 往同一个分区里追加------重跑一次就双倍。
通道设置:脏数据阈值先给一个明确数字,比如 0 或 100。阈值设成 -1 等于放弃质检。
5.4 什么时候上整库实时
下面几种情况再上实时,第一期真没必要:
- 源表变更很频繁,日增量对不上
- 经营看板要「今天到目前为止」
- 有人已经在用 Binlog 做其他系统,你只是多一个订阅方
整库实时注意:
- MaxCompute 必须 2.0 数据类型
- 目标表建议带主键的 Delta 能力(按产品当前形态选 PK 表),否则 UPDATE / DELETE 落不干净
- DDL 策略先设成忽略加列以外的破坏性变更,避免源库 DBA 改个字段把同步打挂
- MySQL 要开 Binlog,
binlog_format=ROW,账号有REPLICATION权限
实时进仓之后,离线 DWD 仍然按天跑。实时链路给 Hologres 热查询,离线链路给对账和复杂汇总。两套口径必须用同一套代码或同一份指标定义,不然「实时数」和「T+1 数」会打起来。
5.5 文件和埋点
投放报表经常是运营丢 CSV 到 OSS。DataWorks 用 OSS → MaxCompute 离线同步,或者 MaxCompute 外部表直接读 OSS。
sql
CREATE EXTERNAL TABLE IF NOT EXISTS ods.ods_mkt_ad_cost_di
(
account_id STRING,
campaign_id STRING,
cost_amt DECIMAL(18,2),
impress BIGINT,
click BIGINT
)
STORED AS TEXTFILE
LOCATION 'oss://biz-data/ods/mkt_ad_cost/'
LIFECYCLE 180;
外部表省存储拷贝,但 Schema 一变就炸。稳定之后,还是 INSERT OVERWRITE 进内部表,调度好管。
6. MaxCompute 离线加工:从 ODS 到 DWS
6.1 DIM:先把维度做成「能 JOIN 的」
商品、门店、渠道没有统一 ID,后面所有汇总都是错的。
日全量维表示例:
sql
INSERT OVERWRITE TABLE dim.dim_itm_sku_df PARTITION (ds = '${bizdate}')
SELECT
sku_id,
sku_name,
spu_id,
cate_id_l1,
cate_name_l1,
cate_id_l2,
cate_name_l2,
brand_id,
brand_name,
GETDATE() AS etl_time
FROM ods.ods_itm_sku_df
WHERE ds = '${bizdate}'
AND NVL(is_deleted, 0) = 0;
日历维自己造,别每次用日期函数现场算:
sql
-- dim_pub_date 一次生成 10 年即可,按 ds 或 date_key 点查
-- 字段至少:date_key, date_s, week_id, month_id, is_weekend, is_holiday, year_ago_ds
year_ago_ds 提前算好,同比 SQL 能少一层乱日期计算。节假日每年补一次。
6.2 DWD:口径只在这里定一次
订单明细是经营分析的心脏。我只保留 支付成功且未整单关闭 的行作为销售事实,退款走退款事实表,不要在一张表里又加又减,后人看不懂。
sql
INSERT OVERWRITE TABLE dwd.dwd_trd_order_di PARTITION (ds = '${bizdate}')
SELECT
o.order_id,
o.user_id,
o.shop_id,
i.sku_id,
s.cate_id_l1,
s.cate_id_l2,
o.channel_id,
o.pay_time,
i.sku_qty,
i.pay_amount,
i.cost_amount,
i.pay_amount - NVL(i.cost_amount, 0) AS gross_profit,
CASE
WHEN o.pay_time IS NOT NULL
AND o.order_status NOT IN ('CLOSED', 'CANCELLED')
THEN 1 ELSE 0
END AS is_valid_pay,
GETDATE() AS etl_time
FROM ods.ods_trd_order_di o
JOIN ods.ods_trd_order_item_di i
ON o.order_id = i.order_id
AND i.ds = '${bizdate}'
LEFT JOIN dim.dim_itm_sku_df s
ON i.sku_id = s.sku_id
AND s.ds = '${bizdate}'
WHERE o.ds = '${bizdate}';
几点硬规定:
- 所有 JOIN 都带分区条件 。维表漏了
ds,就是全表扫,钱和时间一起烧。 INSERT OVERWRITE+ 分区,禁止无分区INSERT INTO。- 金额先统一单位,再进 DWD。
is_valid_pay这种业务旗标,注释写清楚,和产品、财务对过字。- 用户、门店缺失不要默默丢掉。维度缺失打未知件(
shop_id = '-1'),否则你会少算钱还不知道。
退款表单独建 dwd_trd_refund_di,关联原订单号、退款时间、退款金额。经营看「净GMV」时在 DWS 减,不要改历史订单分区。
6.3 DWS:按查询粒度预聚合
看板最常用的是「天 × 门店 × 渠道」。这一层做出来,Hologres 和 API 都轻松。
sql
INSERT OVERWRITE TABLE dws.dws_trd_shop_nd PARTITION (ds = '${bizdate}')
SELECT
shop_id,
channel_id,
COUNT(DISTINCT IF(is_valid_pay = 1, order_id, NULL)) AS pay_ord_cnt,
COUNT(DISTINCT IF(is_valid_pay = 1, user_id, NULL)) AS pay_usr_cnt,
SUM(IF(is_valid_pay = 1, pay_amount, 0)) AS gmv_amt,
SUM(IF(is_valid_pay = 1, gross_profit, 0)) AS gross_profit_amt,
SUM(IF(is_valid_pay = 1, sku_qty, 0)) AS pay_sku_qty
FROM dwd.dwd_trd_order_di
WHERE ds = '${bizdate}'
GROUP BY shop_id, channel_id;
再做一张品类汇总 dws_trd_cate_nd、一张投放汇总 dws_mkt_campaign_nd。ROI 在 ADS 里用交易 DWS 关联投放 DWS,不要在 DWD 订单上直接 JOIN 广告,粒度对不齐。
用户复购、留存这类,用独立 DWS,按 user_id 分布。别和门店天表揉一张,扫起来又宽又慢。
6.4 SQL 节点里的参数
DataWorks ODPS SQL 节点调度参数:
bizdate=$bizdate
代码里只用 ${bizdate}。需要昨天、上周同一天:
sql
-- 昨天分区
ds = '${bizdate}'
-- 7 天前,调度参数里加
-- bizdate7=$bizdate-7
赋值节点、条件节点先别玩花的。日任务链路清晰比「智能分支」重要。
6.5 小文件和数据倾斜
经营订单按天分区一般还好。要是某一天做大促,单分区几千亿行,注意:
JOIN小维表让 MaxCompute 走 mapjoin:/*+ MAPJOIN(s) */- 大 Key(比如一个超大门店)先打散再聚合
- 下游 Hologres 导入按天分区,避免一次灌半年
临时表写 tmp. 下,生命周期 7 天。调试用的 CREATE TABLE xxx AS SELECT 跑完就 DROP,别留在生产库。
7. Hologres:外表加速 vs 内部表服务
7.1 外表:给分析师和对数用
在 Hologres 里建一个 schema 专门放映射:
sql
CREATE SCHEMA IF NOT EXISTS foreign_mc;
批量映射 MaxCompute 三层模型里的表(项目名、Schema 名换成你的):
sql
IMPORT FOREIGN SCHEMA "mc_biz_prod#dws"
LIMIT TO (dws_trd_shop_nd, dws_trd_cate_nd)
FROM SERVER odps_server
INTO foreign_mc
OPTIONS (if_table_exist 'update', if_unsupported_type 'error');
两层模型则 IMPORT FOREIGN SCHEMA mc_biz_prod。三层模型写成 项目#schema,少写一层会报错。
LIMIT TO 一定要加。不加就把整个项目表全映射进来,又慢又乱。
查询:
sql
SELECT shop_id, gmv_amt
FROM foreign_mc.dws_trd_shop_nd
WHERE ds = '20260822';
外表适合:
- 对账:Hologres ADS 和 MaxCompute DWS 对一下
- 临时下钻到 DWD(能跑,别当接口)
- 还没决定要不要常驻的表
不适合:DataService 高峰、大屏 5 秒刷一次、Agent 并发问数。
MaxCompute 分区更新后,导入或对账前再跑一遍 IMPORT FOREIGN SCHEMA ... if_table_exist 'update',把元数据刷新。
7.2 内部表:给应用用
核心指标表在 Hologres 物化。建表属性和 CREATE TABLE 放同一个事务里,分布键、聚簇事后改不了(改等于重建)。
sql
BEGIN;
CREATE TABLE ads.ads_biz_shop_1d (
shop_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
ds TEXT NOT NULL,
pay_ord_cnt BIGINT,
pay_usr_cnt BIGINT,
gmv_amt NUMERIC(18,2),
gross_profit_amt NUMERIC(18,2),
pay_sku_qty BIGINT,
etl_time TIMESTAMPTZ,
PRIMARY KEY (shop_id, channel_id, ds)
);
CALL SET_TABLE_PROPERTY('ads.ads_biz_shop_1d', 'orientation', 'column');
CALL SET_TABLE_PROPERTY('ads.ads_biz_shop_1d', 'distribution_key', 'shop_id');
CALL SET_TABLE_PROPERTY('ads.ads_biz_shop_1d', 'clustering_key', 'ds:asc');
CALL SET_TABLE_PROPERTY('ads.ads_biz_shop_1d', 'segment_key', 'ds');
CALL SET_TABLE_PROPERTY('ads.ads_biz_shop_1d', 'bitmap_columns', 'channel_id');
CALL SET_TABLE_PROPERTY('ads.ads_biz_shop_1d', 'time_to_live_in_seconds', '7776000');
COMMIT;
怎么选键,别背概念,按查询来:
| 属性 | 我怎么选 |
|---|---|
| orientation | 经营分析几乎全是列存。点查用户画像再考虑行存或行列共存 |
| distribution_key | JOIN / GROUP BY 的大表关联键。门店分析用 shop_id,用户分析用 user_id。不要选日期,日期太集中,shard 会偏 |
| clustering_key / segment_key | 时间,加速 ds BETWEEN |
| bitmap | 低基数列等值过滤:渠道、订单类型、是否会员 |
| PRIMARY KEY | 有主键才能按键更新。日指标表用业务键 + ds |
分区表:Hologres 一级 LIST 分区。MaxCompute 多级分区进 Hologres,只留一级(通常是 ds),其余当普通列。
sql
CREATE TABLE ads.ads_biz_shop_1d (
...
PRIMARY KEY (shop_id, channel_id, ds)
) PARTITION BY LIST (ds);
子表可以手建,也可以开动态分区,让它按天自动建子表。调度导入前确认当天子表在,否则 INSERT 直接失败。
7.3 从 MaxCompute 灌进 Hologres
量大、要快,用 Hologres SQL 从外表 INSERT,比再走一遍数据集成快。
DataWorks 里建 Hologres SQL 节点:
sql
-- 刷外表元数据
IMPORT FOREIGN SCHEMA "mc_biz_prod#dws"
LIMIT TO (dws_trd_shop_nd)
FROM SERVER odps_server
INTO foreign_mc
OPTIONS (if_table_exist 'update');
-- 先清当天再写入,避免重跑双份
DELETE FROM ads.ads_biz_shop_1d WHERE ds = '${bizdate}';
INSERT INTO ads.ads_biz_shop_1d
(
shop_id, channel_id, ds,
pay_ord_cnt, pay_usr_cnt, gmv_amt,
gross_profit_amt, pay_sku_qty, etl_time
)
SELECT
shop_id,
channel_id,
ds,
pay_ord_cnt,
pay_usr_cnt,
gmv_amt,
gross_profit_amt,
pay_sku_qty,
NOW()
FROM foreign_mc.dws_trd_shop_nd
WHERE ds = '${bizdate}';
ANALYZE ads.ads_biz_shop_1d;
有主键的表也可以 INSERT ON CONFLICT 做 upsert。日批次我更喜欢 删当天再插,语义清楚,补数据不会留残行。
导入前对参与表跑 ANALYZE,优化器才有统计信息。大表导入 OOM 时,把外表执行并行度调低(hg_experimental_foreign_table_executor_max_dop),别和在线查询抢同一坨内存。生产实例建议开 计算组:导入走离线计算组,查询走在线计算组。
7.4 给 AI 用的宽表单独做
Agent 问数不要让它 JOIN 五张 DWS。给一张当天经营概览:
sql
CREATE TABLE ads.ads_ai_biz_overview_1d (
ds TEXT PRIMARY KEY,
gmv_amt NUMERIC(18,2),
gmv_amt_wow NUMERIC(18,2),
gmv_wow_rate NUMERIC(10,4),
gross_profit_amt NUMERIC(18,2),
gross_profit_rate NUMERIC(10,4),
pay_usr_cnt BIGINT,
pay_ord_cnt BIGINT,
atv_amt NUMERIC(18,2), -- 客单价
ad_cost_amt NUMERIC(18,2),
roi NUMERIC(10,4),
etl_time TIMESTAMPTZ
);
字段名用业务语言,别用源库缩写。问数产品匹配「客单价」「毛利率」时,宽表字段就是答案,少一层翻译,幻觉会少一点。
口径变更只改 MaxCompute DWS / ADS 任务,Hologres 重导当天。不要在 Java 里二次计算毛利率,应用层一算,数仓对账永远对不齐。
8. DataWorks 调度:把链路变成每天能跑的 DAG
8.1 业务流程怎么串
一个经营分析日任务,从左到右:
[同步 ODS 订单]─┐
[同步 ODS 订单明细]─┼─[DIM 商品]─[DWD 订单]─[DWS 门店天]─[Holo 导入门店天]─[质量校验]─[数据服务用的表已就绪]
[同步 ODS 商品]──┘ └─[DWS 品类天]─[Holo 导入概览]
[同步投放]────────────────────────────────────────┘
依赖用 产出表 来挂,不要靠「估计 3 点跑完」。上游节点输出 dwd.dwd_trd_order_di,下游依赖这张表。换人接任务时,血缘还在。
日调度时间:
- 同步:凌晨 01:30 左右,避开业务库高峰,也避开 RDS 备份窗
- 计算:同步成功之后,不要固定 02:00 硬跑,用依赖
- 要求 9 点老板看数,倒推最晚完成时间,设超时告警
跨周期:今天的 DIM 依赖昨天 DIM 的场景不多。用户留存这类,依赖历史分区时,用 跨周期依赖 昨天自己,别在 SQL 里碰运气扫 180 个分区还不写 ds。
8.2 发布和生产
标准模式:开发环境跑通 → 提交 → 发布到生产。发布单写清楚改了哪张表、口径有没有变。口径变更当天要同步改质量规则的期望值,否则规则会误杀。
补数据:运维中心选节点、选业务日期范围。INSERT OVERWRITE 分区表才敢补。INSERT INTO 无分区表,补一次脏一次。
8.3 失败怎么收
告警挂值班表,钉钉 / 短信都行。我分两级:
- 核心链路(订单 DWD、门店 DWS、概览 ADS):失败立即打,夜班要起来
- 边缘表(某条投放渠道、某张实验宽表):早会看
重跑次数不要无限。连续失败说明是数据问题或源库变了,不是偶发抖动。
9. 数据质量:没规则就别发布
经营数字错一次,信任没了。质量规则和节点绑在一起,强规则失败阻断下游。
我最少会挂这些:
| 表 | 规则 | 强弱 |
|---|---|---|
| ODS 订单 | 当天行数 > 0 | 强 |
| ODS 订单 | 主键 order_id 唯一 |
强 |
| DWD 订单 | pay_amount >= 0 |
强 |
| DWD 订单 | 当天 GMV 相对 7 日均值波动不超过阈值(比如 50%,大促调大) | 弱,告警不阻断 |
| DWS 门店 | shop_id 不为空 |
强 |
| ADS 概览 | 有且仅有一行当天记录 | 强 |
| ADS 概览 | gmv_amt 与 DWS 汇总差的绝对值 < 0.01 |
强 |
波动规则别设太死。周一和周末差一倍很正常,用同比、用工作日基线,比用「环比 20%」少误报。
空值、枚举值(订单状态不在码表里)也要管。源系统多一个状态,DWD 的 CASE 没接住,指标会悄悄变少。
DataWorks 数据质量里把规则挂到对应 ODPS / Hologres 节点。强规则失败,下游 Holo 导入不要跑,避免把错数推到看板上。
10. 数据服务:给 AI 和报表一个稳定出口
Hologres 内部表就绪后,上层有三条路,按场景选,不要全走 JDBC 裸连。
10.1 BI
Hologres 兼容 PostgreSQL,DataV、Quick BI、Tableau、Metabase 都能连。账号只读、只开放 ads 和必要的 dws。分析师要下钻 DWD,走申请,别默认全开。
10.2 DataWorks 数据服务
把稳定查询封 API,给运营后台和 Agent 工具调用。
示例:按门店查某天经营。
sql
SELECT
shop_id,
channel_id,
pay_ord_cnt,
gmv_amt,
gross_profit_amt
FROM ads.ads_biz_shop_1d
WHERE ds = ${ds}
AND shop_id = ${shop_id}
发布前:
- 参数做校验,
ds正则\d{8} - 超时设短(1~3 秒),比让接口挂着强
- 限流,Agent 一并发,Hologres 一样会顶不住
- 生产走数据服务的资源组,和同步任务隔离
返回字段加一层中文说明,问数那边少做映射。金额还是数字,单位在文档里写死「元」。
10.3 AI 怎么接,才不像在碰运气
这一段是中台和「Chat 直接连库」的差别。
- 先指标表,后自由 SQL。 Agent 工具白名单只给
ads_*和几个 API。不要把 MaxCompute 项目级 SELECT 扔给模型。 - 口径文档当系统提示的一部分。 GMV、实收、净GMV 各是什么,写在指标字典里,随 API 一起版本化。
- 数字必须出仓,不允许模型口算。 同比、占比在 ADS 就算好。模型只负责选指标和读结果。
- 问不到就说没有。 当天分区没产出(质量阻断),API 返回明确错误,前端显示「数据未就绪」,比返回半截数好看。
异常检测用日指标序列即可:读 ads_ai_biz_overview_1d 近 30 天,规则或模型在应用侧跑。特征可以在 MaxCompute 离线算好再导入,不必在线 JOIN DWD。
11. 权限、成本、监控
11.1 权限
- 生产 MaxCompute:开发人员默认没有
DROP。要删表走工单或运维账号。 - Hologres:
ads写权限只给调度账号。人用只读账号。 - 数据服务 AppKey 按应用拆,不要全公司共用一把。
- 手机号、身份证进 ODS 可以,进 DWS / ADS 要脱敏或直接去掉。经营分析绝大多数场景用不到。
11.2 成本,三条最管用
- 分区条件写进每一条 SQL。 这是 MaxCompute 账单第一杀手。
- Hologres 只存热数据和高频表。 两年明细全进口,存储和实例规格会一起涨。冷查询走外表。
- 临时表和失败重跑。 任务失败循环重跑,等于按失败次数付计算费。先修数据再重跑。
MaxCompute 作业看执行计划:有没有分区裁剪、有没有长尾 Map。Hologres 看慢查询日志,缺索引就补,补不了就预聚合。
11.3 监控
最低配置:
- DataWorks 基线:核心 ADS 9:00 前产出
- MaxCompute 当日计算消费阈值
- Hologres CPU / 内存 / 连接数
- 同步延迟(实时链路才需要)
- 质量规则失败率
基线比单点告警有用。单点失败你知道某个节点挂了;基线告诉你「老板 9 点要看的数赶不上了」。
12. 我踩过的坑
${bizdate}和自然日混用。 调度 8 月 23 日跑的任务,业务日期默认是 22 日。运营说「今天的数」时,先问他要的是自然日还是业务日。- 增量条件用了没有索引的列。 源库被扫爆,DBA 会来找你。先
EXPLAIN,再上同步。 - Overwrite 写成 Append。 重跑后 GMV 翻倍,质量规则如果只查非空,查不出来。用「行数相对昨日」能兜住一部分。
- Hologres 外表当 API。 演示很快,一并发全超时。服务表必须内部表。
- 建内部表忘了同一事务里设 distribution_key。 事后改不了,只能新建表、导数据、改下游。
- 维表漏分区。
dim_itm_sku_df不写ds,作业从 30 秒变成 30 分钟。 - 金额有的分有的元。 对账对了三天,最后发现投放表是分、订单表是元。DWD 统一。
- 生命周期没设。 半年后 ODS 把存储打满。建表语句必须带
LIFECYCLE。 - 开发直接连生产 Hologres。 一条没
WHERE的 SQL 把在线查询打挂。计算组隔离,或者开发实例隔离。 - 口径口口相传。 人一走,指标就变成传说。字典表或文档仓库里写死公式,API 注释里抄一份。
13. 建议的实施顺序
别并行铺 20 条链路。按这个顺序,两到三周能看到第一张能用的经营日报(人够、源表干净的前提下)。
| 阶段 | 交付物 | 完成的标志 |
|---|---|---|
| 1 | 地域、账号、标准模式工作空间、Serverless 资源组、MC / Holo 绑定 | 连通性测试全绿 |
| 2 | 分层命名规范、指标口径一页纸(GMV / 毛利 / 客单) | 业务、财务、数据三方签过 |
| 3 | ODS 订单、订单明细、商品、门店 四张同步 | 连续 3 天分区行数合理 |
| 4 | DIM + DWD 订单,质量规则(主键、金额、非空) | 能对上源库抽样 |
| 5 | DWS 门店天、品类天 | 抽两天人工加总一致 |
| 6 | Hologres 内部表 + 导入任务 + ANALYZE | BI 连上,秒级返回 |
| 7 | 概览 ADS + 数据服务 API | 应用或问数打通一个接口 |
| 8 | 基线、告警、补数据演练 | 人为失败一次,下游阻断且能重跑恢复 |
| 9 | 投放、退款、库存,按同样套路加域 | 新域不破坏老 DAG |
| 10 | 要「今日实时」再上 Binlog / Flink | 实时数和 T+1 对账误差可接受 |
第一张表不要追求「企业级数仓全景图」。先把订单链路做成样板:同步覆盖分区、SQL 全是 INSERT OVERWRITE、质量强规则、Holo 内部表、API 只读 ADS。后面库存、投放、用户都按这套复制。
这套中台对我而言,难点不在开通三个产品,而在 口径、分区、服务边界 叠在一起。MaxCompute 负责把历史和复杂计算按天算对;Hologres 负责把人要看的、程序要查的那一层读快;DataWorks 负责让这条链路每天自己跑、跑错能停。
AI 经营分析不是让模型去翻业务库。模型能问得准,是因为你把「昨天门店实收」变成了一张有分区、有质量、有 API 的表。表不可信,生成再流畅也是在一本正经地胡说。
能跑通订单域,再扩。不要一上来就画一张包打天下的架构图------图很好看,数对不上。