20-数仓分层与建模.md 15 KB

数仓分层与建模

本文档记录 poyee-data-warehouse 数据仓库的分层架构、建模方法论与总线矩阵。 与命名规范(数仓命名规范.md)配套阅读。

1. 架构原则

  • 横向分层:提高数据复用率,明确数据血缘
  • 竖向分域:指导指标体系的建设

2. 分层定义

二维视图(横向分层 × 公共维度侧柱):

                                                ┌────────────┐
┌───────────────────────────────────────────┐   │            │
│  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)

3. 主题域划分

当前划分为以下主题域,对应命名规范的 domain 代码:

代码 说明
交易域 trd 订单、支付、退款、购物车
用户域 usr 注册、登录、活跃、画像
商品域 prd 商品、SKU、SPU、价格
店铺域 shp 店铺、商家
公共域 pub 平台、日历、地理等

4. 数仓总线矩阵

总线矩阵用于指导维度建模和指标体系建设,列出各业务过程与公共维度/业务维度的关系。 以下是模板,还未整理成正式可用的矩阵

业务过程 说明 时间 用户 地区 渠道 商品 店铺 活动 支付
交易域 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 用户关注店铺

5. 维度建模方法论

OLTP 业务库采用范式建模;OLAP 数仓特别是 DWD 层采用维度建模(Kimball)。 模型类型:星型模型,维度退化在 DWD 或 DIM 层完成。

5.1 建模五步法

步骤 说明 示例(发券业务)
1. 确定主题域 选取所属业务域 交易域 / 营销域
2. 选择业务过程 从业务流程中提取可度量的动作 券发放 / 曝光 / 点击 / 领取 / 核销
3. 声明粒度 明确事实表每行表示什么;优先原子粒度 "每个用户每次领券记录";主键 = 领取 id + 幂等 id
4. 确认维度 从哪些角度切分数据 权益类型、发放渠道、门槛、折扣、时间、用户
5. 确认事实 表示度量的数值字段 领取金额、核销金额、引导 GMV

5.2 关键原则

  • 同一事实表粒度必须一致。跨业务过程合并的合并事实表,各自度量必须有同等级粒度。
  • 维度退化:将常用维度属性冗余到事实表,减少运行时 JOIN。
  • 一致性维度:公共维度全局统一(见 dim_pub_* 系列)。
  • 原子粒度优先:最低粒度事实可以无限上卷,高粒度事实不可下钻。

5.3 建模示例

【业务过程】 券的核销
【粒度】      子订单 × 券 id (SKU 级)
【维度】      权益类型 / 领取场景 / 发放渠道 / 时间 / 用户 / 店铺
【事实】      核销金额 / 引导 GMV

5.4 从数仓规划到物理落地的推演

左侧是抽象的规划层级,右侧是一个零售业务的具体示例:

规划层级 示例(xxx 零售) 物理落地
业务板块 xxx 零售
数据域 交易域 Schema 划分
业务过程 支付 DWD 事实表切片
度量 下单金额 事实字段
维度建模 时间 / 区域 / 商品 维度表:订单表、商品表
原子指标 下单金额 order_amt_cny
派生指标 近 1 天 上海区域 支付下单金额 ADS 汇总表字段
汇总表 ads_trd_order_area_agg_inc_d 按区域聚合的下单金额日汇总

关键映射:业务过程 → DWD 事实表;度量 → 事实字段;维度 → DIM 表;派生指标 = 原子指标 + 时间周期 + 修饰词 → DWS/ADS 字段。

5.5 DWD 明细粒度设计(合 vs 拆)

核心规则:粒度相同则合,粒度不同则拆。

判断"粒度是否相同"的一个问题:两个业务动作描述的是不是同一个实体的同一条记录?

  • 同一个实体 + 同一条记录(只是状态/阶段不同)→ 合
  • 同一个实体 + 但每次动作产生新记录 → 拆
  • 不同实体 → 拆

典型场景:

场景 粒度关系 结论 理由
拼团发起 vs 拼团成功 同一个团的状态变化 粒度都是团 ID,用 status + 多个时间列区分
下单 vs 支付 vs 发货 vs 签收 同一个订单的生命周期 粒度都是订单 ID,每个阶段一列时间戳(对应 21-命名规范.md §2.2 acc 快照)
拼团 vs 订单 一对多 团是团、单是单,粒度不同
订单 vs 订单明细行 一对多(一个订单多个商品) 行项目有独立粒度
浏览 vs 点击 vs 加购 各自独立事件流 每次行为是独立记录(对应 21-命名规范.md §2.2 apd 快照)

自检方法:

  1. 能不能自然地用一行表示? 如果合并后一行就是一个完整业务实体(一个团、一个订单),各阶段只是多几列时间/状态字段 → 合
  2. 合并后会不会出现一对多导致行膨胀? 如果合并后同一个 ID 会出现多行 → 说明粒度不同,必须拆
  3. 分析时是否总要 JOIN 回来? 如果拆了之后大多数分析场景都要把两张表 JOIN 在一起 → 说明本来就该合

一句话总结:一行能完整描述一个业务实体就合,合了之后一个 ID 会出现多行就拆。

状态时间字段的设计:保留 3-5 个核心状态的时间字段(值为时间戳),其他非核心状态不建新列,使用原始状态码标记当前状态。后期若需要状态流水分析,新建状态流水表(_apd_d)而不是改主表。

6. 数据同步策略

ODS 层从业务库/埋点/爬虫数据接入。关键问题:数据是否存在物理删除,决定增量策略。

数据来源 接入方式 快照类型 备注
业务库 (PG/MySQL) DataX + CDC inc(增量) 如存在物理删除,后续推行软删除
埋点 (Sensors → Kafka) Kafka → HDFS/Hive apd(追加) 不可变事件流
爬虫数据 爬虫落库 → DataX fulinc 按源站特性决定
维度数据 手工上传 / 配置化 ful(全量) 如国家映射、汇率表
一次性历史 / vendor 单批交付 本地 CSV → bin/csv-to-hdfs-starter.py his(一次性历史) 永不调度,导入后入档;周期段固定 _o

快照类型决策:

  • 数据创建后会被修改 → inc(增量快照)
  • 数据不可变 → apd(追加)
  • 缓慢变化维、需要保留历史轨迹 → zip(拉链表)
  • 每日重刷全表 → ful(全量)
  • 一次性导入、永不再跑 → his(与 ful 严格区分:ful 是周期性调度的全量重刷,his 是导入后归档的一次性快照)

7. 分区与存储策略

  • 分区字段dt(日期,必须);hr(小时,按需)
  • 存储格式:ORC(列存)
  • 压缩orc.compress=NONE(咱不压缩,放弃一部分磁盘换 CPU/查询速度与 debug 友好度 ,后期做冷热数据时再考虑压缩)
  • 纠删码:当前关闭(保持 3 副本),等 Worker 节点扩容后对冷数据启用

7.1 组合快照示例

拼团表 DWD 快照策略:每日增量快照 + 近 7 天全量快照 + 全量拉链表

8. raw 层与 ods 层的职责约定

这是本数仓的核心数据契约,所有 raw / ods 层作业都必须遵守。

8.1 raw 层:schema-on-read landing

  • 全字段 STRING:raw 层所有表的业务字段(不含 dt 分区)一律 STRING 类型,源库是什么类型、含不含空值、超不超长,raw 全部原样落盘
  • 同步任务不做类型转换:DataX ini 里不写 columnType 的类型映射(或统一填 string),CSV 导入时 SparkSQL 读取后也不 CAST
  • 为什么
    • 类型转换失败会中断同步链路,而 raw 是链路出入口,必须稳定
    • 源库偶发脏数据(超长、空串、非预期类型)不会丢失,可追溯
    • 同一份 raw 可以被 ods 重建多次(用不同清洗逻辑),而不需要回源库再同步

8.2 ods 层:类型转换与脏数据识别

  • ods 是类型化的第一层:从 raw 的 STRING 字段做 CAST / TRY_CAST,输出真正类型化的干净表
  • ods 是脏数据拦截线:转换失败的数据不能静默丢弃,必须有可观测的出口(打标记字段、落到 _err 分区、或写入专门的数据质量日志表,具体策略 TBD)
  • ods 不做业务语义加工:只做"把字符串变回正确类型 + 空值兜底 + 去重",不做字段合并、维度关联、指标计算等 dwd 才做的事

8.3 其他框架字段

raw 层是否需要 etl_load_time / src_file / src_row_no 等框架字段,暂不做统一要求,后续实际接入第一批表时再根据需要补充到本节。

8.4 ods 层类型映射参考

总则:raw 层一律 STRING 兜底同步;类型化在 ods 层完成。下表为 ods 层 CAST 目标类型的参考表,具体字段可按业务需要微调(如小金额字段可下沉到 DECIMAL(16,2))。

8.4.1 PostgreSQL → Hive

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:避免上游扩位(int4int8)时下游被动改表
  • 小数统一 DECIMAL(20,4):覆盖绝大多数金额/比率场景;特殊精度需求(如高精度科学计算)单独评估
  • 布尔用 TINYINT(0/1):Hive 的 BOOLEAN 与 ORC/Spark 生态兼容性没有 TINYINT 稳定
  • 时间类型全部 STRING:保留源端字面量,dwd 层再按需 to_timestamp / to_date

8.4.2 Elasticsearch → Hive

(待补,首批 ES 埋点库接入时落地)

9. 相关文档