Przeglądaj źródła

docs(kb): 93 ADR-03 ods 写入定稿——双源 union

tianyu.chu 1 tydzień temu
rodzic
commit
b0ddc02c14
1 zmienionych plików z 23 dodań i 4 usunięć
  1. 23 4
      kb/93-架构决策.md

+ 23 - 4
kb/93-架构决策.md

@@ -72,20 +72,39 @@
   - buffer 取 1 天而非 N 小时:**定时可变更**(同步执行时刻从 6 点调到 3 点或 8 点都不影响窗口语义)+ **可维护**(固定区间可复跑、可回刷)
   - raw 层不纠正分区漂移:所有抓到的记录(含"漂到次日"的)按 ini `dt = start_date`(业务日)统一落当日分区,分区内允许含次日漂移数据
   - ods 层 Spark SQL 用**动态分区** `PARTITION (dt)`,按每行 `update_time` 真实日期归位
-  - ods 写入模式两方案并列(默认方案**本 ADR 不预设**,实施时按具体表需求选 / 可表级混用):
-    - **方案 A:`INSERT OVERWRITE`(覆盖式)** —— 每次 ods 跑覆盖对应 dt 分区。不丢数据:**数据只会向后漂移一天**(某条 update_time=N 号 的记录若 N 号 raw 没抓到漂到 N+1 号,N+1 号 raw 必然抓到——漂移不会再漂到 N+2 号),N+1 号 ods 覆盖 dt=N 号 时能补齐
-    - **方案 B:`INSERT INTO` 追加 + 单分区内 `(pk, max(update_time))` 去重** —— 同 pk 在同 dt 分区内只保留最新 `update_time` 一条。保留"每日 ods 跑时刻的 dt=X 版本"轨迹(审计、回溯),防止覆盖后丢失"update 恰为当天"的中间快照
+  - ods 写入模式:`INSERT OVERWRITE` 覆盖式 + **双源 union `raw dt=T-1 + raw dt=T-2`**,按 `WHERE DATE(update_time) = T-1` 过滤,dedupe by `(pk, max(update_time))`。覆盖式写入支持重跑;双源 union 完整捕获 update_time ∈ [T-1, T) 范围的所有版本(含跨 raw 任务时段被业务覆盖更新跨日的早期版本)
+  - **为什么双源 union 必要**(具体例子):
+
+    **设定**:业务日 04-27,跑批日 04-28,raw 任务凌晨 03:00 跑批,业务库覆盖式更新会推 `update_time` 字段。
+
+    **订单 X 的 update 时间线**(业务库视角):
+    - 04-27 02:00:update,`update_time = 04-27 02:00`(状态 A)
+    - 04-28 02:00:再 update,`update_time = 04-28 02:00`(状态 B,跨日)
+
+    **raw 抓取过程**:
+    - raw dt=04-26 任务(04-27 03:00 跑批,窗口 [04-26, 04-28)):业务库 X 是状态 A,抓到 `update_time = 04-27 02:00` → 落 raw dt=04-26 / update_dt=04-27
+    - raw dt=04-27 任务(04-28 03:00 跑批,窗口 [04-27, 04-29)):业务库 X **已变成状态 B**(04-27 02:00 那条 update_time 字段被覆盖到 04-28 02:00),抓到 `update_time = 04-28 02:00` → 落 raw dt=04-27 / update_dt=04-28
+
+    **ods 04-28 跑批写 dt=04-27 分区**(`WHERE DATE(update_time)='04-27'`):
+    - **单源 raw dt=04-27**:X `update_time = 04-28 02:00`,DATE=04-28,不入 → ods dt=04-27 **漏 X 的 04-27 状态** ❌
+    - **双源 union raw dt=04-27 + raw dt=04-26**:raw dt=04-26 里 X `update_time = 04-27 02:00`(DATE=04-27,入),raw dt=04-27 里 X `update_time = 04-28 02:00`(不入)→ ods dt=04-27 **完整包含 X 的 04-27 状态 A** ✓
+
+    X 的 04-28 状态 B 第二天 ods 04-29 跑批时落 ods dt=04-28 分区,拉链表轨迹完整无断节。
+
+    业务高峰跨零点(如本项目订单 0-6 点高峰)下,跨日漂移是常态,双源 union 是必要机制不是 defensive。
   - ods 层**跨 dt 不去重**:同一业务 id 允许在多个 dt 分区并存(每条代表一个"时间段状态快照")——上层拉链表(SCD Type 2)的必要基础
 
 - **后果**:
   - 正面:不漏漂移记录;raw 简单(只管多抓、不管分对);ods 动态分区自动归位;同 id 多 dt 并存作为拉链表底层
-  - 负面:raw 每日抓量约翻倍;ods SQL 必须统一动态分区写法(方案 A 或 B),开发规范需明文约束
+  - 负面:raw 每日抓量约翻倍;ods SQL 必须统一动态分区写法 + 双源 union,开发规范需明文约束
 
 - **候选方案**:
   - 单日窗口 `[day-start, day-stop)` 固定区间(无 buffer):业界小公司通用做法,本项目因漂移窗口 + 活跃度双放大**否决**
   - 动态 `now` 右界 `[day-start, now)`:可复现性、复跑、补数场景难处理,**否决**
   - 精确匹配漂移的半天 buffer `[day-start, day+6h-stop)`:和具体同步时刻耦合,调定时就得改窗口,**否决**
   - ods 跨 dt 按 `(pk, max(update_time))` 去重:破坏拉链表基础,**否决**
+  - **ods 单源 raw dt=T-1(不 union raw dt=T-2)**:业务覆盖式更新跨日漂移场景下,漏 update_time=T-1 的早期版本(业务库该记录 update_time 已被推到 T 范围,raw dt=T-1 抓到的 DATE=T 不入),拉链表轨迹断一节,**否决**(具体例子见 §决策"为什么双源 union 必要")
+  - **ods `INSERT INTO` 追加 + 分区内去重**(早先并列方案 B):意图保留"每日 ods 跑时刻"的中间快照轨迹。但与"支持重跑"语义冲突(追加模式重跑会重复入数据),且方案 A 双源 union 已能完整捕获跨日漂移版本,方案 B 的"中间快照"价值不抵复杂度代价,**否决**
   - 源库 `REPEATABLE READ` snapshot isolation:业务库长事务风险,**否决**
   - CDC(PG 逻辑复制 / MySQL binlog 流读):架构正路,需独立立项,**本阶段不取**(见 kb/12 CDC 演进节)