本文档记录
poyee-data-warehouse数据仓库的分层架构、建模方法论与总线矩阵。 与命名规范(数仓命名规范.md)配套阅读。
二维视图(横向分层 × 公共维度侧柱):
┌────────────┐
┌───────────────────────────────────────────┐ │ │
│ ADS 应用层:业务指标、面向服务端宽表 │ │ │
├───────────────────────────────────────────┤ │ │
│ TDM 标签层:长表明细 + 宽表 + 人群包 │◄──┤ DIM │
├───────────────────────────────────────────┤ │ │
│ DWS 汇总层:主题聚合、提供公共指标 │◄──┤ 公共维度 │
├───────────────────────────────────────────┤ │ │
│ DWD 明细层:清洗加工 + 维度退化 │◄──┤ │
├───────────────────────────────────────────┤ │ │
│ ODS 贴源层:类型转换、脏数据识别 │ │ │
├───────────────────────────────────────────┤ └────────────┘
│ RAW 采集层:全字段 STRING,原样落盘 │
└───────────────────────────────────────────┘
竖向分域(贯穿 DWD 及以上):交易 trd / 用户 usr / 商品 prd / 店铺 shp / 公共 pub
分层定义表:
| 层 | 代码 | 定位 | 典型操作 |
|---|---|---|---|
| 原始采集层 | raw |
从源系统/外部文件落地的原始数据,全字段 STRING,同步任务不做任何类型转换 | DataX 直接同步、CSV 解析、字段原样落盘 |
| 贴源层 | ods |
类型转换、空值处理、脏数据识别的唯一入口,输出类型化的干净表 | 从 raw STRING 字段做 CAST/TRY_CAST、空值兜底、去重 |
| 维度层 | dim |
公共维度(时间、地区、渠道等),贯穿 DWD/DWS/TDM/ADS | 维度退化、缓慢变化维 |
| 明细层 | dwd |
标准化、维度补全、异常处理;星型模型 | 宽表化、维度退化、数据质量校验 |
| 汇总层 | dws |
建立汇总宽表,提供公共指标 | 多维聚合、主题整合 |
| 主题域模型层 | tdm |
标签明细长表(EAV)+ 核心标签宽表(pivot)+ 人群包(远期 bitmap);按实体类型分表(usr/prd/shp),Hive 离线计算,远期可加 HBase(在线标签服务)/ ClickHouse(人群包交叉计算) | 标签计算、pivot 宽表、bitmap 圈选 |
| 应用层 | ads |
提供数据展示、数据指标 | 面向消费端的定制聚合 |
数据流:
RDS PG / ES ──DataX──▶ RAW ──SparkSQL──▶ ODS ──▶ DWD ──▶ DWS ──▶ TDM ──▶ ADS
│
▼ DataX / Broker Load
服务层(Doris / CK / ES / Mongo)
当前划分为以下主题域,对应命名规范的 domain 代码:
| 域 | 代码 | 说明 |
|---|---|---|
| 交易域 | trd |
订单、支付、退款、购物车 |
| 用户域 | usr |
注册、登录、活跃、画像 |
| 商品域 | prd |
商品、SKU、SPU、价格 |
| 店铺域 | shp |
店铺、商家 |
| 公共域 | pub |
平台、日历、地理等 |
总线矩阵用于指导维度建模和指标体系建设,列出各业务过程与公共维度/业务维度的关系。 以下是模板,还未整理成正式可用的矩阵
| 域 | 业务过程 | 说明 | 时间 | 用户 | 地区 | 渠道 | 商品 | 店铺 | 活动 | 支付 |
|---|---|---|---|---|---|---|---|---|---|---|
| 交易域 | order_create | 用户提交订单(未支付) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| order_pay | 用户完成订单支付 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| order_cancel | 用户取消订单 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| refund | 用户发起退款/售后 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| cart_add | 用户加入购物车 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
| 用户域 | user_register | 用户注册账号 | ✓ | ✓ | ✓ | |||||
| user_login | 用户登录行为 | ✓ | ✓ | ✓ | ||||||
| user_active | 活跃行为(浏览/点击) | ✓ | ✓ | ✓ | ||||||
| 商品域 | product_expose | 商品曝光(列表/推荐位) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| product_click | 商品点击进入详情页 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
| product_favor | 用户收藏商品 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
| 店铺域 | shop_visit | 用户访问店铺 | ✓ | ✓ | ✓ | ✓ | ||||
| shop_follow | 用户关注店铺 | ✓ | ✓ | ✓ | ✓ |
OLTP 业务库采用范式建模;OLAP 数仓特别是 DWD 层采用维度建模(Kimball)。 模型类型:星型模型,维度退化在 DWD 或 DIM 层完成。
| 步骤 | 说明 | 示例(发券业务) |
|---|---|---|
| 1. 确定主题域 | 选取所属业务域 | 交易域 / 营销域 |
| 2. 选择业务过程 | 从业务流程中提取可度量的动作 | 券发放 / 曝光 / 点击 / 领取 / 核销 |
| 3. 声明粒度 | 明确事实表每行表示什么;优先原子粒度 | "每个用户每次领券记录";主键 = 领取 id + 幂等 id |
| 4. 确认维度 | 从哪些角度切分数据 | 权益类型、发放渠道、门槛、折扣、时间、用户 |
| 5. 确认事实 | 表示度量的数值字段 | 领取金额、核销金额、引导 GMV |
dim_pub_* 系列)。【业务过程】 券的核销
【粒度】 子订单 × 券 id (SKU 级)
【维度】 权益类型 / 领取场景 / 发放渠道 / 时间 / 用户 / 店铺
【事实】 核销金额 / 引导 GMV
左侧是抽象的规划层级,右侧是一个零售业务的具体示例:
| 规划层级 | 示例(xxx 零售) | 物理落地 |
|---|---|---|
| 业务板块 | xxx 零售 | — |
| 数据域 | 交易域 | Schema 划分 |
| 业务过程 | 支付 | DWD 事实表切片 |
| 度量 | 下单金额 | 事实字段 |
| 维度建模 | 时间 / 区域 / 商品 | 维度表:订单表、商品表 |
| 原子指标 | 下单金额 | order_amt_cny |
| 派生指标 | 近 1 天 上海区域 支付下单金额 | ADS 汇总表字段 |
| 汇总表 | ads_trd_order_area_agg_inc_d |
按区域聚合的下单金额日汇总 |
关键映射:业务过程 → DWD 事实表;度量 → 事实字段;维度 → DIM 表;派生指标 = 原子指标 + 时间周期 + 修饰词 → DWS/ADS 字段。
核心规则:DWD 事实表默认承载业务事件(不可变事实),实体当前状态进 DIM 拉链表(_zip_d)。
事件 vs 状态判别:
建模规则:
| 类型 | 归属 | 命名(参考 21-命名规范.md) |
示例 |
|---|---|---|---|
| 业务事件 | DWD 事实表,每个业务过程一张 | dwd_{域}_{业务过程}_apd_d |
dwd_trd_order_create_apd_d、dwd_trd_order_pay_apd_d |
| 实体状态 | DIM 拉链表 | dim_{域}_{实体}_zip_d |
dim_trd_order_zip_d(含 current_status / 各阶段时间 / start_date / end_date) |
为什么这么拆:
_apd_d 只追加,与 Hive 列存(ORC)"只追加 + 分区"模型天然契合,不需要 UPDATEis_current 切片或按 dt 切片,直观典型场景:
| 场景 | 事件表(DWD _apd_d) |
状态表(DIM _zip_d) |
|---|---|---|
| 订单履约(下单 → 支付 → 发货 → 签收,单向) | 4 张 _apd_d |
dim_trd_order_zip_d |
| 拼团(发起 → 审核 → 审核拒绝 → 重新提交 → ……,循环) | 每个动作一张 _apd_d(含"审核拒绝"、"重新提交"等可重复事件) |
dim_trd_group_zip_d,每次状态变更生成新拉链行 |
| 浏览 / 点击 / 加购(独立事件流,无状态实体) | 各一张 _apd_d |
无 |
| 订单 vs 订单明细行(一对多) | 各一张 _apd_d(粒度不同必拆) |
无 |
循环状态机的处理:业务流程若存在循环(如拼团审核可能多次反复:"提交 → 拒绝 → 修改 → 再提交"),事件流水(_apd_d)天然支持任意次重复——每次状态变更追加一行,完整还原过程;DIM 拉链表(SCD Type 2)每次状态变更生成新行,旧行 end_date 置变更前一天,状态历史完整可回溯。
何时可考虑 acc:固定线性里程碑场景(不循环、里程碑可枚举且单向推进,如严格不可逆的合同审批流程),如有具体场景按需单独评估,不一刀切禁用。新建 acc 表时在 PR / 设计稿里说明选型理由。
自检:建表想给字段加"时间戳 + 若干状态字段"时,停一步问:这是事件还是状态?事件 → _apd_d;状态 → _zip_d。
ODS 层从业务库/埋点/爬虫数据接入。关键问题:数据是否存在物理删除,决定增量策略。
| 数据来源 | 接入方式 | 快照类型 | 备注 |
|---|---|---|---|
| 业务库 (PG/MySQL) | DataX + CDC | inc(增量) |
如存在物理删除,后续推行软删除 |
| 埋点 (Sensors → Kafka) | Kafka → HDFS/Hive | apd(追加) |
不可变事件流 |
| 爬虫数据 | 爬虫落库 → DataX | ful 或 inc |
按源站特性决定 |
| 维度数据 | 手工上传 / 配置化 | ful(全量) |
如国家映射、汇率表 |
| 一次性历史 / vendor 单批交付 | 本地 CSV → bin/csv-to-hdfs-starter.py |
his(一次性历史) |
永不调度,导入后入档;周期段固定 _o |
快照类型决策:
inc(增量快照)apd(追加)zip(拉链表)ful(全量)his(与 ful 严格区分:ful 是周期性调度的全量重刷,his 是导入后归档的一次性快照)dt(必须),STRING 类型,格式 YYYYMMDD(如 20260101);hr(小时,按需)拼团 DWD/DIM 组合:每个动作(发起 / 审核 / 拒绝 / 重新提交 / 成功 / 失败)独立 dwd_trd_group_{动作}_apd_d 事件表承载不可变流水;实体当前状态进 dim_trd_group_zip_d 拉链表,每次状态变更生成新拉链行。详见 §5.5。
各层 dt 分区键代表的时间含义、对漂移和重复的容忍度、分区间关系不同,是设计 ETL 任务和理解查询行为的基础。
| 层 | 分区键语义 | 时间字段来源 | 漂移容忍 | 重复容忍 | 分区间关系 |
|---|---|---|---|---|---|
| Raw | 批次日(系统时间) | dt = stop - 1(抽取任务的逻辑日期) |
容忍 | 容忍(同 pk 同 dt 内可多条) | 独立,可含跨日漂移数据 |
| ODS | 记录最后写入日(系统时间) | DATE(update_time) |
不容忍 | 分区内不容忍、跨分区容忍(保留 update_time 轨迹) |
独立,同 pk 可跨多分区 |
| DWD 事实表 | 业务行为发生日(业务时间) | DATE(order_create_time) / DATE(event_time) 等业务字段 |
不容忍 | 不容忍(事件不可变) | 独立,追加写 |
| DIM 拉链表 | 不按时间分区(或 is_current 二级分区) |
—— | —— | 多版本非重复([start_date, end_date) 区间不重叠) |
每行是状态生效区间 |
| DIM 快照表 | 快照日(业务时间) | dt = today |
不容忍 | 分区内不容忍;跨分区同 pk 多次出现是"每日快照"而非重复 | 独立,每分区是该日全量 |
| DWS | 统计截止日(业务时间) | 聚合口径的截止点 | 不容忍 | 分区内不容忍;分区间原始明细冗余 | 分区间冗余(滑动窗口重叠) |
| ADS | 报表快照日(业务时间) | 报表生成日 | 不容忍 | 不容忍 | 独立,一行可含多统计周期指标 |
两个容忍度的概念:
update_time 数据落在 Raw dt=8 号 没关系,下游会归位两个容忍度的组合反映各层的核心取舍:
各层基础职责见 §2 表格;本节聚焦每层的关键设计取舍。
基础职责见 §2 + §8.1。
[day-start, day+1-stop)(48 小时宽),所有抓到的记录统一落 dt = stop - 1 分区基础职责见 §2 + §8.2。
PARTITION (dt),按 DATE(update_time) 分发,把 Raw 漂移数据归位到正确分区(pk, max(update_time)) 去重):保留每日 ODS 跑时刻的 dt=X 版本轨迹,用于审计、回溯,防止覆盖后丢失中间快照基础职责 + 事件 vs 状态拆分原则见 §5.5。
| 表特征 | 建模方式 | 分区策略 | 写入模式 |
|---|---|---|---|
| 大 / 中维度表(用户、商品、商户) | 拉链表 SCD2 | 不分区(或 is_current 二级分区) |
当日变更 pk:原行 end_date 置昨天 + 新行 insert |
| 小高频变更维表(类目、地区、配置) | 每日全量快照 | 业务时间分区 | 每日全量覆盖 dt=today 分区 |
| 极小不变表(字典、枚举) | 单表全量 | 不分区 | 偶尔全量覆盖 |
选型判据:变更率 × 保留天数
拉链表分区的特殊性:
is_current='Y'/'N' 做二级分区加速"当前状态"查询start_date 年份 / 月份做粗粒度分区控制扫描范围主题 × 粒度 × 统计周期 一张表
dws_user_order_1d、dws_user_order_7d、dws_user_order_30ddws_shop_order_1d、dws_shop_order_7d、dws_shop_order_30d7d = 今日 1d + 昨日 7d - 7 天前 1d,减少 DWD 扫描压力报表主题 一张表,一行多周期
user_id | order_cnt_1d | order_cnt_7d | order_cnt_30d | gmv_1d | ... | dt原则 1:Raw / ODS 是系统时间,DWD 及以上是业务时间
update_time 及 ETL 批次时间)。贴源层不解释业务语义,只忠实反映"数据库里发生了什么"order_create_time 等)。面向分析,分区语义对齐业务问题("8 号下了多少单"指的是用户 8 号下单的行为,不是数仓 8 号抓到的数据)原则 2:漂移容忍度和重复容忍度自下而上递减
update_time 轨迹)原则 3:分区语义和查询语义对齐
查询条件里的 where dt='8 号' 应该和用户的直觉对齐:
where dt='8 号',分区键是业务下单日where dt='8 号',分区键是批次日两种"8 号"语义不同,分别由不同层承载,不混淆。
原则 4:事实与维度解耦(DWD vs DIM 并列)
原则 5:ODS 为下游保留最大灵活性
update_time 轨迹 → 支持 DIM 拉链表建设原则 6:上层数据可重放,下层数据不可重放
这是本数仓的核心数据契约,所有 raw / ods 层作业都必须遵守。
dt 分区字段一律 STRING 类型columnType 的类型映射(或统一填 string),CSV 导入时 SparkSQL 读取后也不 CASTCREATE EXTERNAL TABLE,DROP TABLE 只删元数据,HDFS 数据保留;raw 作为链路兜底层,误删元数据时数据仍可 MSCK REPAIR / 重建元数据恢复,无需回源库重同步CAST / TRY_CAST,输出真正类型化的干净表_err 分区、或写入专门的数据质量日志表,具体策略 TBD)raw 层是否需要 etl_load_time / src_file / src_row_no 等框架字段,暂不做统一要求,后续实际接入第一批表时再根据需要补充到本节。
总则:raw 层一律 STRING 兜底同步;类型化在 ods 层完成。下表为 ods 层 CAST 目标类型的参考表,具体字段可按业务需要微调(如小金额字段可下沉到 DECIMAL(16,2))。
| PG 类型 | Hive 类型 |
|---|---|
int2 / smallint |
BIGINT |
int4 / integer / int |
BIGINT |
int8 / bigint |
BIGINT |
serial |
BIGINT |
bigserial |
BIGINT |
numeric / decimal |
DECIMAL(20,4) |
real / float4 |
DECIMAL(20,4) |
float8 / double precision |
DECIMAL(20,4) |
char / character |
STRING |
varchar / character varying |
STRING |
text |
STRING |
timestamp / timestamp without time zone |
STRING |
timestamptz |
STRING |
date |
STRING |
time / timetz |
STRING |
boolean / bool |
TINYINT |
uuid |
STRING |
interval |
STRING |
tsvector |
STRING |
array |
STRING(保留 JSON/文本形态,dwd 按需解析) |
hstore |
MAP<STRING,STRING> |
说明:
BIGINT:避免上游扩位(int4 → int8)时下游被动改表DECIMAL(20,4):覆盖绝大多数金额/比率场景;特殊精度需求(如高精度科学计算)单独评估TINYINT(0/1):Hive 的 BOOLEAN 与 ORC/Spark 生态兼容性没有 TINYINT 稳定STRING:保留源端字面量,dwd 层再按需 to_timestamp / to_date(待补,首批 ES 埋点库接入时落地)