Explorar o código

docs(kb/93): 新增 ADR-11 DWD 补数任务流 + 复原 ADR-09

ADR-11 记录 dwd 补数 backfill 与日调度 incremental 分离的决策:
独立补数 SQL + 独立 DS workflow + payment 业务时间过滤 [start,end]
+ ods.dt 扫描 [start-1, 不限](上界放开扫到最新 ods, 不猜漂移天数)
+ 范围全覆盖。

ADR-09 git 复原为原始 N=2 日调度版。kb/92 changelog 同步。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tianyu.chu hai 2 semanas
pai
achega
b669941744
Modificáronse 2 ficheiros con 63 adicións e 79 borrados
  1. 0 0
      kb/92-重构进度.md
  2. 63 79
      kb/93-架构决策.md

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
kb/92-重构进度.md


+ 63 - 79
kb/93-架构决策.md

@@ -354,104 +354,47 @@
   - dim 表数据量增长导致 sche shuffle 性能下降,评估改"分区裁剪 + 局部重算"
   - 业务库消除"主动置 NULL" 场景后,可放宽到字段级 COALESCE 简化写法
 
-### ADR-09 DWD 事件表回算窗口 N
+### ADR-09 DWD 事件表跑批回算窗口 N=2
 
-- **状态**:草案(待 PG 实测 delta_days 分布定 N)
-
-  **修订历史**:
-
-  - 2026-05-10:初版采纳 N=2(commit `a9b6eaa`),基于"业务库 update_time 严格同步刷新"通用假设
-  - 2026-05-27:dwd.dt=20260522 验数缺 31%(51,684 vs PG 74,925)暴露 N=2 在订单状态机长生命周期场景下不够。合并原 ADR-11 重评估到本 ADR,方向"扩大 N"待 PG 实测分布定
-
-- **背景**:
-
-  本项目 ods 层按 ADR-03 严格按 `update_time` 归位 dt(双源 union 完整捕获 `update_time ∈ [T-1, T)` 范围所有版本)。dwd 事件表按业务时间(如 `payment_success_time`)分区。
-
-  业务库存在**两类漂移**:
-
-  **1. 跨零点 OLTP 漂移**(短期,≤ 1 天)
-
-  业务事件时间与 `update_time` 不严格同步刷新。典型场景:`payment_success_time = T-1 23:59:59.500`,ORM 延迟 600ms 刷 `update_time = T 00:00:00.100`,该行落 `ods.dt=T`,事件本应归 `dwd.dt=T-1`,但 dwd 单分区扫 `ods.dt=T-1` 拿不到。N=2 兜底足够 cover。
-
-  **2. 业务状态机漂移**(长期,5~14 天)
-
-  订单从支付(status=101)→ 发货(103)→ 收货(104)→ 退款(301),每次状态变更刷 `update_time`。一个 5-22 支付的订单可能 5-26 才有最后一次 update。在补数场景下(DataX 用 `update_time` 锚点拉),PG 不保留历史快照 → 只能拉到当前最新版本归位到漂走的 `ods.dt`。
-
-  2026-05-27 `dwd.dt=20260522` 验数缺 31% 即此问题:raw_ods 补数 5-26 拉 [5-21, 5-23) 窗,5-22 订单 `update_time` 已漂到 5-25~5-26 → ods 归位漂走 → dwd N=2 看不到。
-
-- **决策**:
-
-  - dwd 事件表 sche 回算近 N 日:扫 `ods.dt IN [${dt}-N+1, ${dt}]` + 过滤 `DATE(business_event_time) IN (${dt}, ${pdt})`
-  - 写入 `dwd.dt IN (${dt}, ${pdt})`:`INSERT OVERWRITE PARTITION (dt)` 动态分区(kb/26 §8 默认 DYNAMIC mode,只覆盖 SELECT 出现的 dt,不动其他历史分区)
-  - **N 按业务实测 delta_days 分布 P95~P99 选定**,候选 N=5 / 7 / 14
-  - **配套机制**:回算窗 N(防)+ 定期校验对账 Hive vs PG(查)**双保险**——订单类事实表业界标准做法
-
-- **业界 N 取值经验**(按业务场景,给团队新成员参考):
-
-  | 业务场景 | 业界 N |
-  |---|---|
-  | 维度表 / 通用聚合 / 短生命周期事件 | N=2~3 |
-  | 订单 / 交易 / 物流(长状态机) | N=5~7 |
-  | 金融退款 / 法律审批 / 极长尾 | N=14~30 |
-
-  **N 不是 universal 默认值,由业务漂移分布决定**。实测 `delta_days = DATE(update_time) - DATE(business_event_time)` 分布,取 P95~P99 即得 N。早期 N=2 的取值是按"维度表 / 通用聚合"档位拿来直接套订单事实表,是经验失配。
+- **状态**:已采纳
 
-- **权威来源**:
+  本项目 ods 层按 ADR-03 严格按 `update_time` 归位 dt(双源 union 完整捕获 `update_time ∈ [T-1, T)` 范围所有版本),dwd 事件表理论上单分区扫 `ods.dt=T-1` 即可拿到所有"业务时间 T-1 + update_time T-1"的事件。
 
-  - **Kimball 维度建模理论**(Ralph Kimball, _The Data Warehouse Toolkit: The Definitive Guide to Dimensional Modeling_, 3rd ed., Wiley, 2013)
-    - 第 3 章 Retail Sales:事实表按业务时间分区原则
-    - 第 12 章 Late-Arriving Facts:迟到事实的回算窗 N 天处理范式
-  - **阿里巴巴数据中台方法论**(《阿里巴巴大数据之路:数据中台之 OneData》第 11 章 事实表设计):DWD 事实表按业务时间分区 + 回算窗设计
-  - **现代湖仓引擎对照**:Apache Hudi / Iceberg / Delta Lake 的 `MERGE INTO` 是回算窗的引擎级替代,需 Spark 3+
+  但业务库 OLTP 写入业务事件时间(如 `payment_success_time`)与 `update_time` 不一定严格同步刷新——典型场景:跨零点支付,`payment_success_time = T-1 23:59:59.500`,业务库 ORM 延迟 600ms 刷 `update_time = T 00:00:00.100`,该行落 `ods.dt=T`;事件本应归 `dwd.dt=T-1`,但 dwd 单分区扫 `ods.dt=T-1` 拿不到。这种漂移在业务高峰跨零点场景下不可忽略。
 
-- **实测 SQL**(决策前必跑):
+  本项目第一张 dwd 事件表 `dwd_trd_order_pay_apd_d` 落地反复讨论后定下范式(5/9 一度采纳"信 OLTP 契约不回算" → 5/10 自破契约编漂移场景 → 复盘 sessions 5/9 T8-T10 发现违反共识 → 用户重新拍"后端不可信"→ 业界范围 N=2/3 拍 N=2 → commit `a9b6eaa`)。
 
-  ```sql
-  SELECT (DATE(update_time) - DATE(payment_success_time)) AS delta_days,
-         COUNT(*) AS cnt
-  FROM public.card_group_order_info
-  WHERE payment_success_time >= '2026-01-01'
-    AND payment_success_time <  '2026-05-01'   -- 取已稳定的老数据
-    AND status IN (101, 103, 104, 105, 106, 301, 302)
-  GROUP BY (DATE(update_time) - DATE(payment_success_time))
-  ORDER BY delta_days;
-  ```
+- **决策**:DWD 事件表跑批回算近 N=2 日:
 
-  按 P95~P99 取 N。
+  - sche sched=T 时,扫 `ods.dt IN (${dt}, ${pdt})`(即 T-1 + T-2)
+  - 过滤业务时间 `DATE(business_event_time) IN (${dt}, ${pdt})`(如 dwd_pay 用 `payment_success_time`)
+  - 写入 dwd `dt IN (${dt}, ${pdt})`:`INSERT OVERWRITE PARTITION (dt)` 动态分区,kb/26 §8 项目默认 DYNAMIC mode 只覆盖 SELECT 出现的 dt,不动其他历史分区
+  - `dwd.dt=T-1` 在 sched=T 跑批时首次写入;sched=T+1 跑批时通过扫 `ods.dt=T` 兜回业务时间 T-1 漂移到 `ods.dt=T` 的事件,重写 `dwd.dt=T-1`
+  - N=2 业界主流(阿里 OneData / 字节 / 美团默认)
 
 - **后果**:
 
   - 正面:
-    - cover 短期跨零点 + 长期业务状态机两类漂移
-    - 与业界订单类事实表设计对齐
+    - 兜底跨零点 ods 漂移(业务库 `update_time` 不严格同步刷新场景)
     - 不依赖 OLTP 应用层契约的强假设
+    - 与 kb/20 §7.3 通用兜底一致
   - 负面:
-    - 每次 dwd sche 扫 ods 数据量增加 N/2 倍(N=7 时 3.5x);写入分区数同步扩大
+    - 每个 dt 分区被回算 2 次(首次 + 次日兜底),写入 / 计算成本上升 2×
     - 动态分区写入需注意 dynamic mode 默认行为(kb/26 §8 已实测)
 
 - **候选方案**:
 
-  - **N=1 单分区不回算**(5/9 一度采纳):基于"OLTP 严格同步刷新"强假设,业务库实际不可信,跨零点漂移会永久丢失事件——否决
-  - **N=2 跨零点兜底**(2026-05-10 一度采纳):cover 短期跨零点漂移 OK,未 cover 业务状态机长生命周期——本 ADR 重评估
-  - **N=5~7 订单类**:cover P95~P99 漂移,业界订单类常见取值
-  - **N=14~30 极长尾**:cover 退款窗(电商 7-14 天)+ 法律周期;本项目订单场景过保守
-  - **MERGE INTO**(Spark 3+ / Iceberg / Hudi / Delta Lake):不需回算窗,引擎原生 upsert;Spark 2.4 不支持,本阶段不取
-  - **延后 1 天跑批**(sched=T+1 写 `dwd.dt=T-1`,扫 `ods.dt IN (T-1, T)`):下游标签延迟 1 天,业务侧不可接受——否决
-  - **跳过 raw/ods 直接从 PG 灌 dwd**(一次性 manual SQL):绕过链路 + 技术债——否决
+  - **N=1 单分区不回算**(5/9 一度采纳):基于"业务库 update_time 与业务事件时间同步刷新 OLTP 契约"假设;OLTP 后端实际不可信,跨零点漂移会永久丢失事件——否决
+  - **N=3 回算近 3 日**:偏保守,金融 / 强稳定性场景;本项目业务侧无此强稳定性需求,2× 写入对齐业界默认即可——否决但留作"业务库延迟 > 1 天频繁时上调"反悔触发
+  - **N=7 回算近 7 日**:极端保守,少见——否决
+  - **MERGE INTO**(Spark 3+ / Iceberg / Hudi / Delta Lake):不需回算逻辑,引擎原生 upsert 处理漂移——本项目 Spark 2.4 不支持,本阶段不取
+  - **延后 1 天跑批**(sched=T+1 写 dwd.dt=T-1,扫 ods.dt IN (T-1, T)):dwd 数据延迟 1 天可用,下游标签延迟,业务侧不可接受——否决
 
 - **反悔条件**:
 
-  - 业务漂移分布显著变化(业务流程改造)→ 重跑实测 SQL 调 N
-  - 数据量增长后 N 天回算扫描成本不可接受 → 降 N + 加重定期校验频率 / 阈值
-  - 迁 Spark 3+ / Iceberg / Hudi / Delta Lake → 改 `MERGE INTO`(更优)
-  - 业务库能严格保证 OLTP 应用层契约(`update_time` 与事件时间同步刷新)→ 降到 N=1(实际很难保证)
-
-- **实施依赖**(落定 N 后执行):
-
-  - 跑 PG SQL 拿 delta_days 分布定 N
-  - 改 `jobs/dwd/trd/dwd_trd_order_pay_apd_d.sql` 回算窗 2 处过滤(`ods.dt` + `payment_success_time`)同步扩大
-  - 重跑 dwd 补数验证缺数回归
-  - 定期校验脚本(GMV + count 对账,复用 `workspace/20260527/gmv_check.py`)扩成 DS 周校验工作流
+  - 业务库延迟 > 1 天的漂移场景频繁出现(如系统升级 / 故障恢复多日数据回流),N 上调到 3 或更大
+  - 迁 Spark 3+ / Iceberg / Hudi / Delta Lake 后改 MERGE INTO(更优)
+  - 业务侧能严格保证 OLTP 应用层契约(update_time 与事件时间同步刷新),可降到 N=1(实际很难保证)
 
 ### ADR-10 TDM 跨层下钻 DWD(1 期专用,dws 单下游场景)
 
@@ -496,3 +439,44 @@
   - dwd 30 天 / 全年扫描 Spark 性能不可接受(1 期数据量级判定失效)
   - 2 期单买 / 限时 / 活动等其他 `order_type` 同样需要标签 → 演进到 A 多维粒度
   - 多下游接入 dws 形成"重复聚合 × N"的代价上升 → 演进到 A
+
+### ADR-11 DWD 事件表补数任务流(backfill 与 incremental 分离)
+
+- **状态**:已采纳
+
+- **背景**:规划 dwd 补数(backfill,调度中断后回刷历史区间)能力。
+
+- **决策**:dwd 补数走独立任务流,与日调度(ADR-09)分离:
+
+  - 独立 SQL `jobs/dwd/trd/dwd_trd_order_pay_apd_d_backfill.sql`
+  - 独立 DS workflow,手动触发传 `start` / `end`,不挂 schedule,不加 DEPENDENT(补数时 ods 已就绪)
+  - payment 业务时间过滤 `[start, end]`(双边,决定覆盖哪些 dwd 分区)
+  - ods.dt 扫描 `[start-1, 不限]`:
+    - 下界 `start-1`:往前 1 天对齐 ADR-09 日调度 pdt buffer(cover create_time 早于 payment 落前一天的边缘)
+    - 上界不限:扫到最新 ods,无论 `update_time` 漂多远都捞到(核心 —— 不猜漂移天数)
+  - 范围内每个 dwd 分区全覆盖(`INSERT OVERWRITE` 冲掉旧 / 缺失版本)
+  - 前提:ods 已完整(raw/ods 同步已 cover 漂移后的 `update_time`)
+
+- **后果**:
+
+  - 正面:补数完整修复任意漂移天数(不限 ods.dt 结束);与日调度窄窗互不污染;不依赖"猜 N"
+  - 负面:维护两份 dwd SQL(日调度 ADR-09 + 补数本 ADR);补数扫描量大(一次性、大数据集群非业务库,可接受)
+
+- **候选方案**:
+
+  - 扩大 ADR-09 日调度 N(如 N=5~7)统一处理日调度 + 补数:日调度每天扫 N 天成本翻倍;长中断(> N)仍救不了;漂移天数不可预知 → 否决。补数"不限 ods.dt 结束"比固定 N 稳
+  - 补数按天循环跑日调度 SQL:中断长时 `update_time` 漂走,窄窗拉不到漂移版本 → 否决
+  - 跳过 raw/ods 直接从 PG 灌 dwd:绕过链路 + 技术债 → 否决
+  - CDC(Debezium / Flink CDC):保留完整变更日志根治"PG 不保留历史" → 重组件本阶段不上
+
+- **反悔条件**:
+
+  - 上 CDC → 补数任务流可下线
+  - 迁 Spark 3+ / Iceberg / Hudi → `MERGE INTO`
+  - dwd 事件表增多 → 补数 SQL 抽象成通用模板
+
+- **运维定位**:
+
+  - 补数是调度中断的运维兜底;日调度保持连续(中断 ≤ N=2 天由 ADR-09 自愈),超期用本任务流回刷
+  - 调度中断越久,补数 `start` 越往前,覆盖区间越大
+  - 业务时间远早于补数 `start` 的极端漂移,由定期校验对账(Hive vs PG)兜底

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio