Bläddra i källkod

docs(kb): raw 全量 EXTERNAL 硬化(CTAS 样例改两步 + §9.1 补 raw DDL 模板 + §8.1 dt 并入全字段 STRING)

tianyu.chu 2 veckor sedan
förälder
incheckning
51a41c2f99
3 ändrade filer med 35 tillägg och 23 borttagningar
  1. 33 22
      kb/00-项目架构.md
  2. 1 1
      kb/20-数仓分层与建模.md
  3. 1 0
      kb/92-重构进度.md

+ 33 - 22
kb/00-项目架构.md

@@ -678,6 +678,28 @@ PARTITIONED BY (dt STRING COMMENT '日期分区 yyyyMMdd')
 STORED AS ORC;
 ```
 
+**raw 层 EXTERNAL 建表模板**(`manual/ddl/raw/trd/raw_trd_legacy_order_his_o_create.sql`):
+
+```sql
+-- ============================================================
+-- 表名:raw.raw_trd_legacy_order_his_o
+-- 业务含义:交易域-历史订单 CSV 导入(一次性 his)
+-- 分层:RAW(全字段 STRING;EXTERNAL 兜底,详见 20-数仓分层与建模.md §8.1)
+-- 负责人:tianyu.chu
+-- 创建日期:2026-xx-xx
+-- 状态:[待执行]
+-- ============================================================
+
+CREATE EXTERNAL TABLE IF NOT EXISTS raw.raw_trd_legacy_order_his_o (
+    order_id    STRING COMMENT '订单ID(源端字面量)',
+    user_id     STRING COMMENT '用户ID(源端字面量)',
+    amount      STRING COMMENT '金额(源端字面量)',
+    order_time  STRING COMMENT '下单时间(源端字面量)'
+) COMMENT '交易域-历史订单CSV导入(一次性,全STRING)'
+STORED AS ORC
+LOCATION '/user/hive/warehouse/raw.db/raw_trd_legacy_order_his_o/';
+```
+
 **ALTER 文件模板**(`manual/ddl/20260520_dwd_trd_order_pay_add_refund.sql`):
 
 ```sql
@@ -788,11 +810,13 @@ raw 层的 `jobs/` 有两类主要任务,根据源数据形态选择:
 3. 调用 SparkSQL 执行 `jobs/raw/{域}/{表}.sql`,文件内通过 `USING csv OPTIONS(...)` 临时视图解析 CSV,再 `INSERT OVERWRITE` 写入对应 raw 表
 4. 清理 HDFS 暂存文件
 
-**CSV 导入任务定义模板**(`jobs/raw/trd/raw_trd_legacy_order_his_o.sql`,**CTAS 一次性导入**):
+**CSV 导入任务定义模板**(`jobs/raw/trd/raw_trd_legacy_order_his_o.sql`):
+
+DDL 先在 `manual/ddl/raw_trd_legacy_order_his_o.sql` 按 raw 层规范建好 `EXTERNAL TABLE`(全字段 STRING,契约详见 `20-数仓分层与建模.md` §8.1;DDL 模板见本文档 §9.1 raw 变体)。本脚本只负责"读 CSV → 写入 EXTERNAL 表的 LOCATION"。
 
 ```sql
 -- ============================================================
--- 目标表:raw.raw_trd_legacy_order_his_o
+-- 目标表:raw.raw_trd_legacy_order_his_o(EXTERNAL,DDL 在 manual/ddl/ 预建)
 -- 业务含义:交易域 - 历史订单 CSV 导入(一次性历史快照 his)
 -- 源数据:本地路径 /data/uploads/legacy_orders/historical.csv
 --        编码 UTF-8 / 带表头 / 逗号分隔 / 双引号 quote / 字段内允许换行
@@ -801,7 +825,6 @@ raw 层的 `jobs/` 有两类主要任务,根据源数据形态选择:
 -- 负责人:tianyu.chu
 -- 创建日期:2026-xx-xx
 -- 状态:[待执行]
--- 备注:CTAS 语法一次性建表 + 写入,无需在 manual/ddl/ 单独写 CREATE TABLE
 -- ============================================================
 
 -- csv-to-hdfs-starter 通过文件头注释读取这两个变量并完成 gzip+put 操作
@@ -822,14 +845,7 @@ OPTIONS (
     inferSchema 'false'
 );
 
--- CTAS:一次性建表 + 写入,全字段 STRING(Spark 默认从 v_stage 推断为 STRING)
--- 不需要预先在 manual/ddl/ 写 CREATE TABLE
-DROP TABLE IF EXISTS raw.raw_trd_legacy_order_his_o;
-
-CREATE TABLE raw.raw_trd_legacy_order_his_o
-USING ORC
-COMMENT '交易域-历史订单CSV导入(一次性,全STRING)'
-AS
+INSERT OVERWRITE TABLE raw.raw_trd_legacy_order_his_o
 SELECT
     order_id,
     user_id,
@@ -839,20 +855,15 @@ FROM v_raw_trd_legacy_order_stage
 ;
 ```
 
-**CTAS vs INSERT OVERWRITE 何时用哪个**:
+**raw 层写入模式对照**:
 
-| 场景 | 推荐写法 | 是否需要 `manual/ddl/` 文件 |
+| 场景 | 写法 | `manual/ddl/` |
 |---|---|---|
-| **一次性 CSV 导入**(历史回刷、单批 vendor 数据),表名 `raw_xxx_his_o` | `DROP TABLE + CREATE TABLE ... AS SELECT`,**不分区** | 否,CTAS 一步搞定 |
-| **每日重复的 CSV 导入**(daily file drop) | 在 `manual/ddl/` 先建分区表,每日 SQL 用 `INSERT OVERWRITE TABLE ... PARTITION (dt='${dt}')` | 是,需要 `manual/ddl/{表名}.sql` |
-| **结构化源库同步**(PG/MySQL 等) | DataX ini,`writeMode=truncate` 或分区覆盖 | 是,需要 `manual/ddl/{表名}.sql` |
-
-**`his` 表为什么不分区**:一次性导入永不追加,分区裁剪没有意义;CTAS 也不适合直接建分区表(每次都会 `DROP`,无法保留历史分区)。如果业务要求"按日期看一眼",下游 ods 建分区表,用 `INSERT OVERWRITE PARTITION (dt) ... DISTRIBUTE BY dt` 一次性把 raw → ods 切片即可(见 §9.3.1)。
+| **一次性 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` 或分区覆盖) | 需要 |
 
-**为什么 CSV 一次性导入推荐 CTAS**:
-- 省掉单独写 `manual/ddl/{表名}.sql` 的步骤,减少维护点
-- 字段类型由 SELECT 列自动确定(CSV 全 STRING 场景下 Spark 默认推断为 STRING,符合 raw 层契约)
-- `DROP + CREATE` 配合 `IF EXISTS` 是幂等的,重跑安全
+**`his` 表为什么不分区**:一次性导入永不追加,分区裁剪没有意义。下游 ods 再按 `dt` 分区,用 `INSERT OVERWRITE PARTITION (dt) ... DISTRIBUTE BY dt` 一次性把 raw → ods 切片(见 §9.3.1)。
 
 **为什么用 SQL 而不是 YAML 描述 CSV 任务**:
 - 复用 `SparkSQL` 现有执行链,`bin/csv-to-hdfs-starter.py` 只需在 `bin/spark-sql-starter.py` 之外加一层 gzip+put+清理的薄壳,不需要单独的 YAML 渲染器

+ 1 - 1
kb/20-数仓分层与建模.md

@@ -197,7 +197,7 @@ RDS PG / ES ──DataX──▶ RAW ──SparkSQL──▶ ODS ──▶ DWD 
 
 ### 8.1 raw 层:schema-on-read landing
 
-- **全字段 STRING**:raw 层所有表业务字段一律 `STRING` 类型
+- **全字段 STRING**:raw 层所有表业务字段以及 `dt` 分区字段一律 `STRING` 类型
 - **同步任务不做类型转换**:DataX ini 里不写 `columnType` 的类型映射(或统一填 `string`),CSV 导入时 SparkSQL 读取后也不 `CAST`
 - **外部表兜底**:raw 层建表一律用 `CREATE EXTERNAL TABLE`,DROP TABLE 只删元数据,HDFS 数据保留;raw 作为链路兜底层,误删元数据时数据仍可 `MSCK REPAIR` / 重建元数据恢复,无需回源库重同步
 

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

@@ -177,5 +177,6 @@
 | 2026-04-20 | **里程碑:DataX + Spark SQL 双入口在新 CDH 环境首次端到端冒烟跑通(tag `milestone/datax+spark-smoke-2026-04-20`)**:链路 DataX 导入 PG `dim_calendar` → Hive `test.dim_calendar_dw2`(`workspace/20260420/dim_calendar.ini`),Spark SQL 从 Hive 查回(`workspace/20260420/select_dim_calendar_dw2.sql`),端到端返回 10 行。新 CDH 环境适配前后共三处连锁修复:(a) `cf87744` 恢复 `HADOOP_CONF_DIR`(Spark on YARN 启动强校验);(b) `5b2569a` 删除 `geo_hash` UDF + `pygeohash` 依赖(common auto-load 链路无 fallback);(c) 本次 `SPARK_CONF_DIR=/etc/spark/conf` setdefault(pip pyspark 默认指向自身空 conf/,缺此项 `enableHiveSupport` 静默回落 in-memory metastore,看不到 HMS 真实库表)。tag 为 annotated tag 指向本次 commit,本地 + 远程均可见 | — |
 | 2026-04-20 | **存储格式约定反转:`orc.compress` 不显式写 `NONE`,走 ORC 默认(ZLIB)**:`workspace/20260420/dim_pub_calendar_ful_o.sql` 建表时写了 `TBLPROPERTIES ('orc.compress' = 'NONE')`,核查 ORC 默认压缩为 ZLIB,NONE 是显式关闭压缩(非 no-op)。前期 kb 三处口径"咱不压缩放弃磁盘换 CPU"属于过早决策,小表省 CPU 微乎其微、业务表规模上来后 ZLIB 更优,维护一套"显式写 NONE"的约束成本大于收益。改为走默认、建表不加 `TBLPROPERTIES`。文档同步三处:`kb/20-数仓分层与建模.md §7` 压缩行(`orc.compress=NONE` → 走 ORC 默认)、`kb/00-项目架构.md §9.1` 末尾存储格式约定行(删 `+ orc.compress=NONE`)、`kb/00-项目架构.md` raw 层 CTAS 样例(删 `OPTIONS ('orc.compress'='NONE')` 一行)。未在文档里加"为什么不显式写 NONE"的解释性注释(见用户反馈) | — |
 | 2026-04-20 | **kb/20 分区/raw STRING 口径收敛**:§7 分区字段补 `dt` 类型 `STRING` + 格式 `YYYYMMDD`(如 `20260101`);§8.1 "全字段 STRING" bullet 去掉 "(不含 `dt` 分区)"(dt 本身也是 STRING,括号让人误以为 dt 是另一种类型) | — |
+| 2026-04-20 | **raw EXTERNAL 规范硬化:kb/00 §9.2 CTAS 样例改 INSERT OVERWRITE**:关闭上一批 changelog 的"CTAS + EXTERNAL 冲突待拍"。(a) kb/00 §9.2 CSV 一次性导入样例从 CTAS 改为预建 `EXTERNAL TABLE` + `INSERT OVERWRITE` 两步;"CTAS vs INSERT OVERWRITE" 对照表替换为"raw 层写入模式对照"(3 个场景全部 `EXTERNAL` + `INSERT OVERWRITE`);"为什么 CSV 一次性导入推荐 CTAS" 论证段删除(论点作废);raw 层不再有"省 manual/ddl/"的例外。(b) kb/20 §8.1 "全字段 STRING" bullet 改为 "raw 层所有表字段(含 `dt` 分区)一律 STRING 类型"(与 §7 dt STRING 规范对齐,消除"业务字段 vs 分区字段"的措辞分裂) | — |
 | 2026-04-20 | **kb/90 新增 §2.12 通用 UDF 注释完整化 + 自查表(聚簇 B 延伸)**:`dw_base/udf/common/spark_common_udf.py` 40 函数注释粗细不一,且当前 common/ auto-load 链路没有任何"新增 UDF 需要登记哪里"的准入规则。三档改造:(a) 40 函数 docstring 统一 5 段模板(摘要 / 入参 / 返回 / 异常与边界 / SQL 示例),按 JSON → Array → String → Numeric-Date-Hash → Cross-type 5 批分 commit;(b) 新建 `kb/31-UDF 手册.md`(与 `30-开发规范.md` 同级独立文档,方案 A 而非并入 30),表头 `函数名 / 分类 / 入参 / 返回 / 摘要 / 代码位置 / 补注释状态`,初版登记 40 函数全量,新增通用 UDF 进 common/ 时必须同步登记;business/ UDF 在自己的子目录 README 维护,不走此表;(c) `kb/30-开发规范.md` 或 `CLAUDE.md` 加硬约束"增删 common/ UDF 先读 kb/31 + 同步更新",与 `tests/unit/udf/test_spark_common_udf.py`(§2.11 占位 registry 登记的阶段 4 首批单测目标)配套(自查表为开发者服务,单测为回归服务)。本条是 2026-04-20 UDF 模块重组(本 changelog 之前记录的 UDF 6 文件合一 + business/common 分离)的延伸,不动 auto-load 机制,只补文档与规则 | — |
 | 2026-04-20 | **dw_base 占位模块骨架 + tests 骨架 + bin 收口(B4 提前 + C 起步)**:(a) 新建 5 个占位模块 `dw_base/io/{db,file,hdfs}/` + `dw_base/ops/` + `dw_base/pm/` + `dw_base/dq/` + `dw_base/sync/`,每个带 `__init__.py` + `README.md`(4 节:职责/接口/依赖/状态);实现留待后续阶段。(b) `tests/{unit,integration}/` 骨架 + `tests/README.md` + `.gitkeep`;首批单测目标 `tests/unit/udf/test_spark_common_udf.py`(40 函数)。(c) `bin/excel_to_hive.py` 删除(一次性工具,有需求重做);`publish.sh` 从项目根 `git mv` 到 `bin/publish.sh`(publish 是 DS 调度入口 = 和 bin 同类)。代码侧单次 commit `6936460`。(d) 文档侧同步:`kb/30-开发规范.md §4.5 占位模块规范`(4 节标准 + "空 __init__.py 无 README → 删"铁律);`kb/90-重构路线.md` 按聚簇 + DAG 重组(新增 §〇 全景与 DAG、§2.10 common/utils/io/ops 四模块律、§2.11 新占位 registry、§六.1 tests 骨架标注、§八 从 P0-P3 线性表替换为聚簇 A-F 推进视图;所有主章节加 `[聚簇 X]` 标签;§2.1 publish.sh 行改为 `bin/publish.sh`);本文档总览引入聚簇视图说明 + 阶段 1/2/4 状态改"推进中 / 部分提前完成" | — |