Pārlūkot izejas kodu

docs(kb+test): 修正 partitionOverwriteMode 默认行为描述(实测 DYNAMIC)

实测 tests/integration/spark/idempotence/partition_overwrite_default.sql:
本环境 Spark 2.4 + CDH 6.3.2 + Hive ORC EXTERNAL TABLE 默认即 DYNAMIC,
不需显式设置。

回修 kb/26 §5 / §8 去掉"必设""硬前提""STATIC 是默认"等基于网上常识
未验证的描述;CLAUDE.md 时间变量摘要同步纠正;测试 README 加实测
结果节;kb/92 changelog 补一条。

新沉淀 memory/feedback_test_before_fix.md:判断"要不要改 X"前先写测试
探查实际行为,不凭网上常识就把"必须改"写入文档或动代码(CLAUDE.md
和 memory/ 在 .gitignore 不入仓,本地生效)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tianyu.chu 1 dienu atpakaļ
vecāks
revīzija
f3f76ba341

+ 8 - 6
kb/26-时间语义.md

@@ -86,7 +86,7 @@ DS 项目级 globalParams(poyee-data-warehouse 项目,所有工作流继承
 - **跨日漂移修正**:raw dt=T-2 因 48h 宽窗抓到的"漂到 T-1"的部分,被 union 进 ods dt=T-1(详见 ADR-03)
 - **dedupe**:`ROW_NUMBER() OVER (PARTITION BY id, DATE_FORMAT(update_time, 'yyyyMMdd') ORDER BY update_time DESC) = 1`,分区内取最新版本
 - **跨 ods dt 不去重**:同 pk 多 dt 分区并存 = 上层拉链表(SCD Type 2)的底层
-- **重跑幂等前提**:`spark.sql.sources.partitionOverwriteMode = DYNAMIC`(必设;STATIC 模式动态分区会清空全表,违反幂等,详见 §8
+- **重跑幂等**:动态分区 INSERT OVERWRITE 只覆盖 SELECT 出现的 dt,其他历史 dt 保留(实测见 §8 + tests/integration/spark/idempotence/
 
 ## 6. dwd / dws / ads 层 dt 语义
 
@@ -108,19 +108,21 @@ raw / ods 都按 "sched 唯一锚定 + INSERT OVERWRITE 单 dt 分区" 模式:
 - 日期递增串行(sched=5/1, 5/2, 5/3 ... 依次跑):每次写自己的 dt 分区,互不干扰
 - ✓ 幂等,✓ 任意顺序
 
-### Spark `partitionOverwriteMode` 必读
+### Spark `partitionOverwriteMode` 实测
 
-Spark 2.x 默认 `spark.sql.sources.partitionOverwriteMode = STATIC`。本项目 ods 用动态分区 `PARTITION (dt)` + SELECT 带出 dt 列,**必须显式改为 DYNAMIC**
+`spark.sql.sources.partitionOverwriteMode` 控制 Spark 在动态分区 `INSERT OVERWRITE TABLE x PARTITION (col)` 时的覆盖行为
 
 | 模式 | 动态分区 INSERT OVERWRITE 行为 |
 |---|---|
-| STATIC(Spark 2.x 默认) | 覆盖整张表所有分区,没在 SELECT 出现的历史 dt 全部消失 |
+| STATIC | 覆盖整张表所有分区,没在 SELECT 出现的历史 dt 全部消失 |
 | DYNAMIC | 只覆盖 SELECT 实际产生的那些 dt,其他历史分区保留 |
 
-STATIC 模式下日常调度跑一次就清空 ods 全表,每天只剩当天一个 dt——彻底反幂等。**DYNAMIC 是本项目 ods 调度的硬前提**。
-
 PARTITION 子句静态指定值(如 `PARTITION (dt='20260507')`)不受此模式影响——无论 STATIC / DYNAMIC 都只覆盖指定的那个分区。
 
+**本环境实测**(2026-05-07,tests/integration/spark/idempotence/partition_overwrite_default.sql):Spark 2.4 + CDH 6.3.2 + Hive ORC EXTERNAL TABLE 不显式设置时**默认即 DYNAMIC** —— 灌 5 个 dt 后跑动态分区 INSERT OVERWRITE 只产 2 个 dt,剩 5 个分区(03/04 被覆盖,01/02/05 保留),符合 DYNAMIC 语义。
+
+不需要在 `dw_base/spark/spark_sql.py` 显式设 DYNAMIC,也不需要在 ods SQL 顶部加 SET。本环境实测结果是这样,但 Spark / Hive 升级或 CDH 配置变更可能改变默认行为,回归靠 §"幂等测试"。
+
 ### 幂等测试(入仓守护)
 
 幂等性必须由 tests/ 下集成测试守护:跑两次(或日期递增多次)验证 dt 分区集合一致、行数一致。具体测试设计见后续阶段。

+ 1 - 0
kb/92-重构进度.md

@@ -202,3 +202,4 @@
 | 2026-05-05 | **kb/01 §5 集群层外部 jar 节新建**:登记 `elasticsearch-hadoop-7.17.25.jar`(落点 `/opt/cloudera/parcels/CDH/lib/spark/jars/`,所有节点,运维分发,不入 git),ES 集群版本 7.17.25 | — |
 | 2026-05-07 | **ods 启动(ADR-06 ods 部分 ✅ + kb/30 §4.4 新增)**:(a) ADR-06 状态草案 → 已采纳,标题日期补 ods ✅ 2026-05-06;ods 层定稿条款写入:类型映射(int 系→BIGINT、numeric→DECIMAL(20,4)、timestamp→TIMESTAMP、其他无争议)、is_deleted BOOLEAN 软删归一、不加技术字段(反悔早先 etl_time/src_sys/src_tbl 三件套);反悔条件加 2 条(跨时区集群迁移、numeric 高精度场景)。(b) 新建 `conf/pg-to-hive-type.ini` 类型映射 conf。(c) `bin/hive-ddl-gen.py` 加 -l ods 分支:normalize_pg_type / load_type_mapping / map_pg_to_hive / reverse_ods_table_name / render_ods_ddl 5 函数;ods DDL 末尾加 is_deleted;不加技术字段;LOCATION /user/hive/warehouse/ods.db/。(d) 单测 `tests/unit/datax/test_hive_ddl_gen.py` 扩展 17 个 ods 测试,33 全过。(e) 跑生成器出 8 张 ods DDL 落 `manual/ddl/ods/{trd,usr,shp,prd}/`,作者统一 tianyu.chu 头其他 TODO 字段保留。(f) kb/30 新增 §4.4 ods 层(类型转换 + 软删归一)整节,节号重排 4.4 ads → 4.5、4.5 文件命名速查 → 4.6、4.6 表结构变更 → 4.7,line 480 内部引用 §4.6 → §4.7 同步 | — |
 | 2026-05-07 | **ods 阶段二(同步 SQL × 8 + 订单 init)+ kb/26 时间语义新文档**:(a) `jobs/ods/{trd,usr,shp,prd}/<表>_inc_d.sql` × 8 调度 SQL + `manual/backfill/ods_trd_card_group_order_info_init.sql` 订单 his_o + inc_d 一次性灌入 SQL;按 ADR-03 双源 union (raw dt=${dt} + raw dt=${pdt}) + `DATE_FORMAT(update_time)=${dt}` 过滤 + (id, ods_dt) dedupe + 动态分区写入;ods 8 表 DDL COMMENT 填业务名 + 状态改 [已执行]。(b) `kb/26-时间语义.md` 新建:T 任务日为唯一时间锚点;DS 变量底层 + 反复绊人点(`$[yyyyMMdd]` 基于 sched 当天而非 `${system.biz.date}`)+ 实测 echo 输出对照;项目级 globalParams 表(cdt=T / dt=T-1 / tdt=T+1 / pdt=T-2);raw 48h 宽窗 + ods 双源 union 各层 dt 语义 + 跨日漂移修正;补数语义 + 串行重跑幂等矩阵 + STATIC vs DYNAMIC 区分(DYNAMIC 是 ods 调度硬前提);避开"业务日"误称(raw/ods 用 update_time = 业务库系统时间不是真业务时间,dwd 才引入真业务时间)。(c) ADR-03 标题下加交叉引用 → kb/26;README 索引 2x 组加 kb/26 行 | — |
+| 2026-05-07 | **partitionOverwriteMode 默认行为实测 + 文档回修**:建 `tests/integration/spark/idempotence/partition_overwrite_default.sql` 探查 Spark 2.4 + CDH 6.3.2 + Hive ORC EXTERNAL TABLE 下不显式设 `spark.sql.sources.partitionOverwriteMode` 时动态分区 INSERT OVERWRITE 行为:实测**默认即 DYNAMIC**(5 个 dt 灌入后跑动态分区只产 2 个 dt,剩 5 个分区且未出现的 3 个原值保留)。回修 kb/26 §5.7 / §8(去掉"必设""硬前提""STATIC 是默认"等基于网上常识未验证的描述)+ CLAUDE.md 时间变量摘要 + 测试 README 加"实测结果"节。新沉淀 `memory/feedback_test_before_fix.md`:判断"要不要改 X"前先写测试探查实际行为,不凭网上常识就把"必须改"写入文档或动代码 | — |

+ 15 - 0
tests/integration/spark/idempotence/README.md

@@ -26,3 +26,18 @@ python3 bin/spark-sql-starter.py -f tests/integration/spark/idempotence/partitio
 ## 跑完反馈
 
 把关注点 B + C 的输出贴回,依此决定 ods 调度是否需要显式 set DYNAMIC。
+
+## 实测结果(2026-05-07)
+
+环境:Spark 2.4 + CDH 6.3.2 + Hive ORC EXTERNAL TABLE,不显式设置 `spark.sql.sources.partitionOverwriteMode`。
+
+- 关注点 A:5 个分区 ✓(dt=20260501..20260505)
+- 关注点 B:**5 个分区**(dt=20260501..20260505 全在)→ **DYNAMIC 行为**
+- 关注点 C:**5 行**
+  - `(1, init-501, 20260501)` 保留
+  - `(2, init-502, 20260502)` 保留
+  - `(99, rewritten-503, 20260503)` 被覆盖
+  - `(100, rewritten-504, 20260504)` 被覆盖
+  - `(5, init-505, 20260505)` 保留
+
+**结论**:本环境默认即 DYNAMIC,ods 调度无需显式设置 `partitionOverwriteMode`。Spark / Hive 升级或 CDH 配置变更可能改变默认行为,回归靠重跑此测试。