# 重构进度 > 本文档追踪 `poyee-data-warehouse` 重构任务的执行状态。每次完成一项,在对应 checkbox 打勾并写入完成日期。 > 与 `90-重构路线.md` 配套使用:90 说"为什么改、怎么改",92 说"改到哪一步了"。 ## 总览 **推进组织方式**:2026-04-20 起 kb/90-重构路线.md 改为聚簇 + DAG 视图(A 配置外移 / B dw_base 重组 / C bin 收口 / D 基础设施 / E 业务 SQL / F 老代码删除),阶段号保留作为历史分期,**实际推进按聚簇**;见 kb/90 §〇 + §八。本 checklist 下方的阶段分节仍保留,便于按阶段对账。 | 阶段 | 状态 | 开始日期 | 完成日期 | |------|------|---------|---------| | 阶段 0:知识库梳理 | ✅ 已完成 | — | 2026-04-14 | | 阶段 1:P0 骨架重命名 | 🟡 推进中 | 2026-04-15 | — | | 阶段 2:P1 硬编码外置 | 🟡 部分提前完成(B4 骨架 / bin/publish.sh / excel_to_hive 删) | 2026-04-20 | — | | 阶段 4:测试体系 + 废弃代码清理 | 🟡 tests 骨架已建 / requirements 精简已完成 | 2026-04-15 | — | | 阶段 5:老项目残留删除 | ⬜ 未开始 | — | — | > 原"阶段 3:业务 SQL 从零开发"不属于重构 scope(2026-04-21 移除,详见变更记录),已从本 checklist 删除。业务 SQL 开发走独立路径,不在本进度追踪内;阶段 5 的前置条件相应调整为"新业务 SQL 生产稳定运行"。 当前所处阶段:**主线阶段 1(P0 骨架)推进中;阶段 2 / 4 部分先导项随聚簇 B4 提前完成**。 --- ## 阶段 0:知识库梳理 ✅ - [x] 项目架构文档 `00-项目架构.md` - [x] 运行环境 `01-运行环境.md` - [x] 权限与账号 `02-权限与账号.md` - [x] 业务流程 `10-业务流程.md` - [x] 数据资产 `11-数据资产.md` - [x] 数仓分层与建模 `20-数仓分层与建模.md` - [x] 命名规范 `21-命名规范.md` - [x] 指标体系 `22-指标体系.md` - [x] 标签体系 `23-标签体系.md` - [x] 开发规范 `30-开发规范.md` - [x] 重构路线 `90-重构路线.md` - [x] 项目根 `CLAUDE.md` 指向 kb ## 阶段 1:P0 骨架重命名 **目标**:让新项目结构立起来,老项目代码可以正常运行不受影响。 - [x] `tendata/` 目录改名为 `dw_base/`(2026-04-15) - [x] 全局替换 `from tendata` / `import tendata` → `from dw_base` / `import dw_base`(2026-04-15,102 个文件) - [x] 全局替换 SQL 中的 `ADD FILE tendata/...` → `ADD FILE dw_base/...`(2026-04-15) - [x] 全局替换 `zip -qr tendata.zip tendata` → `zip -qr dw_base.zip dw_base`(2026-04-15,spark_sql.py f-string 形式已手工修正) - [x] 全局替换 `addPyFile('tendata.zip')` → `addPyFile('dw_base.zip')`(2026-04-15,publish.sh 同步更新) - [x] 全局替换路径正则 `re.sub(r"tendata-warehouse.*", ...)` → `poyee-data-warehouse`(2026-04-22,`dw_base/utils/file_utils.py:9` + `dw_base/utils/hdfs_merge_small_file.py:7` 两处字面量) - [x] 排查 `tendata_corp` 等数据库名/表名引用,**确认不要误替换**(2026-04-15,已确认保留:`tendata_corp`、`tendata_bigdata256!`、`ent_tendata_interface`、`api.tendata.cn`) - [x] 新建 `jobs/` 目录 + `jobs/{raw,ods,dim,dwd,dws,tdm,ads}/` 子目录(2026-04-15,已放 `.gitkeep`,`dim/` 为顶层独立分层) - [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/spark-sql-starter.py` 样例,import 和 Spark 环境初始化能过 - [ ] 验证:在测试集群跑一次 `bin/datax-single-job-starter.sh` 样例,ini→json 生成能过 - [ ] 验证:`bin/csv-to-hdfs-starter.py` 用一个样例 CSV 跑通 raw 层入仓 **完成判定**:老项目 launch-pad 下的某个样例作业,在改名后的 `dw_base/` 基础上能跑通(import 和运行不报错)。 ## 阶段 2:P1 硬编码外置 **目标**:消除对老环境(`alvis` 用户、`/home/alvis/release` 路径、硬编码的 worker 列表)的代码耦合。 - [ ] 新建项目根 `.gitignore`(清单与注意事项见 `90-重构路线.md` §2.4) - [x] 建立 `conf/env.sh`(Shell + Python 环境变量单源,5 vars:RELEASE_USER / RELEASE_ROOT_DIR / PYTHON3_PATH / DATAX_HOME / LOG_ROOT_DIR) - [x] Python 侧由 `dw_base/utils/env_loader.py` 通过 bash 子进程解析注入 `os.environ`(单源,不做双份配置) - [ ] 建立 `conf/workers.ini`(DataX Worker 列表 + 权重 map,整体迁出 `bin/common/init.sh:18-31`) - [ ] 建立 `conf/alerter.ini`(企微 Webhook,**入库**;格式见 `90-重构路线.md` §2.1) - [x] `dw_base/__init__.py` 瘦身(2026-04-21,修剪式,不拆 `core/`;见 `90-重构路线.md` §三 已完成态) - [x] 建立 `conf/spark-defaults.conf`(底层行为/开关类 11 条,少改)+ `conf/spark-tuning.conf`(资源/并行度 10 条,业务常改)(2026-04-21,Spark 原生格式;两文件拆分,见 `90-重构路线.md` §2.3) - [x] 改造 `dw_base/spark/spark_sql.py`:构造函数 10 个 tuning 默认值 → `None` sentinel;新增 `_load_spark_conf_file()`;`__init_spark_session` 按 L1(两 conf 叠加) < L2(SQL SET) < L3(构造参数非 None + `extra_spark_config`) 三级覆盖(2026-04-21) - [ ] 验证:同一条 SQL 在无 SET、有 SET、命令行 -sc 三种场景下 `spark.conf.get(...)` 返回值符合优先级预期 - [x] `RELEASE_USER="alvis"` → `RELEASE_USER="bigdata"` 并迁入 `conf/env.sh` - [x] `RELEASE_ROOT_DIR="/home/alvis/release"` → `/home/bigdata/release` 并迁入 `conf/env.sh` - [ ] `DATAX_WORKERS=(m3 d1 d2 d3 d4)` + 权重 map 迁入 `conf/workers.ini` - [x] 删除 `whoami == RELEASE_USER` 分流,`LOG_ROOT_DIR` 单值 `${HOME}/log` 放入 `conf/env.sh`(见 `90-重构路线.md` §7.2.1) - [ ] 日志路径统一模板 `${LOG_ROOT_DIR}/{module}/{dt}/{file}.log`(3 层) - [ ] 实现 `log_path(module, dt, file)` 工具函数(Python / Shell 各一份,单一来源) - [ ] 企微 Webhook Key 从 `alerter_constants.py` 移入 `conf/alerter.ini`(钉钉已于 2026-04-20 全部删除,不再迁移) - [ ] **DataX 入口收口为两条命令**(暂称 `datax-import` / `datax-export`,**命名待确认**;见 `90-重构路线.md` §2.6) - [ ] 导入命令实现分区管理(`-skip-exist` / `-force-overwrite` / `-skip-partitions`) - [ ] 导出命令实现源 HDFS 路径探测(`-src-check` / `-skip-missing`) - [ ] `bin/datax-gc-generator.py` 从零重写:定位为**参考模板生成器**(`from=pg to=hdfs`,输出到开发者本地 `workspace/{yyyymmdd}/{name}.ini`,该目录被 `.gitignore` 排除、不入仓;开发者裁剪后把成品提交到 `jobs/raw/{domain}/`),不再生成 DDL(见 `90-重构路线.md` §2.7) - [ ] `.gitignore` 增加 `workspace/` 行(见 `90-重构路线.md` §2.4 清单) - [ ] 废弃并删除 `mysql_utils`/`MySQLDataSource`/`MySQLReader.generate_hive_ddl*`/`convert_mysql_column_types`(见 §2.7 拆除清单) - [x] **HDFS HA 自检(前置决策)**:2026-04-18 新 CDH 环境实测:`HADOOP_CONF_DIR` 未设 + `/opt/datax` 无预置 hdfs-site.xml;手动 `export HADOOP_CONF_DIR=/etc/hadoop/conf` 后跑 DataX 仍 `UnknownHostException: nameservice1`。结论:`HADOOP_CONF_DIR` 对 DataX 无效(`datax.py` 不把 conf 目录入 classpath),**锁定走 Path B** - [x] 改造 `HDFSDataSource`:覆写 `get_datasource_dict()` 把 `[hadoop_config]` 整节作为 dict 塞进 `ds_dict['hadoopConfig']`(2026-04-18) - [x] 清理 `dw_base/__init__.py:16` 死代码 `os.environ['HADOOP_CONF_DIR']`(2026-04-18,实测对 DataX JVM 无影响) - [ ] HDFS 数据源 ini 新建:按新 schema 运维补齐 `datasource/hdfs/{env}/*.ini`(prod 带 `[hadoop_config]`;dev/test 只写 `[base] defaultFS`) - [ ] HA 回归测试:真实 HA 集群 + 单 NN 集群 + 主备切换三场景 - [ ] **DataX 速率配置外移**:`conf/datax-speed.ini` 定义分时速率档;`dw_base/datax/job_config_generator.py:60-67` 硬编码替换为读 conf(见 `90-重构路线.md` §2.9) - [ ] 新建 `manual/imports/` + `manual/exports/` 目录(按日期 `{yyyymmdd}/` 组织一次性任务) ## 阶段 4:测试体系 + 废弃代码清理 - [ ] 建立 `tests/` 目录骨架(见 `90-重构路线.md` §6) - [ ] UDF 单测首批 - [ ] DataX 配置生成单测 - [ ] `__contains__` → `in` 全局替换 - [ ] 删除废弃空模块和注释代码 - [ ] **重新实现 Hive HDFS 小文件合并工具**:原 `dw_base/utils/hive_file_merge.py`(2026-04-20 随老业务批清理一并删除)提供 `alter table ... partition (...) concatenate` 压实能力,但硬编码了老 HiveServer 连接 / `cts_*_ex/_im` 表名规则 / `mirror_country` 过滤。新版需通用化:HiveServer 连接从 `conf/` 读取、表过滤参数化,剥离业务命名假设 - [ ] **重写告警模块**:老钉钉告警文件(`dingtalk_*` / `ent_interface_dingtalk*` / `country_count_dingtalk` / `spark_parse_json_to_hive` 里的 `dingtalk()` / `bin/dingtalk-work-alert.sh`)已于 2026-04-20 全部删除;新项目不再使用钉钉,Webhook Key 走 `conf/alerter.ini`(见 `90-重构路线.md §2.1`) - [ ] **重新实现分区保留工具**:老 `dw_base/scheduler/drop_partitions.py` + `drop_daily_full_snapshot_tbls.py`(2026-04-20 删除)提供"按表清理超期分区,保留最近 N 天 + 例外 dt"能力。前者硬编码海关 `cts_{mgdb}_{catalog}` 表名 + `mg_count_monitor` 元表,后者元表驱动(`daily_full_snapshot_tbls` 存 `db/tbl/days`)模式更通用。新版采用元表驱动 + 保留天数参数化 + 例外 dt 白名单,不绑业务表名;新目录可能不叫 scheduler(按 N 天清分区不是调度职责,更像 `ops/` 或 `maintenance/`) - [ ] Spark / HMS 侧 Ranger Hive 策略验证(低优先级,见 `90-重构路线.md` §7.5) - [x] 精简 `requirements.txt`(2026-04-15 提前完成:48 行 → 10 个强依赖,老清单备份到 `requirements.txt.bak` 并逐行打标) ## 阶段 5:老项目残留删除 **前置条件**:新业务 SQL(不属于本重构 scope)在生产稳定运行至少一个完整周期,且 DS 工作流已完全切换到 `jobs/`。 - [ ] 确认 DS 工作流已无对 `launch-pad/` 的引用 - [ ] 删除 `launch-pad/` 整个目录 - [ ] 删除其他已确认废弃的老文件 --- ## 变更记录 | 日期 | 变更 | 操作人 | |------|------|--------| | 2026-04-14 | 初始化进度文档,阶段 0 知识库梳理完成 | — | | 2026-04-15 | `90-重构建议.md` 更名 `90-重构路线.md`;新增 `his` 快照 + `dim` 顶层分层;`manual/ddl/` 作为 DDL 唯一来源(migration 模式);精简 `requirements.txt`(提前完成阶段 4 一项);新增 Spark 配置三级覆盖路线(L1 `conf/spark-defaults.yaml` / L2 SQL `SET` / L3 命令行 `-sc`,阶段 2 任务) | — | | 2026-04-15 | 阶段 1 模块重命名:`tendata/` → `dw_base/`(目录 + 102 个文件的 import / SQL `ADD FILE` / `zip` 打包 / `addPyFile` / `publish.sh`);跳过项:`tendata-warehouse` 正则(待仓库改名)、`tendata_corp` 等业务库名、`ent_tendata_interface` topic、`api.tendata.cn` URL | — | | 2026-04-15 | 阶段 1 目录骨架:新建 `jobs/{raw,ods,dim,dwd,dws,tdm,ads}/` 与 `manual/{ddl,backfill,fix,adhoc,archive}/`,均放 `.gitkeep` 占位 | — | | 2026-04-15 | `sql_style.xml` 从项目根移入 `kb/`,`30-开发规范.md` §4.2 补 IDE 格式化导入说明、关键风格表与"不对齐 AS"理由 | — | | 2026-04-15 | 新增 `.gitignore` 计划(`90-重构路线.md` §2.4 + 阶段 2 checklist),含 `.idea/` / `.claude/` 部分 ignore 策略、`dw_base.zip` 构建产物、`conf/alerter.conf` 敏感项;阶段 1 仓库改名 checklist 追记 `.iml` 同步改名 | — | | 2026-04-18 | `sql_style.xml` 从 `kb/` 移入 `conf/`(IDE 配置不算文档);同步更新 `kb/README.md` 索引、`kb/30-开发规范.md` §4.2.1 路径引用、移除 `kb/90-重构路线.md` §5.1 对应待办行 | — | | 2026-04-18 | kb 文档整理:`zhu_tianyu` → `tianyu.chu`(`00-项目架构.md` 2 处负责人注释);`kb/inbox/` 4 份草稿整合完毕 —— `标签服务演进路线.md` 与 `23-标签体系.md §6` 100% 重复直接删除;`dwd明细粒度设计原则.md` 并入 `20-数仓分层与建模.md` §5.5;`hive数据类型映射.md` 并入 `20-数仓分层与建模.md` §8.4(ES→Hive 占位待补);`业务库同步方案.md` 独立成文为 `kb/12-同步方案.md` 并入 README 索引 | — | | 2026-04-18 | `02-权限与账号.md §1` 补齐 PySpark 鉴权路线(链路 B):Unix 账号身份 + Ranger UserSync 同时同步 LDAP / Unix group;HS2 doAs 仅链路 A 生效;补漏编号 1 并新增"身份(Who)"条目 | — | | 2026-04-18 | 架构框图重排:`01-运行环境.md §1` 大数据平台全景、`20-数仓分层与建模.md §2` 分层 × 维度侧柱、`00-项目架构.md §5` 分层架构图 —— 统一"单层单行 + 右侧 DIM 侧柱"样式,按"中文字符宽 2 / ASCII 宽 1"严格对齐;同步修复三文档数据流 ASCII 尾巴的 orphan `│` 和空行 | — | | 2026-04-18 | `02-权限与账号.md §2` Mermaid 时序图补齐链路 B(PySpark → HMS + Ranger Hive Plugin → NameNode + Ranger HDFS Plugin → HDFS,身份=Unix 账号,无 doAs) | — | | 2026-04-18 | **反转两条决策**:(a) `manual/ddl/` 目录组织从"扁平存放"→ 按 `{layer}/{domain}/` 分子目录;(b) `jobs/{layer}/{domain}/` 下 SQL 从"一张目标表一个 .sql"→ 简单表仍单文件,**多步表**用 `{表名}/{表名}-{NN}-{描述}.sql` 子目录(`99` 为最终 insert),区分"可复用中间结果应升层"和"单目标加速 tmp 表"两种语义;同步更新 `00-项目架构.md §9.1/§9.2/§9.5` 文件命名速查表 | — | | 2026-04-18 | `90-重构路线.md` 新增 §2.6 DataX 入口收口为 `datax-import` + `datax-export`、§2.7 `datax-gc-generator` 从零重写(仅 PG→HDFS,不再生成 DDL)、§2.8 HDFS defaultFS 统一 nameservice;§2.1 workers 行追记"权重 map 一起迁";§7.2.1 反转"删除 whoami 分流"→"保留分流 + 目的地改为 `{module}/{dt}/{file}.log`" | — | | 2026-04-18 | `manual/` 新增两个子目录语义:`manual/imports/{yyyymmdd}/`(一次性入仓)+ `manual/exports/{yyyymmdd}/`(一次性出仓),同步更新 `00-项目架构.md §1 目录树` / §9 `manual/` 用途表 / §9.5 文件命名速查 | — | | 2026-04-18 | 修正 `90-重构路线.md §2.8` 关于 HDFS nameservice 的表述:代码侧零特殊处理(`dw_base/datax/datasources/hdfs_data_source.py:8,21` 只把 `defaultFS` 当不透明字符串透传),nameservice 解析完全靠 DataX worker 节点的 Hadoop 客户端 + `hdfs-site.xml`;仓库内无 `HADOOP_CONF_DIR` export | — | | 2026-04-18 | `90-重构路线.md §2.7` 改写 `datax-gc-generator` 定位为**参考模板生成器**(不是"一键出可用 ini"),产物落 `manual/imports/{yyyymmdd}/` 供开发者按需裁剪字段 / 加 WHERE / 调分区;§2.6 `datax-import` / `datax-export` 名称标注"命名待确认" | — | | 2026-04-18 | `30-开发规范.md` 新增 §4.4 Git 提交信息(Conventional Commits):type 白名单(feat/fix/docs/refactor/perf/test/chore/style/build/ci/revert)、scope 约定(`{层}/{域}` 或模块名)、标题 ≤ 50 字符、破坏性变更 `!` + `BREAKING CHANGE:` 正文约定 | — | | 2026-04-18 | 新增 `workspace/` 概念:开发者本地草稿区(`datax-gc-generator` 输出的参考模板 / 临时 SQL 调试),被 `.gitignore` 排除、永不入仓。同步更新 `90-重构路线.md §2.4` `.gitignore` 清单增加 `workspace/` 行、§2.7 `datax-gc-generator` 输出目的地从 `manual/imports/` 改为 `workspace/{yyyymmdd}/{name}.ini`;并明确 `workspace/` vs `manual/imports/` 分工 | — | | 2026-04-18 | **修正 §2.8**:原"把 `defaultFS` 换 nameservice 就算支持 HA"是错的;DataX HA 必须在 json 里同时提供 `defaultFS` + `hadoopConfig` 块(否则无法把 nameservice 解析为具体 NameNode)。ini schema 升级:新增 `ha_enabled` + `[hadoop_config]` 节,key 照搬 `hdfs-site.xml`;`HDFSDataSource` 需覆写 `get_datasource_dict()`;`reader/writer` 两侧代码不用改(靠 `load_data_source()` 自动注入) | — | | 2026-04-18 | **新增 §2.9**:DataX 速率控制从 `job_config_generator.py:60-67` 的分时硬编码(0750-1900 走 10ch×10MB、其它走 6ch×256MB)外移到 `conf/datax-speed.conf`,按时间段分档;默认值保持向后兼容;在 conf 头注释里补"白天避开业务高峰"的动机 | — | | 2026-04-18 | **排查"运维写死 nameservice"实现**:全仓 grep `HADOOP_OPTS` / `-Dfs.` / 自定义 `hdfs-site.xml` 写入等全部零命中;现存 `conf/bak/.../hdfs-*.ini` 只有 `defaultFS` 一行。仓库零实现。 | — | | 2026-04-18 | 修正早先文档误述:`dw_base/__init__.py:16` 实际上有 `os.environ['HADOOP_CONF_DIR'] = '/etc/hadoop/conf'`(原 2026-04-18 changelog 早条说"仓库内无 HADOOP_CONF_DIR export"不准确) | — | | 2026-04-18 | **§2.8 改造降级为"条件触发"**(第三轮修正):用户提供老项目真实生产 json 样例显示只写 `defaultFS`(无 `hadoopConfig`)也能跑 HA —— 说明老 worker 节点 `hdfs-site.xml` 配置完整,`hadoopConfig` 是**可选覆盖**而非 HA 必要条件。前两轮论断("必须加 `hadoopConfig`"、"运维把 xml 写死单 NN")都被推翻。§2.8 加"新环境 HDFS HA 自检清单"(`echo $HADOOP_CONF_DIR` / grep xml HA keys / `hadoop fs -ls hdfs://nameservice1/`),三项全过则整节改造不做;仅任何一项失败才启动 ini schema 升级 + `HDFSDataSource` 改造。92 阶段 2 checklist 相应改为"自检前置 + 条件触发"4 条子项 | — | | 2026-04-18 | **§2.8 锁定 Path B(第四轮,实测决定)**:新 CDH 环境三连实测(json 含/不含 `hadoopConfig` × `HADOOP_CONF_DIR` 设/不设),结论:对 DataX JVM,仅 json 的 `hadoopConfig` 块有效,`HADOOP_CONF_DIR` 无效(`datax.py` 不把 conf 目录入 classpath,与 `hadoop` 命令行不同)。老项目能纯 `defaultFS` 跑通最可能是老运维把 `hdfs-site.xml` 塞进了 DataX classpath 目录,新环境 `/opt/datax` 没这类预置文件。改造要点:(a) `HDFSDataSource.get_datasource_dict()` 吃 `[hadoop_config]` 整节注入 `hadoopConfig`;(b) 删除 `dw_base/__init__.py:16` `os.environ['HADOOP_CONF_DIR']` 死代码。简化 §2.8 文本:去掉 `ha_enabled` 开关(用 `[hadoop_config]` 节存在性代替)、去掉自检决策树(已决定)、去掉"运维手工改 IP"误记 | — | | 2026-04-20 | **§7.2.1 再次反转**:删除 `whoami == RELEASE_USER` 分流,`LOG_ROOT_DIR` 改为单值默认 `${HOME}/log` 并保留在 `conf/env.sh`(外配后期可改)。理由:`$HOME` 天然按用户隔离(bigdata/个人用户家目录不同),代码判断是多余一层;`bigdata` 本身就是专属调度账号,其 `$HOME` 即是生产日志合法归宿,不需要系统级 `/opt/data/log` 那条路。同步更新 `90-重构路线.md §7.2.1`(核心段)+ `§2.1 硬编码表行` + `§2.4 env.sh 草稿` + `00-项目架构.md §6 部署段` + `92 阶段 2 checklist` | — | | 2026-04-20 | **老业务耦合代码第二批清理(重构计划外)**:在 UDF/模块独立化讨论中顺带盘点 `dw_base/` 子模块,决定 16 文件批量删除:**整目录删 3 个**——`oss/`(oss2_util.py + __init__,新业务不需要对象存储)、`scheduler/`(polling_scheduler / drop_partitions / drop_daily_full_snapshot_tbls 三业务文件,前者绑死老 Mongo 轮询、后两者按 N 天清分区的能力已在阶段 4 记录重写任务)、`hive/`(hive_utils + hive_constants;hive_utils 中 `get_hive_create_table_ddl*` 零引用 + 依赖 `COLUMN_NAME_COMMENT_DICT` 老业务字段字典、DDL 生成器整体不重建;`get_hive_database_name` / `get_hive_table_prefix` 两个命名约定函数语义已在 `kb/21-命名规范.md` 有规则,不重建代码,后续 `bin/datax-gc-generator.py` 从零重写时按新约定实现);**utils/ 删 7 文件**——data_distinct / diff_utils / excel_to_hive_utils / hive_diff_database / hive_to_excel_utils / pdt_check_table / pdt_check_table_multis,全部零外部引用 + 强业务耦合(硬编码 tendata 路径 / 老集群 IP `192.168.30.3` / 中文表名拼音转换 / 海关 `cts_*` 表名模式)。**连带效应**:`bin/datax-gc-generator.py:26` import hive_utils 成破损 import,由 90-路线 §2.7 "从零重写" 任务覆盖,不单独修复。**阶段 4 新增任务**:重新实现分区保留工具(元表驱动 + 参数化天数,目录可能不叫 scheduler)。**CLAUDE.md 规则追加**:"空模块直接删"原则首次执行延后(elasticsearch/flink/ml/validation/common/ 暂留,后续更细粒度规整) | — | | 2026-04-20 | **老业务耦合代码批量清理(重构计划外)**:排查 `tendata` 残留时发现一批与 `tendata_corp` / `ent_tendata_interface` / DolphinScheduler / 钉钉告警强耦合的存量文件,逐项核对后批量删除 40 个文件 + 精简 1 个:**老业务模块 34**(`dw_base/scheduler/` 下 `get_oldmongo_*` ×5、`dingtalk_*` / `ent_interface_dingtalk*` / `country_count_dingtalk` / `mg_company_alias_init` ×8、`mg2es/` 整目录 13 文件;`dw_base/ds/` 整目录 4 文件;`dw_base/spark/udf/spark_read_hive_columns_cnt.py`;`dw_base/utils/tid_utils.py`;`dw_base/spark/td_spark_init.py`(老同事 xunxu 所写未被调用);`bin/hive-exec.sh`),**级联清理 6**(`dw_base/spark/udf/spark_id_generate_udf.py` + `dw_base/spark/udf/enterprise/unique/spark_tid_match_udf.py` 依赖已删 `tid_utils`;`dw_base/utils/hive_file_merge.py` + `dw_base/utils/spark_parse_json_to_hive.py` 依赖已删 `mg2es`/钉钉告警;`bin/hive-exec-job-starter.py` 调用已删 `hive-exec.sh`;`bin/dingtalk-work-alert.sh`),**精简 1**:`dw_base/spark/udf/spark_mmq_udf.py` 从 530 行裁到 4 个数据类型转换函数(phone/domain/website/statname 等场景相关 UDF 与 Mongo 相关逻辑全删)。同步更新:`00-项目架构.md`(移除 `td_spark_init` / DS 相关条目)、`90-重构路线.md`(钉钉 + 企微 Webhook 合并表述、删除 DS API 行、§5.2 依赖清理清单标记提前完成)、`92-进度.md` 阶段 1 第 6 行 `re.sub` checklist 更新残留范围(~15 处)。**阶段 4 新增两项任务**:(1) 重新实现 Hive HDFS 小文件合并工具(通用化连接 / 剥离 `cts_*_ex/_im` 表名假设);(2) 重写告警模块(弃钉钉走 `conf/alerter.ini` Webhook) | — | | 2026-04-20 | **pyspark 入 requirements.txt(反转 2026-04-15 "不写进" 决策)**:实际开发流程是 PyCharm SSH 到服务器用 bigdata 用户的 Python 解释器,该解释器 site-packages 只有 `findspark==2.0.1`,真正的 pyspark 在 CDH `$SPARK_HOME/python/` 下不被索引 → PyCharm 静态索引红线 + 本地 pytest 失败。解法:`pyspark==2.4.0` 入 `requirements.txt`(版本对齐 CDH 6.3.2 parcel 自带 Spark 2.4.0,见 `requirements.txt.bak:45` + `kb/01-运行环境.md:64`);运行时仍靠 `findspark.init()` 指向集群 `$SPARK_HOME/python/`,同版本双轨不冲突。同步 `kb/90-重构路线.md §7.1` KEEP 行放回 pyspark + 末句机制改写 | — | | 2026-04-20 | **UDF 提升为顶层模块(重构计划外)**:`dw_base/spark/udf/` → `dw_base/udf/`。动机:UDF 是独立能力域(后续会高频扩展、需本地单测),不应锁死在 `spark/` 子树里。联动:`dw_base/__init__.py:27` 常量、`bin/spark-sql-starter.py` + `bin/excel_to_hive.py` 文件头 SQL 样例注释、`dw_base/udf/common/spark_common_udf.py` 模块 docstring、`kb/00-项目架构.md`(目录树新增 `udf/` 行 + 模块职责表 + Mermaid 节点)、`kb/23-标签体系.md §5` bitmap UDF 注册路径。`bin/spark-sql-starter.py:172-173` 用的是常量自动生效 | — | | 2026-04-20 | **修正 §7.1 pyspark 误记**:前期文档把 pyspark 列进强依赖 KEEP 行 + "pyspark 2.4.0 固定" 一句,均与真实的 `requirements.txt` 不符。真实机制:`findspark==2.0.1` 运行时定位 CDH 集群已装 PySpark,版本随集群走,客户端不固定也不入 `requirements.txt`。kb/90 §7.1 表格 KEEP 列去 pyspark + "后续事项"末行改为 findspark 机制说明 | — | | 2026-04-20 | **UDF 模块重组(重构计划外)**:独立 `dw_base/spark/udf/` 目录结构为 `common/`(通用 UDF,SparkSQL 入口自动 `ADD FILE` 注册)+ `business/`(业务专用 UDF,SQL 中按需 `ADD FILE` 加载)两类。(a) 6 份源文件(根 `spark_common_udf.py` 24 函数 + `spark_json_array_udf.py` 23 函数 + `spark_mmq_udf.py` 3 函数 + `customs/cts_common.py` + `product/escape_udf.py` + `enterprise/spark_eng_ent_json_array_append_udf.py`)通读 + 去重 + 业务耦合剥离后,合并为单文件 `common/spark_common_udf.py`(500 行 40 函数,分 JSON / Array / String / Numeric-Date-Hash / Cross-type-converters 5 段)。单文件方案而非按类型拆分,理由:跨类型转换函数(`json2str` / `arr2json` / `str2map` 等约 9 个,占 20%+)没有明确归属,强行分只会制造边界争议。(b) 清理 `dw_base/spark/udf/` 下所有老业务 UDF 子目录与根级业务文件共 60 个:整目录删 `contacts/` / `customs/` / `enterprise/` / `product/` / `productApplication/` / `test/`;根目录删 `spark_eng_ent_name_clean.py` / `spark_india_format_phone_udf.py` / `solr_similar_match_udf.py` / `main_test.py` 以及 3 份源 UDF 文件。(c) `dw_base/__init__.py:27` `COMMON_SPARK_UDF_FILE` 常量路径由 `dw_base/spark/udf/spark_common_udf.py` 改为 `dw_base/spark/udf/common/spark_common_udf.py`(`bin/spark-sql-starter.py:172-173` 两处 usage 靠常量传递自动生效)。(d) 删除老 `dingtalk_*` / `mg2es` 级联清理中没赶上的 UDF 业务耦合文件在此批统一清零。`business/` 目录暂为骨架,后续真正出现新业务 UDF 时按需补 | — | | 2026-04-20 | **删除空壳模块 `ml/` / `elasticsearch/` / `flink/` / `validation/`(反转 2026-04-20 早先"暂留"记录)**:4 个目录下均只有 56 字节空 `__init__.py`,零 import / 零内容,保留无意义;2026-04-20 UDF 模块重组 changelog 末尾"暂留"一句是误记。`git rm -r` 一批清零。同步 `kb/90-重构路线.md §5.1` 从废弃代码表中移除这 4 行并加指向本条 changelog 的尾注。`dw_base/common/` 因 `alerter_constants.py` / `config_constants.py` / `container.py` / `template_constants.py` 非空保留,不在本批 | — | | 2026-04-20 | **删除 `dw_base/database/mongodb_utils.py` + kb/90 §5.1 档案化改造**:(a) `mongodb_utils.py` 184 行真实内容仅 `MongoDBHandler` 薄类 19 行(吃 url/port/user/pwd 拼 URI 实例化 MongoClient),其余 165 行全是公司名→Mongo/ES 查询 + TF-IDF 关键词抽取 + 三段老集群 `dds-m5e*` 连接串注释。grep 零外部引用。新项目若需连 Mongo 一行 `MongoClient(uri)` 即可,薄包装无保留价值。(b) §5.1 原表两行实际状态:`mongodb_utils.py` = 本次删除;`conf/datax/` 下老项目遗留 ini/datasource = 项目初始化 `8d2ade5` 时已整体挪入 `conf/bak/datax/{config,datasource}/` 并由 `.gitignore:6 conf/bak` 拦截,早已完成但没画勾。(c) §5.1 改造为"待清理表(当前为空)+ 历史档案表(完成项留档)"双表结构,空壳模块 4 行 + 本次 mongodb_utils + conf/datax 挪 bak 三项入档案。尾注说明:代码里残留的 `conf/datax/config/` replace 死逻辑 + `conf/datax/generated` 默认值属于 §2.x 路径硬编码清理(改名 `conf/datax-json/`),不在本节范围 | — | | 2026-04-20 | **alerter + datax-speed 扩展名统一为 `.ini` + alerter 口径统一为"入库"**:(a) `conf/alerter.conf` → `conf/alerter.ini` + 从 gitignore 改为入库;反转 2026-04-15 changelog(line 139)当时记的 `.conf` + gitignore 口径。理由:项目 conf/ 全部 ini + 代码侧 `configparser` 使用 10+ 次 + `PyYAML` 在 requirements 但零 `import yaml`(躺尸依赖)→ ini 是事实标准;webhook key 低敏(最多被拿去发垃圾消息,非账密级)+ 部署靠 git pull,gitignore 会让 bigdata / DolphinScheduler 拉不到配置。结构:`[common] url_prefix` + `[channels] ba/dcp/etl/skb/realtime = `。(b) `conf/datax-speed.conf` → `conf/datax-speed.ini`,同一口径收敛,消除 `.conf/.ini` 混用。同步点:kb/00 §6 sparkconfig 表告警 Webhook 行(gitignore 列 `是` → `否`)、kb/90 §2.1 硬编码表 alerter 行 / §2.4 目录树 / §2.9 整节 4 处、kb/92 阶段 2 checklist 4 处(line 62 去掉"敏感文件"措辞、line 66 改为入库、line 78 钉钉条删除合并入 line 79、line 91 扩展名改)、记忆 `project_templates_and_config` 去掉"kb/92 一致性提醒"段(已消除)。**未改动**:2026-04-15 changelog line 139(历史快照保留)、2026-04-18 changelog line 153(历史快照保留)、`workers.conf` 扩展名 + kb/90 §2.1 里"ini 或 yaml 格式"未定的表述(不在本批范围,用户后续拍板) | — | | 2026-04-20 | **DS 残留清理 + workers / spark-defaults 扩展名收敛**:(a) DS 残留:`dw_base/ds/` 目录在 `f20d9c3` 就随老业务批被删,但前瞻 kb 未同步 —— 本次清 `kb/00-项目架构.md:42` 目录树 `ds/` 行 + `:122` Mermaid 节点 `DS_API` + `:169` Mermaid 边 `DS_API --> DS` + `kb/90-重构路线.md:70-72` 目标态目录树 `ds/` 子树 3 行。(b) `conf/workers.conf` → `conf/workers.ini`(kb/90 §2.1 硬编码表 + §2.4 目录树 + kb/92 checklist 2 处),并把 §2.1 里"ini 或 yaml 格式"的未决表述锁死为"ini 格式"—— 与本项目 Python 读配置统一走 configparser 的约定一致(见上一条 alerter 口径)。(c) `conf/spark-defaults.yaml` → `conf/spark-defaults.conf`(**Spark 原生 flat `spark.x.y value` 格式,非 ini**):反转中途一度采纳的"全部配置 `.ini` 统一"结论,理由是该文件是 `$SPARK_HOME/conf/spark-defaults.conf` 的克隆(运维熟悉、与 `spark-submit --properties-file` 原生兼容、代码侧零映射转换、无 section 前缀/key 拼接 tax)。§2.3 草案块从 `[executor]/[driver]/[sql]/[default]` section + configparser + `spark.{section}.{key}` 拼接的写法,整段改写为 flat key-value;`_load_default_config()` 从 configparser 改为 10 行手写解析器。同步点:kb/90 §2.1 硬编码表 + §2.3 整节(草案 + 代码要点 + 两个落地坑 4 处引用)+ §2.4 目录树、kb/00 §6 配置分类表 + §6.2 三级覆盖图 + §6.2 说明段(启动加载方式从 "configparser" 改为 "Spark 原生 key value")、kb/92 checklist 2 处、记忆 `project_templates_and_config` 扩展名约定段追加 spark-defaults.conf 例外说明。**未改动**:2026-04-15 changelog line 134(`conf/spark-defaults.yaml` 历史快照保留)。**路线外变更同步入册原则**:DS 目录删除在 `f20d9c3` 未同步 kb 前瞻文档,属于此前"默默做完"漏记,本次补 | — | | 2026-04-20 | **`DATAX_HOME` 条件赋值 + 默认值对齐新环境(§2.1 最小铺垫)**:`bin/common/init.sh:17` 从 `DATAX_HOME="/opt/module/datax"` 改为 `DATAX_HOME="${DATAX_HOME:-/opt/datax}"`。触发:dim_calendar 测试同步在服务器上报 `/opt/module/datax/bin/datax.py` 不存在,实际新 CDH 环境 DataX 装在 `/opt/datax`。两步改动:(a) 默认值从 tendata 时代的 `/opt/module/datax` 更正为新项目真实路径 `/opt/datax`,(b) 改为 `${VAR:-default}` 条件赋值,允许 shell 环境 override(未来多机器部署路径不一致时零代码改动)。未做:§2.1 完整外配到 `conf/env.sh` 仍待推进。同步更新 `kb/90-重构路线.md §2.1` 表格对应行的当前值与备注 | — | | 2026-04-20 | **恢复 `dw_base/__init__.py` 的 `HIVE_CONF_DIR` export(反转 2026-04-18 第四轮决策的另一部分;应随 HADOOP_CONF_DIR 恢复时一起做而遗漏,本次补)**:`spark-sql-starter.py` 启动通过 `HADOOP_CONF_DIR` 校验后,查 `test.dim_calendar_dw2` 报 `Table or view not found`,同一张表在 Hive CLI `describe` 正常。根因:`.enableHiveSupport()` 没找到 `hive-site.xml` 会静默回落 in-memory metastore,看不到 HMS 真实库表。和 `HADOOP_CONF_DIR` 是同一批 2026-04-18 被误删的 env export,属同类 bug。整改:(a) 取消 `dw_base/__init__.py:19` `HIVE_CONF_DIR` 的注释;(b) kb/90 §2.8 "老 env 设置"段重写合并 HADOOP_CONF_DIR / HIVE_CONF_DIR 两条说明 + 代码改造清单附表行合并;(c) 教训同步入记忆 `feedback_act_not_flatter.md` 的触发面扩到"修同类问题时邻域扫一遍" | — | | 2026-04-20 | **删除 `geo_hash` UDF + `pygeohash` 依赖(重构计划外)**:`spark-sql-starter.py` 在新 CDH 环境启动时报 `ModuleNotFoundError: No module named 'pygeohash'`(`dw_base/udf/common/spark_common_udf.py:19 import pygeohash`)。根因:2026-04-20 UDF 重组(kb/92:165)把 `geo_hash(lat, lng, precision)` 保留进 `common/`(auto-load),但 `pygeohash` 在 kb/90 §7.1 还标 LAZY 且注释"只被即将清理的老业务代码引用"—— UDF 进 common 后该分类失效。当前阶段业务 SQL 尚未开始,`geo_hash` 零现成消费者;真需要 geo 分析再从老项目 copy + 放 `business/`。整改:(a) 删 `spark_common_udf.py:19 import pygeohash` + `:375-376 def geo_hash(...)`;(b) kb/90 §7.1 LAZY 行的 `pygeohash` 移除(仓库零引用,不需要 LAZY 档) | — | | 2026-04-20 | **kb/00 目录树同步真实状态 + §2/§3 收敛**:`dw_base/` 实际子目录已变(2026-04-20 删 `scheduler/` / `hive/`,新建占位 `io/` / `ops/` / `dq/` / `pm/` / `sync/`),kb/00 §1 目录树仍停留在旧态;§2 核心模块职责表 + §3 Mermaid 均与 §1 同一信息三视图重复。整改:(a) §1 目录树同步真实子目录,职责并入每行行尾注释;顺带补漏 `publish.sh` 已挪入 `bin/`(`6936460`)+ 新增 `tests/` 条目 + `conf/` 注释更新为"非敏感入库"口径;(b) §2 表删除,替换为一行"职责已并入 §1"指引;(c) §3 Mermaid 删除,保留标题 + 一行"待基础模块实装后重绘"说明。保留 §2/§3 heading 保证节号稳定,外部引用(kb/90 §4.3/§5/§6.4/§9、kb/30 §8/§9.6)零更新 | — | | 2026-04-20 | **恢复 `dw_base/__init__.py:16` 的 `HADOOP_CONF_DIR` export(反转 2026-04-18 第四轮决策的一小部分)**:`spark-sql-starter.py -f workspace/20260420/select_dim_calendar_dw2.sql` 在新 CDH 环境报 `When running with master 'yarn' either HADOOP_CONF_DIR or YARN_CONF_DIR must be set` —— Spark on YARN 启动时 `SparkSubmitArguments.validateSubmitArguments` 强校验该 env;bigdata 账号 shell 里未 export。2026-04-18 第四轮决策(`kb/92:158`)把这行当"死代码"删,判断只基于 DataX JVM(DataX 确实不靠它,HA 走 ini `[hadoop_config]` 注入),忽略了同一 `dw_base/__init__.py` 也是 Spark 入口的 bootstrap。本次恢复:`os.environ['HADOOP_CONF_DIR'] = '/etc/hadoop/conf'` 加回,注释更新为"Spark 需要;DataX 不读 classpath"。同步更新 `kb/90 §2.8` 末尾"死代码"段(重写为"仅对 Spark 生效")+ §2.8 代码改造清单附表对应行(从"删除"改为"保留",给出双路径理由)。DataX 侧结论(ini `[hadoop_config]` 注入 HA、`HADOOP_CONF_DIR` 对 DataX JVM 无效)不受影响 | — | | 2026-04-20 | **补 `SPARK_CONF_DIR` env 兜底 + 回退 `HIVE_CONF_DIR` 伪修复 + 删 HDP 残留注释**:昨日恢复 `HIVE_CONF_DIR` 后冒烟测试仍报 `Table or view not found`,说明 HIVE_CONF_DIR 不是真正触发点。对比老项目 `tendata-warehouse-release/tendata/__init__.py` 发现老代码同样把 `HIVE_CONF_DIR` 注释掉(反证此前上一条 changelog 的"伪修复"),且同样没有 `SPARK_CONF_DIR`。真正差异在环境:新 CDH 用 pip 安装的 pyspark 其 `SPARK_CONF_DIR` 默认指向自身空 `conf/`,不会加载集群 `/etc/spark/conf/hive-site.xml`,`.enableHiveSupport()` 静默回落 in-memory metastore。整改:(a) `dw_base/__init__.py` `os.environ.setdefault('SPARK_CONF_DIR', '/etc/spark/conf')`(用 setdefault 允许 shell 侧 export 覆盖),同时把 `HIVE_CONF_DIR` 重新注释掉,并删除历史遗留行 `# os.environ['SPARK_HOME'] = '/usr/hdp/3.1.5.0-152/spark2'`(过期 HDP 路径 + 本就是注释,无保留价值);多行 comment 合并为单行;(b) `kb/01-运行环境.md` 新增 §4 Spark 运行时环境变量(HADOOP_CONF_DIR / SPARK_CONF_DIR 表);(c) `kb/90-重构路线.md §2.8` 代码改造清单附表行回退 —— 从"保留 HADOOP_CONF_DIR + HIVE_CONF_DIR"改为"保留 HADOOP_CONF_DIR + 新增 SPARK_CONF_DIR.setdefault",附详情指向 `01 §4`;(d) `kb/90 §7.1` findspark 加 TODO:CDH + pip pyspark 环境下其功能可由 `SPARK_CONF_DIR + setdefault` 覆盖,evaluate 移除。教训:上一条 changelog(HIVE_CONF_DIR 恢复)是在没对比老项目的情况下拍板的,属于典型的"邻域扫但没找对根因";act-not-flatter memory 同步更新边界判定(同根因 yes/no) | — | | 2026-04-20 | **raw 层外部表规范 + "每日调度" 措辞收敛**:(a) `kb/20-数仓分层与建模.md §8.1` 新增 bullet:raw 层建表一律 `CREATE EXTERNAL TABLE`,DROP TABLE 只删元数据、HDFS 数据保留,raw 作为链路兜底层误删可恢复;(b) `kb/00-项目架构.md` 三处"每日调度"收敛为"调度执行":§9 目录说明 `jobs/` 用途行(L617)、§9 目录说明 jobs SQL 路径行(L623)、§9.2 标题(L703)—— 因为调度周期不一定是日(小时/周/月/ad-hoc 均可),"每日"措辞在通用性描述里不准确;同文档 L909 `ads_trd_gmv_d.sql` 注释 + `kb/23-标签体系.md` 两处 `tdm_*_ful_d` 场景描述保留"每日计算"(`_d` 后缀表明具体表就是日粒度)。**遗留冲突待拍**:`kb/00-项目架构.md §9.2` L829 的 raw 层 CTAS 样例(`CREATE TABLE raw.raw_trd_legacy_order_his_o ... AS SELECT`)是 managed table,与 §8.1 新规范的"raw 一律 EXTERNAL"冲突——`CREATE EXTERNAL TABLE ... AS SELECT` 在 Spark / Hive 语法上不支持。候选:① 样例拆两步(先 `CREATE EXTERNAL TABLE` + `LOCATION` 再 `INSERT OVERWRITE`)② §8.1 加 "CTAS 例外" 条款 ③ 删掉 §9.2 CTAS 路径只留 INSERT OVERWRITE 路径。待用户拍板 | — | | 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 打在 feature 分支 commit 上,违反后续 kb/30 §3.4.6.3 "Tag 打在 release → master 合并提交" 规则,已于 2026-04-21 删除(本地 + 远程) | — | | 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) 注册准入规则(于 2026-04-21 评估取消,详见当日 changelog)。本条是 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 状态改"推进中 / 部分提前完成" | — | | 2026-04-21 | **kb/30 §4.6 整合 Git 协作规范**:`kb/inbox/git规范.md` 草稿 6 章整合进 kb/30 §4.6(§4.5 与 §5 之间),节号重排为 §4.6.1~§4.6.6 对齐现有多级风格;草稿 §六 "命名规范" 改名为 "分支 / Tag 命名" 避开与 `kb/21-命名规范.md` 主文档重名;补分支/hotfix/tag 命名示例到各 3 个(个人分支 3 个与 §4.6.1.2 ASCII 图的李四/王五/张三对齐) | — | | 2026-04-21 | **kb/30 大梳理:删 §1 通用开发流程 / §4.5 占位模块规范,Commit 信息归入 Git 协作节,整体节号重排**:(a) 删 §1 通用开发流程(与项目无针对性);(b) 删 §4.5 占位模块规范(该规范仅用于架构规划专用、非普通开发内容,迁至项目根 `CLAUDE.md`);(c) 原 §4.4 Commit 信息(Conventional Commits)并入 Git 协作规范作为 §3.4.7;(d) 节号重排 §2→§1 TPAD / §3→§2 数仓流程 / §4→§3 代码开发(§4.1-§4.3→§3.1-§3.3、§4.6→§3.4,其 §4.6.1-6 → §3.4.1-6)/ §5→§4 / §6→§5 / §7→§6;(e) 联动 `kb/README.md` §30 条目描述 + `kb/90-重构路线.md §2.11` 占位模块标准引用从 `kb/30 §4.5` 改指 `CLAUDE.md` | — | | 2026-04-21 | **datasource 目录结构收敛到 kb/00 §1 一处**:老 kb 内 datasource 目录树在 kb/00 §1(扁平老结构)、kb/00 §6.4(带 env 层目标态)、kb/02 §4(带 env 层)三处平行画,一改就易漏。kb/00 §1 子树补齐 env 层 + 实例举例(`postgresql/{prod,test,dev}/hobby.ini`)作为唯一真源;kb/00 §6.4 删内嵌目录示例块 + 删"当前状态:尚未落地"段(按"不标现状/目标态,以目标态为准"约定);kb/02 §4 删重复目录树,改跨文档引 `kb/00 §1` | — | | 2026-04-21 | **kb/31-UDF手册.md 骨架建立**(聚簇 B / §2.12(b) 启动):新建独立文档作为 `dw_base/udf/common/` 通用 UDF 自查表的稳定锚点;仅骨架(概述 + 准入规则占位 + 空表头),分类不预划由 Codex 按 `spark_common_udf.py` 实际划分登记;`kb/README.md` §3x 加入口。配套修正 `kb/90 §2.12`:(a) 文件名 `kb/31-UDF 手册.md` → `kb/31-UDF手册.md`(无空格);(b) 准入规则落点候选从 `kb/30 或 CLAUDE.md` 改为 `kb/30 或 kb/31 内部`(规范类条款不进 CLAUDE.md)。首版全量登记 + 硬约束正文待 Codex UDF 注释统一化落地后另起 changelog | — | | 2026-04-21 | **kb/31 表头定稿 + 合并 common/business + 取消 UDF 准入规则**:(a) kb/31 表头改为 `函数编号 \| 函数名 \| 分类 \| 入参 \| 返回 \| 描述 \| 示例 \| 补注释状态`,函数编号由 Codex 在 `spark_common_udf.py` 以顺序注释注入(不依赖行号,避免改动污染其他函数);(b) business UDF 纳入 kb/31 第二段(原"business 在子目录 README 维护,不走此表"规划取消);(c) UDF 准入规则取消(无执行主体,规则无意义)。联动 `kb/90 §2.12`:删"业务 UDF 进 common 准入标准缺失"问题项、同步新表头、删"business 不走此表"表述、删"注册准入规则"整点、关系节去"准入规则"字样;`kb/92` L181 历史条目 (c) 改软标注指向本条。另 `kb/30 §5` 删"jobs/ 里没有 CREATE TABLE 可改"冗余句与"归档策略"防御性条款 | — | | 2026-04-21 | **聚簇 A.1 环境变量外配到 `conf/env.sh`(bash + Python 单源)**:新建 `conf/env.sh` 统一 5 vars —— `RELEASE_USER="bigdata"`(替换 `"alvis"`)/ `RELEASE_ROOT_DIR="/home/bigdata/release"` / `PYTHON3_PATH` / `DATAX_HOME`(保持原有 `${DATAX_HOME:-/opt/datax}` 条件赋值)/ `LOG_ROOT_DIR="${HOME}/log"`。Python 侧新建 `dw_base/utils/env_loader.bootstrap_env()` 通过 `subprocess.run(['bash', '-c', '. env.sh && env -0'])` 让 bash 自己解析 env.sh,再 `os.environ.setdefault(...)` 注入(避免重写一套解析器导致 bash/py 双解析漂移;setdefault 允许 shell 侧已 export 的值优先)。`dw_base/__init__.py` 顶部加 `bootstrap_env()` 调用 + 读 `os.environ['RELEASE_USER' / 'RELEASE_ROOT_DIR' / 'LOG_ROOT_DIR']`,删 `/opt/data/log` vs `${HOME}/data/log` 的 whoami 分流(对齐 kb/90 §7.2.1 的单值决策)。`bin/common/init.sh` 顶部加 `. conf/env.sh` + 删对应硬编码变量 + 删 whoami 分流 + 删 `export LOG_ROOT_DIR / PYTHON3_PATH / RELEASE_ROOT_DIR / DATAX_HOME`(env.sh 已 export);`bin/publish.sh` 加 `. conf/env.sh` + 删 L15 `RELEASE_ROOT_DIR="/home/alvis/release"` 硬编码。联动 kb/90 §2.1 删 5 行已完成硬编码(DATAX_HOME / PYTHON3_PATH / RELEASE_USER / RELEASE_ROOT_DIR / LOG_ROOT_DIR)+ §2.2 config 结构删 `env.py`(单源不做双份)+ §7.2.1 大幅瘦身(去掉现状/为什么/代码改动三段,仅保留日志路径 `{module}/{dt}/{file}.log` 约定)+ 本文阶段 2 checklist 4 处 [x] | — | | 2026-04-21 | **`dw_base/common/__init__.py` 撤销删除(反转先前空壳判断)**:早先 `ls` 只看到 `__init__.py` 0 字节就判定"空壳"、按 CLAUDE.md "空 __init__.py + 无 README 下次清理直接删"规则挂入 A.1 执行清单第 9 项。`grep` 查证发现 `dw_base/common/` 下 `config_constants.py` / `container.py` / `template_constants.py` 分别被 `bin/spark-sql-starter.py` / `bin/datax-job-config-generator.py` / `dw_base/utils/log_utils.py` / `dw_base/datax/plugins/reader/mysql_reader.py` import,`__init__.py` 0 字节是 Python 正常的 package 标识(`dw_base/__init__.py` 含 bootstrap 逻辑,保持 regular package 一致性优于切 namespace package)。结论:不删。教训:空壳判断必须 `ls` 整个目录 + grep 所有子模块引用,不能只看 `__init__.py` 字节数 | — | | 2026-04-21 | **删除 `bin/flume-control.sh`(194 行;事实不可用)**:脚本顶部 shebang 损坏(`ho#!/bin/bash`)+ 依赖已在 2026-04-20 删除的 `bin/wechat-work-alert.sh` + L64 `conf/flume/*.properties` 与 L162 `conf/flume/config/*.properties` 路径自相矛盾,实际已跑不起来。决定:整文件删 + Kafka→HDFS 接入通道的设计理念归档到 kb/90 §5.2 历史档案,按需重建时以该档为参考(不沿用老 `SKB_LITTLE_CUTE` / 手机号硬编码告警,重建时按 `conf/alerter.ini` 外配走) | — | | 2026-04-21 | **SQL 风格基线尝试后撤回**:本轮前半段把 `sql_style.xml` 从 `conf/` 挪到项目根,并在 kb/30 §3.2.1 / §3.2.2 立了强基线(关键字/类型 UPPER、SELECT/FROM/ORDER/GROUP 一项一行、JOIN/ON 缩进、CASE/CTE/UNION/OVER/INSERT OVERWRITE/分号九条换行与缩进样例)。实测 IDEA formatter 支持面不足(KEYWORD_CASE 仅作用于新输入不改存量、SELECT 前置逗号长期不支持、ORDER/GROUP 一项一行的 option 名 JetBrains 非公开、UNION 前后空行 formatter 不管、CASE THEN/END 独立行无选项控制),强约束无法靠 formatter 落地。本轮后半段全部回退:删 `sql_style.xml`、kb/30 §3.2.1 / §3.2.2 整节删除、原 §3.2.3 不对齐 AS 重编号为 §3.2.1。团队 SQL 格式化改由各自 IDEA 默认 + 项目 SQL 方言统一设为 Spark 承担,冲突走 review | — | | 2026-04-21 | **下线"阶段 3:业务 SQL 从零开发" + 取消 UDF 注释补齐 + kb/31 首批登记 13 个通用 UDF**:(a) 业务 SQL 从零开发属于新开发、不属于重构 scope:kb/92 总览表删阶段 3 行、阶段 3 整节删除、阶段 5 前置条件从"阶段 3 稳定"改为"新业务 SQL 稳定";kb/90 §〇 聚簇表删 E 行、DAG 图删 E 节点、关键依赖边 "A+B+C→E" 与 "E→F" 合并为 "新业务 SQL 生产稳定→F"、§八 聚簇 E 整节删除、当前推进建议"等待前置"里 E 行改为 F 行。(b) 通用 UDF 注释已由开发者手动补完(`spark_common_udf.py` 13 个 `@udf` 函数均带 `UDF-XX` 顺序编号 + 分节注释),kb/90 §2.12 删"5 段模板 + 5 批 commit"规划、"40 函数"更正为"13 个注册 UDF"、标题从"注释完整化 + 自查表"改为"自查表"。(c) `kb/31-UDF手册.md` §1 通用 UDF 表从空壳填入 13 行(UDF-01/02/21/22/23/31/32/33/41/42/51/52/53),分类按代码中分节注释(JSON / ARRAY / STRING / NUMERIC-DATE-HASH / CROSS-TYPE),函数编号按代码中 `UDF-XX` 注释;§2 业务 UDF 保持占位;非 `@udf` 普通 `def`(18 个辅助 / 工具函数)不登记 | — | | 2026-04-21 | **聚簇 B.1 `__init__.py` 瘦身(修剪式,不拆 `core/`)**:`dw_base/__init__.py` 从 127 行 → 83 行。三处删除:(a) `import findspark` + `findspark.init()` —— 查证 3 条事实后安全删:findspark 全仓仅此 2 处引用;入口全走 `python3 xxx.py`(非 `spark-submit`),`SPARK_HOME` 从未被代码注入,findspark 在 CDH 节点上 `which spark-submit → readlink -f` 反推出 parcel `$SPARK_HOME` 把 `$SPARK_HOME/python` 前插进 sys.path,但 pip pyspark 2.4.0 和 parcel pyspark 2.4.0 同版本,业务表现零差异(见里程碑 `datax+spark-smoke-2026-04-20` 冒烟链路,HMS 真正入口是 `SPARK_CONF_DIR=/etc/spark/conf/hive-site.xml`,与 findspark 无关);(b) 删 21 个外部零引用的颜色常量 —— `CHG_BOLD` / `NORM_BLU` / `NORM_WHT` / 7×`BOLD_*` / 7×`BGRD_*`(if/else 两分支同步删),保留实际被引用的 6 个(`DO_RESET` / `NORM_RED` / `NORM_GRN` / `NORM_YEL` / `NORM_MGT` / `NORM_CYN`);(c) 删 `IS_RUN_BY_NORMAL_USER` 状态变量(两处赋值外部无引用,仅内部 `elif` 分支走到时为 `True`,无消费者)。**不拆 `core/*` 的理由**:findspark 去掉后"懒加载"诉求大半消失,拆分需改 11 处调用点 import,ROI 低;py/sh 颜色双份是运行时分家的必然(跨 runtime 单源化要加 subprocess 解析,得不偿失),真冗余只是 py 侧定义超过实际被用的部分。联动:`requirements.txt:3` 删 `findspark==2.0.1`;`tests/README.md:26` findspark 段改写为 HMS 入口说明;`kb/00 §1` `__init__.py` 行注释去 findspark;`kb/90 §三` 改写为"已完成 · 修剪式"并附未拆 `core/` 的理由;`kb/90 §7.1` KEEP 行去 findspark + 末尾 TODO 行改写为"已删除" | — | | 2026-04-22 | **勘误:移除 "SQL SET 资源类参数不生效" 的错误表述**:A.4 落地时误把 "`SparkSession.conf.set()` 对已启动 session 不改变资源" 这条 Spark 固有行为类推到"SQL 里的 SET"。实际 `spark_sql.py:query()` L433-440 对 SQL 内 `SET spark.*` 做预扫描,在 session 启动前塞进 `_final_spark_config`,`__init_spark_session` 通过 `builder.config()` 写入后才 `getOrCreate()`——**资源类 SET 同样生效**。清理 4 处错误:`dw_base/spark/spark_sql.py:155` 注释、`conf/spark-tuning.conf` L6-7 注意段、`kb/90 §2.3` "落地坑"段 + L2 行括号、本文 L74 基于错误前提的验证项 | — | | 2026-04-22 | **仓库改名 `tendata-warehouse-release` → `poyee-data-warehouse` 收尾**:项目根目录由用户手动改名完成;代码侧 `dw_base/utils/file_utils.py:9` + `dw_base/utils/hdfs_merge_small_file.py:7` 两处 `re.sub(r"tendata-warehouse.*", ...)` 字面量同步更新。`.idea/*.iml` / `modules.xml` / `workspace.xml` 因 `.idea` + `*.iml` 在 `.gitignore`,属本地 IDE 状态,不入库亦不影响运行(老 `tendata-warehouse-release.iml` + modules.xml / workspace.xml 里的 module name 残留不处理)。联动 kb/90 §1.1 L88 表格行打勾 + §2.3 末尾"与仓库改名的联动"段压缩为一行记录 | — | | 2026-04-21 | **聚簇 A.4 Spark 参数外配 + `spark_sql.py` 三级覆盖**:按业务调整频率拆两文件入库 —— `conf/spark-defaults.conf`(12 条底层行为/开关类,初始化后少改:`spark.sql.adaptive/broadcastTimeout/codegen/arrow*/files/statistics.*` + `spark.dynamicAllocation.enabled` + `spark.files.ignoreCorruptFiles` + `spark.debug.maxToStringFields` + `spark.port.maxRetries` + `hive.exec.orc.default.block.size`)+ `conf/spark-tuning.conf`(10 条资源/并行度/队列,业务早期常改:`spark.{driver,executor}.{memory,cores}` + `spark.executor.instances` + `spark.executor.memoryOverhead` + `spark.driver.maxResultSize` + `spark.default.parallelism` + `spark.sql.shuffle.partitions` + `spark.yarn.queue`)。`dw_base/spark/spark_sql.py` 改造:(a) 模块级新增 `_load_spark_conf_file(path)`,读 Spark 原生 `key value` 格式,支持 `#` 注释与空行,文件缺失返回 `{}` 容错单测;(b) `__init__` 10 个 tuning 相关构造参数默认值 `'2g' / 200 / ...` → `Optional[...] = None` sentinel,不破坏既有调用点显式传参;(c) `__init_spark_session` 原 22 条硬编码 `.config(...)` 链替换为三段:L1 先 `spark-defaults.conf` 后 `spark-tuning.conf`(相同 key tuning 覆盖 defaults)→ L2 `self._final_spark_config`(SQL 内 SET)→ L3 构造参数非 None 项 + `extra_spark_config`(L3 内 extra 覆盖 named),保持原"extra > SQL SET > named" 的向后兼容;日志分层打 `L1/L2/L3` 前缀便于排查。联动:`kb/90 §2.2` conf 结构加 `spark-tuning.conf` + `§2.3` 改写为两文件模型(去单文件草案)+ 删"坑 2"(B1 → A2 依赖边)+ 聚簇 L59 依赖边删 | — |