Răsfoiți Sursa

docs(kb): kb/00 §9 数仓开发文件组织迁入 kb/30 §4

kb/00 主题收敛到架构(模块/数据流/配置),§9 的 DDL/jobs 组织
约定 + migration 模式 + 文件命名速查属数仓开发方法论,整节移到
kb/30 作为新的 §4;kb/30 原 §4–§7 顺序后移为 §5–§8。
kb/90 §八 + kb/92 阶段 1 里对 kb/00 §9 的外部引用同步改到 kb/30 §4.x。
tianyu.chu 2 săptămâni în urmă
părinte
comite
97904d7aaa
4 a modificat fișierele cu 161 adăugiri și 158 ștergeri
  1. 0 151
      kb/00-项目架构.md
  2. 158 5
      kb/30-开发规范.md
  3. 1 1
      kb/90-重构路线.md
  4. 2 1
      kb/92-重构进度.md

+ 0 - 151
kb/00-项目架构.md

@@ -442,154 +442,3 @@ bin/datax-multiple-hive-job-starter.sh -gcd jobs/raw/trd -start-date 20260415 -e
 - 仅通过 DS 一次性工作流或命令行手动触发
 - `fix/` 和 `backfill/` 类脚本上线前必须经过 1 人以上 Review
 - 执行完成后保留 3-6 个月作为审计证据,过期移入 `archive/` 或删除
-
-## 9. 样板 job 结构
-
-**核心原则:DDL 与计算 SQL 物理分离,DDL 全部在 `manual/ddl/` 下单一来源。**
-
-- `manual/ddl/` 存放**所有 DDL**(首次建表 + 后续 ALTER),采用 **migration 模式**:每次 DDL 操作是一个不可变文件,**禁止回头改老文件**
-- `jobs/` 存放调度执行的采集 / 计算任务,只做 `INSERT OVERWRITE` 或数据同步,不写 CREATE TABLE
-
-
-一张表的完整生命周期涉及:
-- `manual/ddl/{layer}/{domain}/{表名}_create.sql` —— 首次建表,永久保留
-- 若干 `manual/ddl/{layer}/{domain}/{表名}_{yyyymmdd}_{描述}_change.sql` —— 之后每次 ALTER,独立文件
-- `jobs/{layer}/{domain}/{表名}.sql` 或 `jobs/{layer}/{domain}/{表名}/{表名}-{NN}-{描述}.sql` —— 调度执行的计算 SQL(不含建表),详见 §9.2
-
-### 9.1 `manual/ddl/` —— DDL 唯一来源
-
-**目录组织**:按 `{layer}/{domain}/` 分子目录。layer 代码取自 `21-命名规范.md` §3.1(`raw`/`ods`/`dim`/`dwd`/`dws`/`tdm`/`ads`),domain 代码取自 §3.2(`trd`/`usr`/`prd`/`shp`/`pub`)。每张目标表的首次建表 + 所有 ALTER 都落在这个子目录里,便于一眼看清某层某域的表清单。
-
-```
-manual/ddl/
-├── raw/
-│   └── trd/
-│       ├── raw_trd_order_pay_inc_d_create.sql           # 首次建表(永久保留)
-│       └── 20260612_raw_trd_legacy_order_change_partition.sql
-├── ods/
-│   └── trd/
-│       └── ods_trd_order_pay_inc_d_create.sql
-├── dwd/
-│   └── trd/
-│       ├── dwd_trd_order_pay_inc_d_create.sql
-│       └── 20260520_dwd_trd_order_pay_add_refund.sql    # ALTER(独立文件,不改原文件)
-├── ads/
-│   └── trd/
-│       └── ads_trd_gmv_d_create.sql
-├── tmp/                                                 # 单目标加速中间表 DDL(见 §9.2)
-│   └── dwd_trd_order_pay/
-│       ├── tmp_dwd_trd_order_pay_01_create.sql
-│       └── tmp_dwd_trd_order_pay_02_create.sql
-└── archive/
-    └── 20260301_old_alter.sql                           # 已归档
-```
-
-**按 `grep` 的友好度**:`grep -r "CREATE TABLE.*dwd_trd_order_pay_inc_d" manual/ddl/` 仍能直接命中;分子目录带来的额外索引成本小于"一眼看到分层分域"的收益。
-
-**存储格式约定**:所有分层一律 `STORED AS ORC`。策略详见 `20-数仓分层与建模.md` §7。
-
-### 9.2 `jobs/` 层 —— 调度执行的计算 SQL
-
-**文件粒度:一张目标表对应一套 SQL 文件**,按复杂度两档:
-
-- **简单表** — `jobs/{layer}/{domain}/{表名}.sql` 一个文件顶到底(单次 `INSERT OVERWRITE`,可带 `WITH` CTE)
-- **多步表** — `jobs/{layer}/{domain}/{表名}/{表名}-{NN}-{描述}.sql`,序号三位,`99` 固定留给最终 `INSERT OVERWRITE` 目标表那一步。DS 工作流对应 N 个 task 节点按序号链式依赖
-
-所有 `.sql` 只写 `INSERT OVERWRITE` / `INSERT INTO`,**不写 CREATE TABLE**(表由 `manual/ddl/` 保证已存在)。
-
-```
-jobs/dwd/trd/
-├── dwd_trd_order_refund_inc_d.sql            # 简单表,单文件
-├── dwd_trd_shop_gmv_agg_ful_d.sql
-└── dwd_trd_order_pay_inc_d/                  # 多步表,目标表名同名子目录
-    ├── dwd_trd_order_pay_inc_d-01-build_tmp_pay_base.sql
-    ├── dwd_trd_order_pay_inc_d-02-build_tmp_refund_agg.sql
-    └── dwd_trd_order_pay_inc_d-99-insert_target.sql
-```
-
-**什么时候从简单表升级到多步表:**
-
-| 触发条件 | 处理 |
-|---------|------|
-| 单 SQL shuffle 过大(单作业耗时 > 30 min 且 shuffle read > 100GB) | 拆分中间结果物化为 tmp 表 |
-| 同一块 CTE 在多个 WITH 节里重复扫描 | 物化后 cache 复用 |
-| 复杂业务逻辑,读多源后多轮 join,需要中间落盘便于 debug / 回溯 | 拆分单步 |
-| 中间结果需要被**多个目标表**复用 | **不用 tmp**,升层为 dwd/dws 独立表 |
-
-**中间表两类,严格区分:**
-
-1. **单目标加速中间表(tmp)** — 只服务本目标表,命名 `tmp_{目标表名}_{NN}`,DDL 收到 `manual/ddl/tmp/{目标表名}/` 子目录。生命周期跟随本次任务,每次 `INSERT OVERWRITE` 覆盖或 drop+recreate,不留历史
-2. **可复用中间结果** — 被 ≥2 个目标表引用,**升层为独立 dwd/dws 表**,按正常五段式命名,DDL 单独登记。**不允许用 tmp 前缀**
-
-**从单文件升级到子目录的操作步骤**:删掉原单文件,建子目录、拆 SQL、DS 工作流拆 task 节点;`manual/ddl/tmp/{目标表名}/` 同步补齐 tmp 表 DDL。一次性改完,避免半新半旧。
-
-**WITH / CTE 还是拆文件**:轻量中间结果用 `WITH` 内联(不物化,本质还是单 SQL);重量中间结果需要物化为 tmp 表时才升级到"多步表子目录"(见本节上方触发条件表)。不要盲目把 CTE 都拆成 tmp —— shuffle 不大、不复用的 CTE 留在 `WITH` 里反而更清爽。
-
-### 9.3 raw 层(采集任务)
-
-raw 层的 `jobs/` 有两类主要任务,根据源数据形态选择:
-
-| 场景 | 文件类型 | 执行器 |
-|---|---|---|
-| 从 MongoDB / PG / MySQL 等结构化源库同步 | `.ini`(DataX 配置) | `bin/datax-single-job-starter.sh` |
-| 从本地 / 外部 CSV 文件导入 | `.sql`(含 `USING csv` 临时视图 + `INSERT OVERWRITE`) | `bin/csv-to-hdfs-starter.py`(阶段 1 实现) |
-
-**raw 层数据类型约定**:全字段 `STRING`,类型转换与脏数据识别下推到 ods 层。契约详见 `20-数仓分层与建模.md` §8.1。
-
-**CSV 导入流程**:
-
-1. 本地 CSV 文件如果较大,先 `gzip` 压缩
-2. `bin/csv-to-hdfs-starter.py` 把(压缩后的)CSV `hdfs dfs -put` 到 HDFS 暂存区
-3. 调用 SparkSQL 执行 `jobs/raw/{域}/{表}.sql`,文件内通过 `USING csv OPTIONS(...)` 临时视图解析 CSV,再 `INSERT OVERWRITE` 写入对应 raw 表
-4. 清理 HDFS 暂存文件
-
-**raw 层写入模式对照**:
-
-| 场景 | 写法 | `manual/ddl/` |
-|---|---|---|
-| **一次性 CSV 导入**(历史回刷、单批 vendor 数据),表名 `raw_xxx_his_o` | 预建 `EXTERNAL TABLE`(不分区),`INSERT OVERWRITE TABLE ...` | 需要 |
-| **每日重复的 CSV 导入**(daily file drop) | 预建分区 `EXTERNAL TABLE`,每日 `INSERT OVERWRITE TABLE ... PARTITION (dt='${dt}')` | 需要 |
-| **结构化源库同步**(PG/MySQL 等) | DataX ini,写入预建 `EXTERNAL TABLE`(`writeMode=truncate` 或分区覆盖) | 需要 |
-
-**`his` 表为什么不分区**:一次性导入永不追加,分区裁剪没有意义。下游 ods 再按 `dt` 分区,一次性切片。
-
-**为什么用 SQL 而不是 YAML 描述 CSV 任务**:
-- 复用 `SparkSQL` 现有执行链,`bin/csv-to-hdfs-starter.py` 只需在 `bin/spark-sql-starter.py` 之外加一层 gzip+put+清理的薄壳,不需要单独的 YAML 渲染器
-- `USING csv OPTIONS(...)` 本身就是 Spark 的声明式 CSV 读取语法,YAML 再封装一层是多余的
-- 与其他分层文件类型一致(除 raw DataX ini 外,其他都是 `.sql`),读者不需要切换上下文
-
-### 9.4 ads 层(SQL + 导出 ini 并存)
-
-```
-manual/ddl/
-└── ads_trd_gmv_d.sql                  # 建表 DDL(首次建表,永久保留)
-
-jobs/ads/trd/
-├── ads_trd_gmv_d.sql                  # 每日计算产出 ads 表
-└── ads_trd_gmv_d_export.ini           # 导出到 Doris/ClickHouse/MySQL 的 DataX ini
-```
-
-**命名规则**:导出 ini 文件名 = `{ads 表名}_export.ini`,便于一眼对应。
-
-### 9.5 文件命名速查
-
-| 目录 | 文件后缀 | 文件名规则 | 说明 |
-|---|---|---|---|
-| `manual/ddl/{layer}/{domain}/` | `.sql` | `{表名}_create.sql`(首次) 或 `{yyyymmdd}_{表名}_{change}.sql`(ALTER) | DDL 唯一来源;首次建表用 `CREATE TABLE IF NOT EXISTS`,后续 ALTER 带日期前缀 |
-| `manual/ddl/tmp/{目标表名}/` | `.sql` | `tmp_{目标表名}_{NN}_create.sql` | 多步表的单目标加速中间表 DDL |
-| `jobs/raw/{domain}/` | `.ini`(DataX)或 `.sql`(CSV 导入) | `{目标表名}.ini` 或 `{目标表名}.sql` | DataX 采集或 CSV 导入任务定义 |
-| `jobs/{ods\|dwd\|dws\|tdm}/{domain}/` | `.sql` | **简单表**:`{目标表名}.sql`;**多步表**:子目录 `{目标表名}/{目标表名}-{NN}-{描述}.sql`(`99` 为最终 insert) | 每日 `INSERT OVERWRITE` 计算,详见 §9.2 |
-| `jobs/ads/{domain}/` | `.sql` + `.ini` | **简单表**:`{ads 表名}.sql` + `{ads 表名}__{db_type}_{instance}.ini`;**多步**:`{ads 表名}/{ads 表名}-{NN}-{描述}.sql` + 同级目录放 ini | 产出 + 导出;同一张 ads 表扇出多下游时各一份 ini |
-| `manual/backfill/` | `.sql` | `{yyyymmdd}_{表名}_history.sql` | 一次性历史回刷脚本 |
-| `manual/imports/{yyyymmdd}/` | `.ini` / `.sql` | `{任务描述}.ini` 或 `.sql` | 一次性入仓任务(离线硬盘、历史 dump、外部 CSV 等),按执行日期归档 |
-| `manual/exports/{yyyymmdd}/` | `.ini` | `{任务描述}.ini` | 一次性出仓任务,按执行日期归档 |
-
-### 9.6 表结构变更流程(migration 模式)
-
-当要给某张表加列 / 改字段时,**只写新文件,不改老文件**:
-
-在 `manual/ddl/{yyyymmdd}_{表名}_{change}.sql` 写 ALTER 语句(带工单号、目的、回滚方案)
-
-- ALTER 文件按时间前缀线性堆叠,`grep dwd_trd_order_pay manual/ddl/` 即可看到该表的全部 DDL 历史,按文件名时间序回放就是表结构的完整演化
-- 真要在新环境重建这张表,按时间顺序把 `manual/ddl/{表名}.sql` + 所有相关 ALTER 文件依次执行即可,结果和生产一致。**注意**:目前没有自动化重放工具,需要人手按文件名时间序执行;未来视需要可以写一个 `bin/replay-ddl.sh`(当前未实现)
-- 这是数据库 migration 工具(Flyway / Alembic / Liquibase)的标准做法,已被工业界验证

+ 158 - 5
kb/30-开发规范.md

@@ -305,7 +305,160 @@ flowchart LR
 - `feat: xxx fix: yyy docs: zzz` —— 一条 commit 混多个 type
 - `feat: 新增了一大堆表` —— scope 和具体目标不明
 
-## 4. 测试规范
+## 4. 数仓开发文件组织
+
+> 讲 DDL 与计算 SQL 怎么在 `jobs/` 与 `manual/ddl/` 下组织;本节管"文件放哪",§3 管"代码怎么写"。
+
+**核心原则:DDL 与计算 SQL 物理分离,DDL 全部在 `manual/ddl/` 下单一来源。**
+
+- `manual/ddl/` 存放**所有 DDL**(首次建表 + 后续 ALTER),采用 **migration 模式**:每次 DDL 操作是一个不可变文件,**禁止回头改老文件**
+- `jobs/` 存放调度执行的采集 / 计算任务,只做 `INSERT OVERWRITE` 或数据同步,不写 CREATE TABLE
+
+
+一张表的完整生命周期涉及:
+- `manual/ddl/{layer}/{domain}/{表名}_create.sql` —— 首次建表,永久保留
+- 若干 `manual/ddl/{layer}/{domain}/{表名}_{yyyymmdd}_{描述}_change.sql` —— 之后每次 ALTER,独立文件
+- `jobs/{layer}/{domain}/{表名}.sql` 或 `jobs/{layer}/{domain}/{表名}/{表名}-{NN}-{描述}.sql` —— 调度执行的计算 SQL(不含建表),详见 §4.2
+
+### 4.1 `manual/ddl/` —— DDL 唯一来源
+
+**目录组织**:按 `{layer}/{domain}/` 分子目录。layer 代码取自 `21-命名规范.md` §3.1(`raw`/`ods`/`dim`/`dwd`/`dws`/`tdm`/`ads`),domain 代码取自 §3.2(`trd`/`usr`/`prd`/`shp`/`pub`)。每张目标表的首次建表 + 所有 ALTER 都落在这个子目录里,便于一眼看清某层某域的表清单。
+
+```
+manual/ddl/
+├── raw/
+│   └── trd/
+│       ├── raw_trd_order_pay_inc_d_create.sql           # 首次建表(永久保留)
+│       └── 20260612_raw_trd_legacy_order_change_partition.sql
+├── ods/
+│   └── trd/
+│       └── ods_trd_order_pay_inc_d_create.sql
+├── dwd/
+│   └── trd/
+│       ├── dwd_trd_order_pay_inc_d_create.sql
+│       └── 20260520_dwd_trd_order_pay_add_refund.sql    # ALTER(独立文件,不改原文件)
+├── ads/
+│   └── trd/
+│       └── ads_trd_gmv_d_create.sql
+├── tmp/                                                 # 单目标加速中间表 DDL(见 §4.2)
+│   └── dwd_trd_order_pay/
+│       ├── tmp_dwd_trd_order_pay_01_create.sql
+│       └── tmp_dwd_trd_order_pay_02_create.sql
+└── archive/
+    └── 20260301_old_alter.sql                           # 已归档
+```
+
+**按 `grep` 的友好度**:`grep -r "CREATE TABLE.*dwd_trd_order_pay_inc_d" manual/ddl/` 仍能直接命中;分子目录带来的额外索引成本小于"一眼看到分层分域"的收益。
+
+**存储格式约定**:所有分层一律 `STORED AS ORC`。策略详见 `20-数仓分层与建模.md` §7。
+
+### 4.2 `jobs/` 层 —— 调度执行的计算 SQL
+
+**文件粒度:一张目标表对应一套 SQL 文件**,按复杂度两档:
+
+- **简单表** — `jobs/{layer}/{domain}/{表名}.sql` 一个文件顶到底(单次 `INSERT OVERWRITE`,可带 `WITH` CTE)
+- **多步表** — `jobs/{layer}/{domain}/{表名}/{表名}-{NN}-{描述}.sql`,序号三位,`99` 固定留给最终 `INSERT OVERWRITE` 目标表那一步。DS 工作流对应 N 个 task 节点按序号链式依赖
+
+所有 `.sql` 只写 `INSERT OVERWRITE` / `INSERT INTO`,**不写 CREATE TABLE**(表由 `manual/ddl/` 保证已存在)。
+
+```
+jobs/dwd/trd/
+├── dwd_trd_order_refund_inc_d.sql            # 简单表,单文件
+├── dwd_trd_shop_gmv_agg_ful_d.sql
+└── dwd_trd_order_pay_inc_d/                  # 多步表,目标表名同名子目录
+    ├── dwd_trd_order_pay_inc_d-01-build_tmp_pay_base.sql
+    ├── dwd_trd_order_pay_inc_d-02-build_tmp_refund_agg.sql
+    └── dwd_trd_order_pay_inc_d-99-insert_target.sql
+```
+
+**什么时候从简单表升级到多步表:**
+
+| 触发条件 | 处理 |
+|---------|------|
+| 单 SQL shuffle 过大(单作业耗时 > 30 min 且 shuffle read > 100GB) | 拆分中间结果物化为 tmp 表 |
+| 同一块 CTE 在多个 WITH 节里重复扫描 | 物化后 cache 复用 |
+| 复杂业务逻辑,读多源后多轮 join,需要中间落盘便于 debug / 回溯 | 拆分单步 |
+| 中间结果需要被**多个目标表**复用 | **不用 tmp**,升层为 dwd/dws 独立表 |
+
+**中间表两类,严格区分:**
+
+1. **单目标加速中间表(tmp)** — 只服务本目标表,命名 `tmp_{目标表名}_{NN}`,DDL 收到 `manual/ddl/tmp/{目标表名}/` 子目录。生命周期跟随本次任务,每次 `INSERT OVERWRITE` 覆盖或 drop+recreate,不留历史
+2. **可复用中间结果** — 被 ≥2 个目标表引用,**升层为独立 dwd/dws 表**,按正常五段式命名,DDL 单独登记。**不允许用 tmp 前缀**
+
+**从单文件升级到子目录的操作步骤**:删掉原单文件,建子目录、拆 SQL、DS 工作流拆 task 节点;`manual/ddl/tmp/{目标表名}/` 同步补齐 tmp 表 DDL。一次性改完,避免半新半旧。
+
+**WITH / CTE 还是拆文件**:轻量中间结果用 `WITH` 内联(不物化,本质还是单 SQL);重量中间结果需要物化为 tmp 表时才升级到"多步表子目录"(见本节上方触发条件表)。不要盲目把 CTE 都拆成 tmp —— shuffle 不大、不复用的 CTE 留在 `WITH` 里反而更清爽。
+
+### 4.3 raw 层(采集任务)
+
+raw 层的 `jobs/` 有两类主要任务,根据源数据形态选择:
+
+| 场景 | 文件类型 | 执行器 |
+|---|---|---|
+| 从 MongoDB / PG / MySQL 等结构化源库同步 | `.ini`(DataX 配置) | `bin/datax-single-job-starter.sh` |
+| 从本地 / 外部 CSV 文件导入 | `.sql`(含 `USING csv` 临时视图 + `INSERT OVERWRITE`) | `bin/csv-to-hdfs-starter.py`(阶段 1 实现) |
+
+**raw 层数据类型约定**:全字段 `STRING`,类型转换与脏数据识别下推到 ods 层。契约详见 `20-数仓分层与建模.md` §8.1。
+
+**CSV 导入流程**:
+
+1. 本地 CSV 文件如果较大,先 `gzip` 压缩
+2. `bin/csv-to-hdfs-starter.py` 把(压缩后的)CSV `hdfs dfs -put` 到 HDFS 暂存区
+3. 调用 SparkSQL 执行 `jobs/raw/{域}/{表}.sql`,文件内通过 `USING csv OPTIONS(...)` 临时视图解析 CSV,再 `INSERT OVERWRITE` 写入对应 raw 表
+4. 清理 HDFS 暂存文件
+
+**raw 层写入模式对照**:
+
+| 场景 | 写法 | `manual/ddl/` |
+|---|---|---|
+| **一次性 CSV 导入**(历史回刷、单批 vendor 数据),表名 `raw_xxx_his_o` | 预建 `EXTERNAL TABLE`(不分区),`INSERT OVERWRITE TABLE ...` | 需要 |
+| **每日重复的 CSV 导入**(daily file drop) | 预建分区 `EXTERNAL TABLE`,每日 `INSERT OVERWRITE TABLE ... PARTITION (dt='${dt}')` | 需要 |
+| **结构化源库同步**(PG/MySQL 等) | DataX ini,写入预建 `EXTERNAL TABLE`(`writeMode=truncate` 或分区覆盖) | 需要 |
+
+**`his` 表为什么不分区**:一次性导入永不追加,分区裁剪没有意义。下游 ods 再按 `dt` 分区,一次性切片。
+
+**为什么用 SQL 而不是 YAML 描述 CSV 任务**:
+- 复用 `SparkSQL` 现有执行链,`bin/csv-to-hdfs-starter.py` 只需在 `bin/spark-sql-starter.py` 之外加一层 gzip+put+清理的薄壳,不需要单独的 YAML 渲染器
+- `USING csv OPTIONS(...)` 本身就是 Spark 的声明式 CSV 读取语法,YAML 再封装一层是多余的
+- 与其他分层文件类型一致(除 raw DataX ini 外,其他都是 `.sql`),读者不需要切换上下文
+
+### 4.4 ads 层(SQL + 导出 ini 并存)
+
+```
+manual/ddl/
+└── ads_trd_gmv_d.sql                  # 建表 DDL(首次建表,永久保留)
+
+jobs/ads/trd/
+├── ads_trd_gmv_d.sql                  # 每日计算产出 ads 表
+└── ads_trd_gmv_d_export.ini           # 导出到 Doris/ClickHouse/MySQL 的 DataX ini
+```
+
+**命名规则**:导出 ini 文件名 = `{ads 表名}_export.ini`,便于一眼对应。
+
+### 4.5 文件命名速查
+
+| 目录 | 文件后缀 | 文件名规则 | 说明 |
+|---|---|---|---|
+| `manual/ddl/{layer}/{domain}/` | `.sql` | `{表名}_create.sql`(首次) 或 `{yyyymmdd}_{表名}_{change}.sql`(ALTER) | DDL 唯一来源;首次建表用 `CREATE TABLE IF NOT EXISTS`,后续 ALTER 带日期前缀 |
+| `manual/ddl/tmp/{目标表名}/` | `.sql` | `tmp_{目标表名}_{NN}_create.sql` | 多步表的单目标加速中间表 DDL |
+| `jobs/raw/{domain}/` | `.ini`(DataX)或 `.sql`(CSV 导入) | `{目标表名}.ini` 或 `{目标表名}.sql` | DataX 采集或 CSV 导入任务定义 |
+| `jobs/{ods\|dwd\|dws\|tdm}/{domain}/` | `.sql` | **简单表**:`{目标表名}.sql`;**多步表**:子目录 `{目标表名}/{目标表名}-{NN}-{描述}.sql`(`99` 为最终 insert) | 每日 `INSERT OVERWRITE` 计算,详见 §4.2 |
+| `jobs/ads/{domain}/` | `.sql` + `.ini` | **简单表**:`{ads 表名}.sql` + `{ads 表名}__{db_type}_{instance}.ini`;**多步**:`{ads 表名}/{ads 表名}-{NN}-{描述}.sql` + 同级目录放 ini | 产出 + 导出;同一张 ads 表扇出多下游时各一份 ini |
+| `manual/backfill/` | `.sql` | `{yyyymmdd}_{表名}_history.sql` | 一次性历史回刷脚本 |
+| `manual/imports/{yyyymmdd}/` | `.ini` / `.sql` | `{任务描述}.ini` 或 `.sql` | 一次性入仓任务(离线硬盘、历史 dump、外部 CSV 等),按执行日期归档 |
+| `manual/exports/{yyyymmdd}/` | `.ini` | `{任务描述}.ini` | 一次性出仓任务,按执行日期归档 |
+
+### 4.6 表结构变更流程(migration 模式)
+
+当要给某张表加列 / 改字段时,**只写新文件,不改老文件**:
+
+在 `manual/ddl/{yyyymmdd}_{表名}_{change}.sql` 写 ALTER 语句(带工单号、目的、回滚方案)
+
+- ALTER 文件按时间前缀线性堆叠,`grep dwd_trd_order_pay manual/ddl/` 即可看到该表的全部 DDL 历史,按文件名时间序回放就是表结构的完整演化
+- 真要在新环境重建这张表,按时间顺序把 `manual/ddl/{表名}.sql` + 所有相关 ALTER 文件依次执行即可,结果和生产一致。**注意**:目前没有自动化重放工具,需要人手按文件名时间序执行;未来视需要可以写一个 `bin/replay-ddl.sh`(当前未实现)
+- 这是数据库 migration 工具(Flyway / Alembic / Liquibase)的标准做法,已被工业界验证
+
+## 5. 测试规范
 
 见 `90-重构路线.md` §6。核心要点:
 - UDF 单测:纯 Python,不依赖 Spark
@@ -313,7 +466,7 @@ flowchart LR
 - Spark 集成测试:`local[*]` 模式
 - 数据质量校验:行数、空值率、主键唯一性
 
-## 5. manual/ 临时 SQL 规范
+## 6. manual/ 临时 SQL 规范
 
 `manual/` 目录存放**一次性、非幂等**的 SQL 脚本,与 `jobs/` 的语义完全独立。详细目录约定见 `00-项目架构.md` §8,这里只列开发团队必须遵守的核心规则:
 
@@ -322,12 +475,12 @@ flowchart LR
 3. **文件头必须声明元信息**:作者、日期、工单号、目的、执行状态(待执行 / 已执行 yyyy-mm-dd)
 4. **`fix/` 和 `backfill/` 强制 Review**:涉及线上数据订正和历史回刷的脚本,合并前至少 1 人 Review
 5. **与 `jobs/` 的边界**:
-   - 表结构变更 → **只在 `manual/ddl/` 写一个新的 ALTER 文件**,**不要回头改 `manual/ddl/{表名}.sql`**(migration 模式,详见 `00-项目架构.md` §9.6)。
+   - 表结构变更 → **只在 `manual/ddl/` 写一个新的 ALTER 文件**,**不要回头改 `manual/ddl/{表名}.sql`**(migration 模式,详见 §4.6)。
    - 历史数据回刷 → 优先复用 `jobs/` 原 SQL + 日期参数,`manual/backfill/` 只放调用包装
    - 临时取数给业务方 → `manual/adhoc/`
    - 脏数据订正 → `manual/fix/`,必须附 TPAD 工单号
 
-## 6. 开发样板
+## 7. 开发样板
 
 `conf/templates/` 下按**引擎**分顶层,模板供开发者照抄写新文件,不被任何代码读取。
 
@@ -343,7 +496,7 @@ flowchart LR
 - 文件名用双扩展名 `*.template.{ini,sql}`,避免被 DataX / Spark SQL 引擎误拾
 - 新增样板归到对应子目录;新增引擎类时顶层并列目录
 
-## 7. 相关文档
+## 8. 相关文档
 
 - [命名规范](21-命名规范.md)
 - [数仓分层与建模](20-数仓分层与建模.md)

+ 1 - 1
kb/90-重构路线.md

@@ -602,7 +602,7 @@ else:
 |------|------|------|------|
 | `bin/publish.sh`(从项目根挪入) | ✅ 2026-04-20 完成 | — | §2.1 行 |
 | `bin/excel_to_hive.py` 删除(有需求重做) | ✅ 2026-04-20 完成 | — | changelog |
-| `bin/csv-to-hdfs-starter.py` 实现 | 待启动 | A 大部分就绪 | kb/00 §9 模板 |
+| `bin/csv-to-hdfs-starter.py` 实现 | 待启动 | A 大部分就绪 | kb/30 §4.3 |
 | `bin/datax-import` + `bin/datax-export` 两命令收口 | 待启动 | **B2** 四模块边界 / A datax 路径解耦 | §2.6 |
 | `bin/datax-gc-generator.py` 从零重写 | 待启动 | datasource 多环境就绪 | §2.7 |
 

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

@@ -51,7 +51,7 @@
 - [x] 新建 `manual/` 目录 + 5 个子目录(`ddl/`、`backfill/`、`fix/`、`adhoc/`、`archive/`)(2026-04-15,已放 `.gitkeep`;`manual/ddl/` 是所有 DDL 的唯一来源)
 - [x] 项目仓库改名 `tendata-warehouse-release` → `poyee-data-warehouse`(2026-04-22,根目录已由用户手动改名;`.idea/*.iml` 与 `modules.xml` 不入库,残留不影响运行,不处理)
 - [ ] 更新 `publish.sh` 中的部署路径与项目名
-- [ ] 实现 `bin/csv-to-hdfs-starter.py`:本地 CSV → gzip(可选,大文件才压缩)→ `hdfs dfs -put` 到暂存区 → 调用 `SparkSQL` 执行 `jobs/raw/{域}/{表}.sql`(含 `USING csv` 临时视图 + `INSERT OVERWRITE`)→ 清理暂存。任务定义采用 SQL 格式(参见 `00-项目架构.md` §9.3 模板
+- [ ] 实现 `bin/csv-to-hdfs-starter.py`:本地 CSV → gzip(可选,大文件才压缩)→ `hdfs dfs -put` 到暂存区 → 调用 `SparkSQL` 执行 `jobs/raw/{域}/{表}.sql`(含 `USING csv` 临时视图 + `INSERT OVERWRITE`)→ 清理暂存。任务定义采用 SQL 格式(参见 `30-开发规范.md` §4.3
 - [ ] 验证:在测试集群跑一次 `bin/spark-sql-starter.py` 样例,import 和 Spark 环境初始化能过
 - [ ] 验证:在测试集群跑一次 `bin/datax-single-job-starter.sh` 样例,ini→json 生成能过
 - [ ] 验证:`bin/csv-to-hdfs-starter.py` 用一个样例 CSV 跑通 raw 层入仓
@@ -184,3 +184,4 @@
 | 2026-04-22 | **datasource 扁平化 + db_type 按父目录段判定(方向反转)**:反转 2026-04-15 起立项的"一套代码跑多环境"设计(kb/90 原 §2.5:`-env` 参数 + `datasource/{db_type}/{env}/{instance}.ini` 分层 + env 注入)。实际前期跨环境同步是常态(test 业务库 → prod HDFS),"全局 env"概念不成立。新方案:(a) source ini 落位 `datasource/{db_type}/{env}-{实例简称}.ini`,env 写进文件名(env ∈ dev/test/prod),扁平组织;(b) sync ini 内 `dataSource = {db_type}/{env}-{实例简称}`(强制带 db_type 前缀;旧裸名 `hdfs-ha` 不再支持);(c) 代码侧 `dw_base/datax/plugins/plugin.py:37` + `plugin_factory.py:34` 的 db_type 提取从"文件名按 `-` 切首词"改为"按 `/` 切首段"(父目录名)—— 旧算法在新文件名出现 `-` 时会把 `env` 误判为 db_type,必须改。联动:kb/00 §1 目录树重绘(去 env 子目录,加样例 `prod-hobby.ini` / `test-hobby.ini` / `prod-ha.ini` 等 + sync ini 引用形式说明段)+ §6.1 配置分类表落位描述、kb/02 §4 同步、kb/21 §3.9 过时 3 行修正(废 `-env` 注入表述;引用形式 `{db_type}/{env}-{实例简称}`;JSON 输出路径去 `{env}/` 层)、kb/90 §2.1 硬编码表合并 3 行为 1 行 + §2.5 大幅压缩(三阶段设计整段删,保留路径解耦部分)+ §2.8 末尾表行更新 + §八 状态表合并、kb/92 L90 checklist 落位改扁平。conf/templates/datasource/\*.template.ini × 3 头注释补"落位"+"dataSource 引用"两行。`bin/datax-job-config-generator.py` L5/L114/L115 的路径注释讲的是另一个 `conf/datax/config/` 作业分组层级,与本次 datasource 扁平化无关,不动;`dw_base/datax/plugins/reader/mysql_reader.py:178` `{group}/mysql-{database}` 生成逻辑是非活跃代码(批量采集未启动),留到 §2.7 重构时对齐 | — |
 | 2026-04-22 | **conf/templates 按引擎顶层重组 + README 样板段迁入 kb/30 §6**:`conf/templates/{datasource,datax/{raw,ads,manual},sql,ddl}/` → `conf/templates/{datax/{datasource,sync},spark/{sql,ddl}}/`。datasource 挪进 datax/ 匹配其仅服务于 DataX 的事实(Spark 入口只读 Hive,不读 ini);datax/{raw,ads,manual} 三空目录合并为 datax/sync/(kb/21 §3.9 删后三分类在样板层级无意义);sql/ddl 归 spark/ 按执行引擎归属。已备 3 份 datasource ini 随 git mv 挪入 datax/datasource/,头注释不含 conf/templates 字面量不需改。README 原 L111-116 "样板(待补充)"整段删除,替换为一句指向 kb/30 §6 链接;kb/30 新增 §6 开发样板(原 §6 相关文档 → §7);kb/90 §2.1 模板目录行同步更新目录字面量 + 入口从 README 改指 kb/30 §6 | — |
 | 2026-04-22 | **kb/21 §3.9 DataX ini 命名移出**:kb/21 主题收敛到 Hive 表/字段/词根命名,§3.9 "DataX ini 文件命名"(命名模板表 + 导出类双下划线规则 + 通用约定 + 样板指引 + 老命名反思)整节删除,§8 速查表同删 3 行 DataX ini;DataX ini 命名示例已在 kb/00 §1 目录树 + §9.5 文件命名速查中体现,不再单独成节。连带清理 7 处跨文档 §3.9 引用:kb/00(L143 示例标题 + L582 §9.5 jobs/ads 行)、kb/30(§3.4.7 docs commit 示例改指 §3.4)、kb/90(§2.1 硬编码表 3 行 + §2.5 目标态段)。kb/21 §5.1 顺手清理一条残留垃圾字符 `buyc1` | — |
+| 2026-04-22 | **kb/00 §9 样板 job 结构迁入 kb/30 §4**:kb/00 主题收敛到架构(模块/数据流/配置),§9 "DDL/jobs 组织 + migration 模式 + 命名速查" 属数仓开发方法论,整节(§9.1–§9.6)迁入 kb/30 新增的 §4 数仓开发文件组织;kb/30 原 §4/§5/§6/§7 顺序后移为 §5/§6/§7/§8,§6 manual/ 临时 SQL 规范里 "详见 `00-项目架构.md` §9.6" 改指本文 §4.6(就近内引)。外部引用更新:kb/90 §八 csv-to-hdfs 行"参见"列改指 kb/30 §4.3;kb/92 阶段 1 csv-to-hdfs checklist 里 "§9.3 模板" 改指 kb/30 §4.3。历史 changelog 里带 "§9.x" 的条目保留不改(历史 snapshot) | — |