# 数仓分层与建模 > 本文档记录 `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 状态) **核心规则: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`) | **为什么这么拆**: - DWD `_apd_d` 只追加,与 Hive 列存(ORC)"只追加 + 分区"模型天然契合,不需要 UPDATE - 状态查询("截止 dt 各状态订单数")走拉链表 `is_current` 切片或按 dt 切片,直观 - 跨表 JOIN("下单数 - 退单数 = 实际下单数")是维度建模的**正常能力**,不是代价 - 这是 Hive / 列存数仓的事实标准做法(阿里 OneData / 字节 / 美团数仓主流) **典型场景:** | 场景 | 事件表(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`。 ## 6. 数据同步策略 > 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` 是导入后归档的一次性快照) ## 7. 分区与存储策略 - **分区字段**:`dt`(必须),`STRING` 类型,格式 `YYYYMMDD`(如 `20260101`);`hr`(小时,按需) - **存储格式**:ORC(列存) - **纠删码**:当前关闭(保持 3 副本),等 Worker 节点扩容后对冷数据启用 ### 7.1 组合快照示例 **拼团 DWD/DIM 组合**:每个动作(发起 / 审核 / 拒绝 / 重新提交 / 成功 / 失败)独立 `dwd_trd_group_{动作}_apd_d` 事件表承载不可变流水;实体当前状态进 `dim_trd_group_zip_d` 拉链表,每次状态变更生成新拉链行。详见 §5.5。 ### 7.2 各层分区语义 各层 `dt` 分区键代表的时间含义、对漂移和重复的容忍度、分区间关系不同,是设计 ETL 任务和理解查询行为的基础。 | 层 | 分区键语义 | 时间字段来源 | 漂移容忍 | 重复容忍 | 分区间关系 | |---|---|---|---|---|---| | Raw | 批次日(系统时间) | `dt = start_date`(业务日) | **容忍** | **容忍**(同 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 | 报表快照日(业务时间) | 报表生成日 | 不容忍 | 不容忍 | 独立,一行可含多统计周期指标 | **两个容忍度的概念:** - **漂移容忍** = "某条记录落在了错误的 dt 分区里,能不能接受?" 描述**数据 vs 分区归属**的正确性 - Raw 容忍:9 号 `update_time` 数据落在 Raw `dt=8 号` 没关系,下游会归位 - **重复容忍** = "同一个 pk 在分区内或跨分区出现多次,能不能接受?" 描述**分区内 / 跨分区的唯一性**约束 - Raw 容忍:一次抽取窗口内同 pk 可能被捞多次,不需要去重 - ODS 分区内不容忍(需去重),跨 dt 容忍(保留轨迹) - DWD 事实表双不容忍:事件就是一次,多一条就是错 - DIM 拉链表"多版本非重复":同 pk 多行表示状态演化 **两个容忍度的组合反映各层的核心取舍:** - Raw 双容忍 → 抽取简单,绝不丢数据 - ODS 漂移不容忍 + 跨分区重复容忍 → 归位 + 保留轨迹 - DWD 事实表双不容忍 → 事件唯一,业务语义精确 - DIM 拉链表特殊 → 多版本不是重复 - DWS / ADS 双不容忍 + 分区间明细冗余 → 聚合结果唯一,存储冗余换查询速度 ### 7.3 各层职责与设计要点 各层基础职责见 §2 表格;本节聚焦每层的关键设计取舍。 #### Raw 层 > 基础职责见 §2 + §8.1。 - **写入窗口**:抽取窗口 `[day-start, day+1-stop)`(48 小时宽),所有抓到的记录统一落 `dt = start_date`(业务日)分区 - **设计理由**:宽窗覆盖"零点漂移"和"覆盖式更新下的永久丢失",保证数据永不丢失 - **代价**:分区里混有"未来时间"的记录 + 同 pk 可能重复出现,接受这两个代价换抽取逻辑简单 #### ODS 层 > 基础职责见 §2 + §8.2。 - **写入**:Spark SQL 动态分区 `PARTITION (dt)`,按 `DATE(update_time)` 分发,把 Raw 漂移数据归位到正确分区 - **两种写入模式**(可表级混用): - **方案 A(INSERT OVERWRITE)**:每次 ODS 跑批覆盖对应 dt 分区;数据不丢失,因为 Raw 最多漂一天,次日 Raw 必然抓到 - **方案 B(INSERT INTO + 分区内 `(pk, max(update_time))` 去重)**:保留每日 ODS 跑时刻的 `dt=X` 版本轨迹,用于审计、回溯,防止覆盖后丢失中间快照 - **关键约束**:**跨 dt 不去重**。同一 pk 允许在多个 dt 分区并存,每条代表一个"时间段状态快照"——是上层 DIM 拉链表(SCD Type 2)的必要基础 #### DWD 层(事实明细) > 基础职责 + 事件 vs 状态拆分原则见 §5.5。 - **分区**:业务时间(下单日、支付日、事件发生日),不是抽取日 - **写入**:每天**冗余跑近 3 日**,兜底 ODS 漂移(虽然 ODS 已归位,但 ODS 漂移修正后 DWD 需要回算) #### DIM 层(维度) - **职责**:承载业务实体的状态 - **建模**:按表特征选型,**不统一** | 表特征 | 建模方式 | 分区策略 | 写入模式 | |---|---|---|---| | 大 / 中维度表(用户、商品、商户) | **拉链表 SCD2** | 不分区(或 `is_current` 二级分区) | 当日变更 pk:原行 `end_date` 置昨天 + 新行 insert | | 小高频变更维表(类目、地区、配置) | **每日全量快照** | 业务时间分区 | 每日全量覆盖 `dt=today` 分区 | | 极小不变表(字典、枚举) | 单表全量 | 不分区 | 偶尔全量覆盖 | **选型判据:变更率 × 保留天数** - 变更率低(< 1% / 日)+ 保留长:拉链更优 - 变更率高(> 20% / 日)+ 保留短:快照更优 **拉链表分区的特殊性:** - 拉链表每行是"状态生效区间",不是"时间点",天然不适合按业务时间分区 - 可以按 `is_current='Y'/'N'` 做二级分区加速"当前状态"查询 - 大表可以按 `start_date` 年份 / 月份做粗粒度分区控制扫描范围 #### DWS 层 - **职责**:面向分析主题的轻度汇总,**用冗余存储换查询性能** - **组织方式**:`主题 × 粒度 × 统计周期` 一张表 - `dws_user_order_1d`、`dws_user_order_7d`、`dws_user_order_30d` - `dws_shop_order_1d`、`dws_shop_order_7d`、`dws_shop_order_30d` - **分区语义**:业务截止日。每个分区自包含完整周期聚合(7 日表每天分区含过去 7 天完整聚合) - **分区间冗余是刻意设计**:滑动窗口天然重叠,换取"一次分区裁剪命中答案"的查询速度 - **来源**:DWD 事实表 + DIM 维度表 join 聚合 - **增量计算优化**:`7d = 今日 1d + 昨日 7d - 7 天前 1d`,减少 DWD 扫描压力 #### ADS 层 - **职责**:面向具体报表 / 应用,把多个统计周期拼成一行,BI 直接用 - **组织方式**:`报表主题` 一张表,`一行多周期` - `user_id | order_cnt_1d | order_cnt_7d | order_cnt_30d | gmv_1d | ... | dt` - **分区语义**:报表快照日(历史报表不可被后续数据改动,保审计性) - **生成方式**:多张 DWS 同分区日 join 而来 - **可重放性**:所有 ADS 数据都能从 DWS / DWD / DIM 重算;实务中用 TTL 定期删除历史分区,要查老报表就触发任务重放 ### 7.4 分区与建模设计原则 **原则 1:Raw / ODS 是系统时间,DWD 及以上是业务时间** - **Raw / ODS**:系统时间(`update_time` 及 ETL 批次时间)。贴源层不解释业务语义,只忠实反映"数据库里发生了什么" - **DWD / DIM / DWS / ADS**:业务时间(`order_create_time` 等)。面向分析,分区语义对齐业务问题("8 号下了多少单"指的是用户 8 号下单的行为,不是数仓 8 号抓到的数据) **原则 2:漂移容忍度和重复容忍度自下而上递减** - Raw:双容忍(漂移 + 重复都接受) - ODS:漂移不容忍、分区内重复不容忍、跨分区重复容忍(保留 `update_time` 轨迹) - DWD / DIM / DWS / ADS:漂移不容忍、分区内重复不容忍(DIM 拉链的"多版本"不算重复) **原则 3:分区语义和查询语义对齐** 查询条件里的 `where dt='8 号'` 应该和用户的直觉对齐: - 分析师问"8 号下单数" → 查 DWD 事实表 `where dt='8 号'`,分区键是业务下单日 - 运维排查"8 号这批抓了什么" → 查 Raw `where dt='8 号'`,分区键是批次日 两种"8 号"语义不同,分别由不同层承载,不混淆。 **原则 4:事实与维度解耦(DWD vs DIM 并列)** - **DWD 事实表**:记录不可变业务事件,只追加,业务时间分区 - **DIM 维度表**:记录实体状态,拉链或快照,按规模和变更率选型 - 跨表计算(下单数 - 退单数 = 实际下单数)是维度建模的**正常能力**,不是代价 - 不把两者混在一层 → 建模方法、分区策略、更新方式清晰分离 **原则 5:ODS 为下游保留最大灵活性** - ODS 跨 dt 不去重 → 保留 `update_time` 轨迹 → 支持 DIM 拉链表建设 - ODS 不预设下游建模方式 → DWD 可按表特征自由选事件化,DIM 可按规模选拉链 / 快照 **原则 6:上层数据可重放,下层数据不可重放** - **Raw / ODS**:源头层,业务库覆盖式更新会导致历史无法还原,**不可重放**,要长期保留 - **DWD / DIM**:规整层,理论可从 ODS 重放,实务按"不可重放"管理,长期保留 - **DWS / ADS**:派生层,可重放(前提上游数据还在),用 TTL 控制存储成本 - **保留周期自下而上递减**(越接近派生数据的层,TTL 越短) ## 8. raw 层与 ods 层的职责约定 这是本数仓的核心数据契约,所有 raw / ods 层作业都必须遵守。 ### 8.1 raw 层:schema-on-read landing > schema-on-read = 数据写入时不解释类型,读取时再按需解析。这是小数仓 / 列存 / 数据湖共用的范式,与传统 RDBMS 的 schema-on-write 形成对照。 **全 STRING 的设计理由:** 1. **隔离源端类型变化**:源系统改字段类型(`int4` → `bigint`、`varchar` → `text`)raw 入库零影响;类型解释下移 ods,源端变化只动 ods CAST 表。raw 表 schema 长期稳定,避免反复 ALTER 触发 metastore 高频变更 2. **同步阶段不可失败**:raw 是回源链路兜底层,类型转换中单条脏数据可能让任务整个失败(如某行 `int4` 字段含 `'N/A'` → CAST 异常);STRING 永不失败,全量入仓后再到 ods 拦截脏数据 3. **保留原始精度与原文**:CAST 可能丢精度(`NUMERIC(38,18)` → `DECIMAL(20,4)` 截断)、丢时区(`timestamptz` 反序列化)、改格式(日期字符串 `'20260101'` 反序列化后再格式化可能不一致);STRING 原汁原味,ods 想怎么解析都行 4. **脏数据可观测**:业务库历史脏数据(`'1900-00-00'` 日期、`'-1'` 状态、超长字段)必须先入仓再观测;类型化阶段静默丢弃 / 报错跳过就再也看不到。STRING 100% 入仓 + ods 显式标记 / 分流(见 §8.2 脏数据拦截线) 5. **schema-on-read 范式契合**:小数仓 / 列存(ORC)读取时类型解析成本极低,所以 raw 做 schema-on-read landing + ods 做 schema-on-write 是标准分工;vs 传统 OLTP 的 schema-on-write 必须入库时定型,灵活性差 **何时可破例**:上述理由是工程权衡,不是教条。某个 raw 表场景如果这 5 条 ≥3 条不适用,可单独评估破例方式(如:数据源本就是 self-describing 的 NDJSON / Parquet / Avro 时保留单列裸文本 / 自带类型直存;schema 由上游严控且无脏数据风险时直接 typed 列入仓)。破例必须在 PR / 设计稿明示理由,不是默认。 **规则:** - **全字段 STRING**:raw 层所有表业务字段以及 `dt` 分区字段一律 `STRING` 类型 - **同步任务不做类型转换**:DataX ini 里不写 `columnType` 的类型映射(或统一填 `string`),CSV 导入时 SparkSQL 读取后也不 `CAST` - **外部表兜底**:raw 层建表一律用 `CREATE EXTERNAL TABLE`,DROP TABLE 只删元数据,HDFS 数据保留;raw 作为链路兜底层,误删元数据时数据仍可 `MSCK REPAIR` / 重建元数据恢复,无需回源库重同步 ### 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` | **说明**: - 整数统一 `BIGINT`:避免上游扩位(`int4` → `int8`)时下游被动改表 - 小数统一 `DECIMAL(20,4)`:覆盖绝大多数金额/比率场景;特殊精度需求(如高精度科学计算)单独评估 - 布尔用 `TINYINT`(0/1):Hive 的 `BOOLEAN` 与 ORC/Spark 生态兼容性没有 TINYINT 稳定 - 时间类型全部 `STRING`:保留源端字面量,dwd 层再按需 `to_timestamp` / `to_date` #### 8.4.2 Elasticsearch → Hive (待补,首批 ES 埋点库接入时落地) ## 9. 相关文档 - [命名规范](21-命名规范.md) — 表/字段命名五段式 - [指标体系](22-指标体系.md) — 原子/派生指标定义 - [标签体系](23-标签体系.md) — TDM 层画像/标签