在准备环境前提交次全部更改。
This commit is contained in:
374
.kiro/specs/etl-pipeline-debug/design.md
Normal file
374
.kiro/specs/etl-pipeline-debug/design.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# 设计文档:ETL 管道全流程调试与 Debug
|
||||
|
||||
## 概述
|
||||
|
||||
本设计覆盖 `apps/etl/pipelines/feiqiu/` 下 ETL 管道的全流程调试,采用五阶段策略:
|
||||
|
||||
1. **分层单元调试**:逐层(ODS → DWD → DWS → INDEX)使用 FakeDB/FakeAPI 和真实数据库验证任务逻辑
|
||||
2. **全量数据刷新**:执行 2026-01-01 至 2026-02-16 的 `api_full` Flow,内嵌性能计时,边执行边 Debug,发现问题即修复并重试
|
||||
3. **黑盒数据校验**:从 API 源数据出发,逐层对比各 Schema 各表的记录数、金额汇总、可疑值检测和抽样逐字段比对
|
||||
4. **架构优化分析**:分析 ETL 整体结构,在保证稳定和正确的基础上提出精简架构的建议,生成架构报告
|
||||
5. **报告生成**:基于全量刷新阶段采集的计时数据生成性能分析报告,汇总所有调试结果生成 Debug 报告
|
||||
|
||||
性能计时从全量刷新阶段开始就嵌入采集(每任务/每层/API 调用耗时),报告阶段仅做分析和输出,不再单独重跑流程采集数据。
|
||||
|
||||
调试使用 `.env` 中配置的真实 API 和数据库连接(test_etl_feiqiu),所有发现的问题和修复记录在结构化 Debug 报告中。性能计时在全量刷新阶段实时采集,报告阶段读取中间数据做分析输出。
|
||||
|
||||
## 架构
|
||||
|
||||
### 调试流程架构
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[阶段1: 分层单元调试] --> B[阶段2: 全量数据刷新<br/>内嵌性能计时]
|
||||
B --> C[阶段3: 黑盒数据校验]
|
||||
C --> D[阶段4: 架构优化分析]
|
||||
D --> E[阶段5: 报告生成<br/>含性能分析]
|
||||
|
||||
subgraph 阶段1 [分层单元调试]
|
||||
A1[ODS 层: API抓取 → 表写入] --> A2[DWD 层: ODS → DWD 清洗装载]
|
||||
A2 --> A3[DWS 层: DWD → DWS 汇总]
|
||||
A3 --> A4[INDEX 层: 指数算法计算]
|
||||
A4 --> A5[编排层: PipelineRunner + TaskExecutor]
|
||||
A5 --> A6[CLI 入口: 参数解析 + 模式切换]
|
||||
A6 --> A7[配置体系: AppConfig 加载合并]
|
||||
end
|
||||
|
||||
subgraph 阶段2 [全量数据刷新 - 内嵌计时 + 边执行边Debug]
|
||||
B1[api_full Flow 执行<br/>每任务/每层计时] --> B2{发现Bug?}
|
||||
B2 -->|是| B3[修复Bug]
|
||||
B3 --> B4[重试失败的层/任务]
|
||||
B4 --> B2
|
||||
B2 -->|否| B5[increment_verify 校验]
|
||||
B5 --> B6[自动补齐缺失数据]
|
||||
B6 --> B7[输出计时 JSON 中间文件]
|
||||
end
|
||||
|
||||
subgraph 阶段3 [黑盒数据校验]
|
||||
C1[API → ODS 记录数对比] --> C2[ODS → DWD 记录数+金额对比]
|
||||
C2 --> C3[DWD → DWS 聚合一致性]
|
||||
C3 --> C4[可疑值检测: 边缘值/空值/重复]
|
||||
C4 --> C5[抽样100条逐字段比对]
|
||||
end
|
||||
|
||||
subgraph 阶段4 [架构优化分析]
|
||||
D1[代码结构分析] --> D2[冗余识别]
|
||||
D2 --> D3[精简建议]
|
||||
end
|
||||
|
||||
subgraph 阶段5 [报告生成 - 含性能分析]
|
||||
E1[读取计时 JSON] --> E2[瓶颈识别 + 优化建议]
|
||||
E2 --> E3[汇总 Debug 报告]
|
||||
end
|
||||
```
|
||||
|
||||
### 现有系统架构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CLI[cli/main.py] --> PR[PipelineRunner]
|
||||
CLI --> TE[TaskExecutor]
|
||||
PR --> TE
|
||||
TE --> TR[TaskRegistry<br/>52个任务]
|
||||
TE --> CM[CursorManager]
|
||||
TE --> RT[RunTracker]
|
||||
|
||||
TR --> ODS[ODS 任务<br/>BaseOdsTask × 23]
|
||||
TR --> DWD[DWD 任务<br/>DwdLoadTask]
|
||||
TR --> DWS[DWS 任务<br/>BaseDwsTask × 15]
|
||||
TR --> IDX[INDEX 任务 × 4]
|
||||
TR --> UTL[工具类任务 × 7]
|
||||
|
||||
ODS --> API[APIClient<br/>上游 SaaS]
|
||||
ODS --> DB[(PostgreSQL<br/>ods.*)]
|
||||
DWD --> DB
|
||||
DWS --> DB
|
||||
IDX --> DB
|
||||
|
||||
QC[IntegrityChecker] --> API
|
||||
QC --> DB
|
||||
```
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 调试脚本组件
|
||||
|
||||
调试通过一组 Python 脚本实现,放置在 `apps/etl/pipelines/feiqiu/scripts/debug/` 目录下:
|
||||
|
||||
| 脚本 | 职责 | 对应需求 |
|
||||
|------|------|----------|
|
||||
| `debug_ods.py` | ODS 层逐任务调试:连接真实 API 和 DB,执行单个 ODS 任务并验证写入结果 | 需求 1, 9 |
|
||||
| `debug_dwd.py` | DWD 层调试:执行 DWD_LOAD_FROM_ODS 并验证 TABLE_MAP 中每对映射的记录数 | 需求 2, 9 |
|
||||
| `debug_dws.py` | DWS 层调试:逐个执行 DWS 任务并验证汇总结果 | 需求 3, 9 |
|
||||
| `debug_index.py` | INDEX 层调试:执行 4 个指数任务并验证计算结果 | 需求 4, 9 |
|
||||
| `debug_orchestration.py` | 编排层调试:验证 PipelineRunner 和 TaskExecutor 的流程控制 | 需求 5 |
|
||||
| `run_full_refresh.py` | 全量刷新:执行 2026-01-01 ~ 2026-02-16 的 api_full,内嵌性能计时,边执行边 Debug,发现问题即修复重试,输出计时 JSON | 需求 10, 13 |
|
||||
| `debug_blackbox.py` | 黑盒校验:从 API 源数据逐层对比各表数据完整性 + 可疑值检测 + 抽样逐字段比对 | 需求 12 |
|
||||
| `analyze_performance.py` | 性能分析:读取全量刷新采集的计时 JSON,统计耗时,识别瓶颈,生成性能优化报告 | 需求 13 |
|
||||
| `analyze_architecture.py` | 架构分析:分析代码结构,识别冗余和可精简点,生成架构优化报告 | 需求 14 |
|
||||
| `generate_report.py` | 报告生成:汇总所有调试结果生成 Markdown 报告 | 需求 11 |
|
||||
|
||||
### 关键接口
|
||||
|
||||
```python
|
||||
# 调试脚本的统一入口接口
|
||||
class DebugResult:
|
||||
"""单个调试步骤的结果"""
|
||||
layer: str # "ODS" / "DWD" / "DWS" / "INDEX" / "ORCHESTRATION"
|
||||
task_code: str # 任务代码
|
||||
status: str # "PASS" / "FAIL" / "WARN" / "ERROR"
|
||||
message: str # 结果描述
|
||||
details: dict # 详细信息(记录数、错误堆栈等)
|
||||
fix_applied: str | None # 已应用的修复措施
|
||||
|
||||
class BlackboxCheckResult:
|
||||
"""黑盒校验单表结果"""
|
||||
layer: str # "API_ODS" / "ODS_DWD" / "DWD_DWS"
|
||||
source_table: str # 源表名
|
||||
target_table: str # 目标表名
|
||||
source_count: int # 源记录数
|
||||
target_count: int # 目标记录数
|
||||
count_diff: int # 差异数
|
||||
amount_diffs: list # 金额差异列表
|
||||
missing_keys: list # 缺失记录主键
|
||||
mismatch_count: int # 内容不一致数
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### Debug 报告结构
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class DebugReport:
|
||||
"""Debug 报告数据模型"""
|
||||
title: str # 报告标题
|
||||
generated_at: datetime # 生成时间
|
||||
environment: dict # 环境信息(DB DSN, API base, store_id)
|
||||
|
||||
# 分层调试结果
|
||||
ods_results: list[DebugResult] # ODS 层调试结果
|
||||
dwd_results: list[DebugResult] # DWD 层调试结果
|
||||
dws_results: list[DebugResult] # DWS 层调试结果
|
||||
index_results: list[DebugResult] # INDEX 层调试结果
|
||||
orchestration_results: list[DebugResult] # 编排层调试结果
|
||||
|
||||
# 黑盒校验结果
|
||||
blackbox_results: list[BlackboxCheckResult]
|
||||
|
||||
# 全量刷新结果
|
||||
full_refresh: dict # {status, layers, counts, duration}
|
||||
verification: dict # {status, total_tables, consistent, backfilled}
|
||||
|
||||
# 性能分析
|
||||
performance_report: dict # {layer_timings, bottlenecks, recommendations}
|
||||
|
||||
# 架构优化
|
||||
architecture_report: dict # {structure_analysis, redundancies, simplification_suggestions}
|
||||
|
||||
# 汇总
|
||||
total_issues: int # 发现的问题总数
|
||||
fixed_issues: int # 已修复的问题数
|
||||
remaining_issues: int # 遗留问题数
|
||||
```
|
||||
|
||||
### 黑盒校验数据流
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
API[上游 API] -->|iter_paginated| COUNT_API[API 记录数]
|
||||
API -->|抽样100条| SAMPLE[抽样记录集]
|
||||
ODS[(ods.* 表)] -->|SELECT COUNT| COUNT_ODS[ODS 记录数]
|
||||
ODS -->|可疑值检测| SUSPECT[可疑值: 边缘值/空值/重复]
|
||||
DWD[(dwd.* 表)] -->|SELECT COUNT| COUNT_DWD[DWD 记录数]
|
||||
DWS[(dws.* 表)] -->|SELECT SUM| SUM_DWS[DWS 汇总值]
|
||||
|
||||
COUNT_API --> CMP1{API vs ODS<br/>记录数对比}
|
||||
COUNT_ODS --> CMP1
|
||||
COUNT_ODS --> CMP2{ODS vs DWD<br/>记录数+金额对比}
|
||||
COUNT_DWD --> CMP2
|
||||
COUNT_DWD --> CMP3{DWD vs DWS<br/>聚合一致性}
|
||||
SUM_DWS --> CMP3
|
||||
|
||||
SAMPLE --> CMP4{抽样逐字段比对<br/>API vs ODS 100条}
|
||||
ODS --> CMP4
|
||||
|
||||
SUSPECT --> CMP5{可疑值分析<br/>追溯上游原因}
|
||||
|
||||
CMP1 --> RPT[校验报告]
|
||||
CMP2 --> RPT
|
||||
CMP3 --> RPT
|
||||
CMP4 --> RPT
|
||||
CMP5 --> RPT
|
||||
```
|
||||
|
||||
|
||||
## 正确性属性
|
||||
|
||||
*属性是在系统所有有效执行中都应成立的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。*
|
||||
|
||||
基于前置分析,以下属性覆盖了可自动化测试的验收标准。本项目的许多需求(特别是需求 8-12 的黑盒校验和全量刷新)依赖真实数据库和 API 连接,属于集成测试范畴,不适合属性测试。属性测试聚焦于可用 FakeDB/FakeAPI 验证的核心逻辑。
|
||||
|
||||
### Property 1: ODS 任务提取记录数一致性
|
||||
|
||||
*对任意* ODS 任务和 FakeAPI 提供的任意非空记录列表,任务执行后 FakeDB 接收到的记录数应等于 API 提供的记录数减去被跳过的记录数(缺失主键 + 重复哈希)。
|
||||
|
||||
**Validates: Requirements 1.1, 1.2**
|
||||
|
||||
### Property 2: ODS 冲突处理策略正确性
|
||||
|
||||
*对任意* `ods_conflict_mode` 配置值(nothing/backfill/update),BaseOdsTask 生成的 SQL 语句应包含对应的冲突处理子句:nothing → `DO NOTHING`,backfill → `COALESCE`,update → `IS DISTINCT FROM`。
|
||||
|
||||
**Validates: Requirements 1.3**
|
||||
|
||||
### Property 3: ODS 跳过缺失主键记录
|
||||
|
||||
*对任意*包含缺失主键字段的记录集合,BaseOdsTask 的 skipped 计数应等于缺失主键的记录数,且这些记录不应出现在写入的行中。
|
||||
|
||||
**Validates: Requirements 1.4**
|
||||
|
||||
### Property 4: ODS content_hash 去重
|
||||
|
||||
*对任意*两条内容相同的 ODS 记录(仅 fetched_at 不同),计算出的 content_hash 应相同;当已有记录的 content_hash 与新记录相同时,新记录应被跳过。
|
||||
|
||||
**Validates: Requirements 1.5**
|
||||
|
||||
### Property 5: ODS 快照删除标记
|
||||
|
||||
*对任意*启用 `snapshot_missing_delete` 的 ODS 任务,当 API 返回的记录集是已有记录集的真子集时,差集中的记录应被标记为 `is_delete=1`。
|
||||
|
||||
**Validates: Requirements 1.7**
|
||||
|
||||
### Property 6: DWD FACT_MAPPINGS 列映射完整性
|
||||
|
||||
*对任意* FACT_MAPPINGS 中的映射条目 `(dwd_col, ods_expr, cast_type)`,当 `ods_expr` 是简单列名时,该列应存在于对应的 ODS 源表中。
|
||||
|
||||
**Validates: Requirements 2.4**
|
||||
|
||||
### Property 7: DWD only_tables 过滤
|
||||
|
||||
*对任意*非空的 `dwd.only_tables` 配置列表,DwdLoadTask 处理的表集合应是配置列表与 TABLE_MAP 键集合的交集。
|
||||
|
||||
**Validates: Requirements 2.6**
|
||||
|
||||
### Property 8: DWS 分段累加一致性
|
||||
|
||||
*对任意*时间窗口和分段配置,BaseTask 的 `_accumulate_counts` 方法对各分段计数的累加结果应等于各分段计数的逐键求和。
|
||||
|
||||
**Validates: Requirements 3.3**
|
||||
|
||||
### Property 9: PipelineRunner Flow 层解析
|
||||
|
||||
*对任意*有效的 Flow 名称(PIPELINE_LAYERS 的键),PipelineRunner 解析出的层列表应与 PIPELINE_LAYERS 中定义的值完全一致。
|
||||
|
||||
**Validates: Requirements 5.1, 10.2**
|
||||
|
||||
### Property 10: PipelineRunner 无效 Flow 拒绝
|
||||
|
||||
*对任意*不在 PIPELINE_LAYERS 键集合中的字符串,PipelineRunner.run() 应抛出 ValueError。
|
||||
|
||||
**Validates: Requirements 5.2**
|
||||
|
||||
### Property 11: TaskExecutor 工具类任务跳过游标
|
||||
|
||||
*对任意*被 TaskRegistry 标记为 `requires_db_config=False` 的任务代码,TaskExecutor 应通过 `_run_utility_task` 路径执行,不调用 CursorManager 和 RunTracker。
|
||||
|
||||
**Validates: Requirements 5.5**
|
||||
|
||||
### Property 12: CLI data_source 解析
|
||||
|
||||
*对任意* `--data-source` 参数值(online/offline/hybrid)和 `--pipeline-flow` 参数值(FULL/FETCH_ONLY/INGEST_ONLY),`resolve_data_source` 应返回正确的映射值,且 `--data-source` 优先于 `--pipeline-flow`。
|
||||
|
||||
**Validates: Requirements 6.3, 6.4**
|
||||
|
||||
### Property 13: AppConfig 优先级合并
|
||||
|
||||
*对任意*嵌套字典 DEFAULTS 和 CLI 覆盖,`_deep_merge` 后 CLI 中的键值应覆盖 DEFAULTS 中的同名键值,未被覆盖的键应保持原值。
|
||||
|
||||
**Validates: Requirements 7.1**
|
||||
|
||||
### Property 14: AppConfig store_id 验证
|
||||
|
||||
*对任意*非整数字符串作为 `app.store_id`,AppConfig._normalize 应抛出 SystemExit。
|
||||
|
||||
**Validates: Requirements 7.2**
|
||||
|
||||
### Property 15: AppConfig DSN 组装
|
||||
|
||||
*对任意* host、port、name、user、password 组合(db.dsn 为空时),AppConfig._normalize 应组装出格式为 `postgresql://{user}:{password}@{host}:{port}/{name}` 的 DSN 字符串。
|
||||
|
||||
**Validates: Requirements 7.3**
|
||||
|
||||
### Property 16: AppConfig 点号路径 get
|
||||
|
||||
*对任意*嵌套字典和有效的点号路径,`config.get(path)` 应返回路径对应的值;对无效路径应返回 default 参数值。
|
||||
|
||||
**Validates: Requirements 7.4**
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 数据库连接错误
|
||||
- 调试脚本在启动时验证数据库连接,连接失败时记录错误详情并在报告中标注
|
||||
- 单表处理失败时回滚该表事务,继续处理后续表(DWD 层已实现此逻辑)
|
||||
- 全量刷新过程中连接断开时,利用 `DatabaseConnection.ensure_open()` 尝试重连
|
||||
|
||||
### API 连接错误
|
||||
- APIClient 内置重试机制(`retry_max=3`,指数退避)
|
||||
- API 返回非 0 code 时抛出 ValueError,由上层捕获并记录
|
||||
- Token 过期时在报告中标注,提示用户更新 `.env` 中的 `API_TOKEN`
|
||||
|
||||
### 任务执行错误
|
||||
- ODS 任务:数据库异常时回滚并递增 errors 计数
|
||||
- DWD 任务:单表失败时回滚该表,继续后续表,最终汇总错误
|
||||
- DWS/INDEX 任务:继承 BaseTask 的异常处理,回滚后重新抛出
|
||||
- 工具类任务:异常直接向上传播,不影响游标和运行记录
|
||||
|
||||
### 数据质量错误
|
||||
- 黑盒校验发现的不一致记录在报告中详细列出主键
|
||||
- 金额差异超过阈值时标记为 WARN 级别
|
||||
- 校验后自动补齐通过 `run_backfill` 执行,补齐失败的记录单独记录
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 双轨测试方法
|
||||
|
||||
本项目采用单元测试 + 属性测试的双轨方法:
|
||||
|
||||
- **属性测试**:使用 `hypothesis` 库验证上述 16 个正确性属性,每个属性至少运行 100 次迭代
|
||||
- **单元测试**:使用 `pytest` 验证具体示例、边界情况和错误条件
|
||||
- **集成测试**:使用真实数据库(`TEST_DB_DSN`)验证端到端数据流
|
||||
|
||||
### 属性测试配置
|
||||
|
||||
- 库:`hypothesis`(已在项目依赖中)
|
||||
- 最小迭代次数:100
|
||||
- 每个属性测试必须引用设计文档中的属性编号
|
||||
- 标签格式:`Feature: etl-pipeline-debug, Property {number}: {property_text}`
|
||||
|
||||
### 测试文件组织
|
||||
|
||||
```
|
||||
apps/etl/pipelines/feiqiu/tests/unit/
|
||||
├── test_debug_ods_properties.py # Property 1-5: ODS 层属性测试
|
||||
├── test_debug_dwd_properties.py # Property 6-8: DWD/DWS 层属性测试
|
||||
├── test_debug_orchestration_properties.py # Property 9-12: 编排层属性测试
|
||||
├── test_debug_config_properties.py # Property 13-16: 配置层属性测试
|
||||
└── (现有测试文件保持不变)
|
||||
```
|
||||
|
||||
### 集成测试
|
||||
|
||||
集成测试通过调试脚本实现,连接真实数据库和 API:
|
||||
- `debug_ods.py`:逐个执行 ODS 任务,验证写入结果
|
||||
- `debug_dwd.py`:执行 DWD 装载,验证映射正确性
|
||||
- `debug_blackbox.py`:黑盒校验,逐层对比数据完整性
|
||||
- `run_full_refresh.py`:全量刷新 + 校验
|
||||
|
||||
### 现有测试基础设施
|
||||
|
||||
项目已有完善的测试工具:
|
||||
- `FakeDBOperations`:拦截并记录 SQL 操作,提供 commit/rollback 计数
|
||||
- `FakeAPIClient`:返回预置内存数据,记录调用参数
|
||||
- `OfflineAPIClient`:从归档 JSON 回放数据
|
||||
- `RealDBOperationsAdapter`:连接真实 PostgreSQL 的适配器
|
||||
- `create_test_config()`:构建测试用 AppConfig
|
||||
Reference in New Issue
Block a user