提交 ef5bdd1d authored 作者: 陈泽健's avatar 陈泽健

fix(performance): 启动回填 ResourceClosedError 修复(双 scalar 调用)+ 补提交拆表测试套件

- _migrate_legacy_executions() 中 existing.scalar() 双调用:Result.scalar() 取完首行即关闭 result,二次调用抛 ResourceClosedError——legacy 迁移一旦完成,此后每次启动回填必炸,导致拆表从表存量回填永远不执行;改为单次取值 legacy_count
- 补提交 test_performance_satellite_tables.py 14 用例(ea2a90cb 提交信息已声明但文件漏入库)
- HANDOFF_性能测试.md 记录拆表部署 5.60 闭环:12 从表建成 + 存量回填 101 行 + JSON 'null' 字面量校验口径 + 部署/验证脚本清单
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 f60a2914
......@@ -2,13 +2,69 @@
> **生成时间**: 2026-09-08
> **当前分支**: `platform-auto-test`
> **最近提交**: `b21f2fe9` fix(performance): 全库扫描 order_by 兜底排查——26 处两段式改造防 MySQL 1038(已推送 origin/platform-auto-test)
> **会话窗口**: 性能测试 — 2026-09-08 P3 待办闭环:performance 大 JSON/TEXT 列垂直拆表(7 个从表 + 双写 + 读优先从表 + 启动回填 + 零回归)
> **状态**: ✅ 代码开发与测试完成(3 文件修改 + 1 新增测试,全量 427 passed 零回归),待部署 5.60 与提交推送
> **最近提交**: `ea2a90cb` perf(performance): 大JSON列垂直拆表闭环 MySQL 1038 + 登录汇总透出(已推送 origin/platform-auto-test)
> **会话窗口**: 性能测试 — 2026-09-08 P3 待办闭环:performance 大 JSON/TEXT 列垂直拆表(7 个从表 + 双写 + 读优先从表 + 启动回填 + 零回归)+ **部署 5.60 验证闭环**
> **状态**: ✅ 已提交推送 `ea2a90cb` + 已部署 5.60(12 张从表建成 + 存量回填 101 行 + 修复启动回填 ResourceClosedError,本地全量 437 passed)
---
## ⚡ 最新会话更新(2026-09-08)— P3 待办闭环:performance 大 JSON 列垂直拆表 ✅
## ⚡ 最新会话更新(2026-09-08 续)— 拆表部署 5.60 验证闭环 + 启动回填 ResourceClosedError 修复 ✅
### A. 部署状态核验(拆表代码此前未部署)
- 提交 `ea2a90cb` 推送后**未走部署步骤**,远端 5.60 五个关键文件 md5 全部匹配 `b21f2fe9`(CRLF 变体),MySQL 无任何从表;
- 本次补部署 5 个文件(`database.py` / `models/performance.py` / `services/performance_service.py` / `routers/performance.py` / `schemas/performance.py`),远端 .bak 备份 + 原子替换(.new → mv)+ md5 逐文件复核一致。
### B. 意外发现并修复:启动回填 ResourceClosedError(`_migrate_legacy_executions`)
首次部署重启后从表建成但回填 0 行,启动日志:`存量执行数据回填失败(不影响启动,下次重试): This result object is closed.`
| 项 | 说明 |
|----|------|
| 根因 | `performance_service.py` `_migrate_legacy_executions()``if existing.scalar() and existing.scalar() > 0:`**同一 Result 调用两次 `scalar()`**——SQLAlchemy 中 `scalar()`(内部 `first()`)取完第一行即关闭 result,第二次调用抛 `ResourceClosedError` |
| 为何早不炸 | 5.60 首次迁移前无 legacy 记录 → `scalar()` 返回 0 falsy → `and` 短路不调用第二次;**迁移一旦完成,之后每次启动必炸**,导致后续拆表回填永远不执行 |
| 修复 | 改为 `legacy_count = (...).scalar() or 0; if legacy_count > 0:`(单次取值);全库扫描确认无其他同类双调用 |
| 验证 | 本地最小复现(双 scalar 抛错)+ 修复后 init_db 正常;5.60 重启日志 `存量回填已执行过,跳过` + `大字段从表存量回填: 101 行` ✅ |
### C. 部署验证结果 ✅
| 项 | 结果 |
|----|------|
| 远端 5 文件 md5 | ✅ 全部 = HEAD `ea2a90cb` 工作区(含 scalar 修复) |
| 容器 | ✅ `docker compose restart app` → Up (healthy),`/health` 200 第 1 次探测 |
| 代码标记 | ✅ 容器内 `_backfill_satellite_tables` / `_satellite_upsert` / `_TASK_SATELLITES` 全命中 |
| 12 张从表建表 | ✅ MySQL 全部创建(create_all) |
| 存量回填 | ✅ 101 行(任务 13 + 执行 79 + 合并报告 9),12/12 组「主表有效非空行数 = 从表行数」逐一吻合 |
| API 冒烟 | ✅ `/api/performance/tasks``/api/performance/executions` 均 200 |
| 本地全量测试 | ✅ **437 passed**(含拆表套件 14 用例),零回归 |
| 启动日志 | ✅ 无 performance 相关 error/traceback |
> 注:回填校验需排除 MySQL JSON 字面量 `'null'`(`JSON_TYPE=NULL`)行——主表部分历史执行的大列存的是 JSON null(无有效数据),从表无行是正确行为,读路径从表缺行回退主表列语义一致。
### D. 剩余待办(更新)
| # | 任务 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | **scalar 修复提交推送** | P1 | `performance_service.py` `_migrate_legacy_executions()` 双 scalar 修复(本节 B)+ 本 HANDOFF 更新,待 /GitCommit |
| 2 | 跟踪其他窗口 progress | — | recorder/projects 为其他窗口工作,合并时注意冲突 |
### E. 本次会话工具与脚本(`backend/tmp/`,gitignored)
| 脚本 | 用途 |
|------|------|
| `verify_satellite_deploy_560.py` | 部署核验:远端 5 文件 md5 + 容器健康 + 从表清单 + 启动日志(含双 scalar 修复前发现回填失败的过程) |
| `deploy_satellite_560.py` | 部署执行:5 文件 .bak 备份 → .new 原子上传 → mv → md5 复核 → `docker compose restart app` → 健康探测 → 容器内 grep 代码标记 |
| `verify_backfill_560.py` / `verify_backfill_final_560.py` | 回填对比:12 组「主表非空 vs 从表行数」(final 版排除 JSON 字面量 `'null'`,结论全部 OK) |
| `diff_missing_backfill_560.py` | 定位缺行明细:LEFT JOIN 找主表非空但从表缺行的 execution(确认全部为 `JSON_TYPE=NULL``'null'` 字面量行) |
| `check_tables_and_running_560.py` | 从表存在性 + running/pending 执行检查(部署前确认重启无中断风险) |
**远端备份**:5.60 `/data/third_party/plat-auto-test/backend/app/` 下旧文件备份为 `*.bak_20260908_*`(部署脚本自动创建),确认稳定后可清理。
**md5 对比方法论**:Windows 工作区文件为 CRLF,`git show` 输出为 LF,与 Linux 远端直接 md5 比对会全部误报不一致——需按「git blob 内容 × LF/CRLF 变体」逐一匹配远端 md5(本次远端 5 文件全部命中 `b21f2fe9` 的 CRLF 变体,由此断定未部署)。部署脚本上传本地文件原样字节(CRLF),Python 运行不受换行符影响,与历史部署习惯一致。
---
## ⚡ 会话更新(2026-09-08)— P3 待办闭环:performance 大 JSON 列垂直拆表 ✅
### A. 背景与目标
......@@ -65,8 +121,8 @@ MySQL 1038(`Out of sort memory`)根因是整行加载大 JSON/TEXT 列消耗
| # | 任务 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | 部署 5.60 | P2 | 将 `database.py` / `models/performance.py` / `services/performance_service.py` 部署至 5.60 容器,并重启容器生效回填 |
| 2 | 代码提交推送 | P2 | /GitCommit 规范提交并推送到 origin/platform-auto-test |
| 1 | ~~部署 5.60~~ | ~~P2~~ | ✅ 已部署并验证(12 从表 + 101 行回填,见顶部 2026-09-08 续节 C) |
| 2 | ~~代码提交推送~~ | ~~P2~~ | ✅ 已完成(`ea2a90cb` 已推送;后续 scalar 修复待提交,见顶部待办) |
### G. 多窗口并行开发注意(提交时必读)
......
......@@ -1258,12 +1258,14 @@ class PerformanceService:
Returns:
int: 回填的执行记录数
"""
# 检查是否已有 legacy 回填记录
existing = await self.db.execute(
select(func.count()).select_from(PerformanceExecution)
.where(PerformanceExecution.triggered_by == "legacy")
)
if existing.scalar() and existing.scalar() > 0:
# 检查是否已有 legacy 回填记录(注意:Result.scalar() 只能调用一次,二次调用抛 ResourceClosedError)
legacy_count = (
await self.db.execute(
select(func.count()).select_from(PerformanceExecution)
.where(PerformanceExecution.triggered_by == "legacy")
)
).scalar() or 0
if legacy_count > 0:
logger.info("存量回填已执行过,跳过")
return 0
......
此差异已折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论