Przeglądaj źródła

docs(kb): 93 新增 ADR-03 零点漂移决策草案

raw 层 where 右界 +1 天 buffer 覆盖漂移(固定区间、可复跑)
ods 动态分区归位、跨 dt 不去重(拉链表基础)
ods 写入模式两方案并列(INSERT OVERWRITE / INSERT INTO+单分区去重),
默认不预设、实施时按特殊表选
实施时迁入 kb/12 同步方案

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tianyu.chu 1 tydzień temu
rodzic
commit
23a4e74752
1 zmienionych plików z 42 dodań i 0 usunięć
  1. 42 0
      kb/93-架构决策.md

+ 42 - 0
kb/93-架构决策.md

@@ -46,3 +46,45 @@
   - 负面:单 DS 任务节点内部的批量(多 ini + `-parallel`)场景无法在 DataX 层散到多 worker,要分布式必须在 DS 层拆成多 task
 - **候选方案**:保留 DataX 两层分发——否决,"两层独立随机"破坏 DS worker group 语义
 - **反悔条件**:DS 换成无 worker group 支持的调度器;或单 task 内批量规模大到 DS 拆分成本过高
+
+### ADR-03 零点漂移决策
+
+- **状态**:草案(2026-04-23 讨论成型;实施 ods 时迁入 kb/12-同步方案.md 对应节,本 ADR 届时撤回)
+
+- **背景**:
+  T+1 按 `update_time` 过滤单日窗口 `[day-start, day-stop)` 同步时,源库在同步执行期间持续写入——跨零点的记录 `update_time` 会从 N 号"漂到" N+1 号,单日窗口**无法捕获漂移记录**。
+  
+  漂移概率和漂移数据量的相关变量:
+  - **漂移窗口 = 执行时刻距零点的小时数**。越晚跑漂移窗口越大
+  - 漂移窗口越大,单条记录的漂移概率越高
+  - 漂移窗口内用户越活跃(业务写入高峰落在漂移窗口内),漂移数据量越多
+  
+  **业界一般做法**:小数据量、用户低活跃度的场景下,通常凌晨 0:30 前后跑 T+1,漂移窗口 30 分钟、活跃度低,单日固定窗口 `[day-start, day-stop)` 即可,**忽略极少量漂移数据**是可接受的工程权衡。
+  
+  **本项目特殊性**:业务高峰在凌晨 6 点前,同步定时必须避开高峰定在 6 点后,漂移窗口 6 小时;且若干用户行为集中在 0-6 点,漂移窗口和活跃度两个放大因子都踩中,漂移不能忽略——需要单独设计。
+  
+  极端场景佐证:某用户习惯 0-6 点更新自己的数据,若走业界做法的单日固定窗口,数仓永远只能看到该用户最早的 `create_time` 版本,最新状态永远抓不到。
+
+- **决策**:
+  - raw 层 where 右界扩展为次日同期:`where update_time >= '{day-start} 00:00' AND update_time < '{day+1-stop} 00:00'`,在关闭侧加 1 天 buffer 覆盖漂移
+  - buffer 取 1 天而非 N 小时:**定时可变更**(同步执行时刻从 6 点调到 3 点或 8 点都不影响窗口语义)+ **可维护**(固定区间可复跑、可回刷)
+  - raw 层不纠正分区漂移:所有抓到的记录(含"漂到次日"的)按 ini `dt = stop-1` 统一落当日分区
+  - 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 层**跨 dt 不去重**:同一业务 id 允许在多个 dt 分区并存(每条代表一个"时间段状态快照")——上层拉链表(SCD Type 2)的必要基础
+
+- **后果**:
+  - 正面:不漏漂移记录;raw 简单(只管多抓、不管分对);ods 动态分区自动归位;同 id 多 dt 并存作为拉链表底层
+  - 负面:raw 每日抓量约翻倍;ods SQL 必须统一动态分区写法(方案 A 或 B),开发规范需明文约束
+
+- **候选方案**:
+  - 单日窗口 `[day-start, day-stop)` 固定区间(无 buffer):业界小公司通用做法,本项目因漂移窗口 + 活跃度双放大**否决**
+  - 动态 `now` 右界 `[day-start, now)`:可复现性、复跑、补数场景难处理,**否决**
+  - 精确匹配漂移的半天 buffer `[day-start, day+6h-stop)`:和具体同步时刻耦合,调定时就得改窗口,**否决**
+  - ods 跨 dt 按 `(pk, max(update_time))` 去重:破坏拉链表基础,**否决**
+  - 源库 `REPEATABLE READ` snapshot isolation:业务库长事务风险,**否决**
+  - CDC(PG 逻辑复制 / MySQL binlog 流读):架构正路,需独立立项,**本阶段不取**(见 kb/12 CDC 演进节)
+
+- **反悔条件**:迁 CDC;或 1 天 buffer 的存储 + ods 计算成本显著超过 CDC 实施成本