docs: design for telemetry SQLite concurrency fix (align HarnessLog pattern)
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
# 遥测 SQLite 高并发写锁修复设计
|
||||||
|
|
||||||
|
> 2026-07-16。修复 `SQLiteTelemetryRecorder` 在 concurrency≥12 时 `database is locked`、遥测大量丢失的问题。诉求:训练(concurrency 24)时遥测**零丢失**——保住"详细 agent 日志用于回溯分析"这一核心诉求。
|
||||||
|
|
||||||
|
## 1. 根因(对照 HarnessLog 暴露)
|
||||||
|
|
||||||
|
同样高并发写 SQLite,`app/harness/log.py:HarnessLog` 从不锁死,`adapters/telemetry.py:SQLiteTelemetryRecorder` 频繁 locked。差异在**并发控制放在哪一层**:
|
||||||
|
|
||||||
|
| | HarnessLog(不锁死) | SQLiteTelemetryRecorder(锁死) |
|
||||||
|
|--|---------------------|-------------------------------|
|
||||||
|
| 连接 | 单长连接(构造建一次,`check_same_thread=False`,全程复用) | 每次 `_write` 新建连接 + close |
|
||||||
|
| 并发控制 | 进程内 `threading.Lock` 串行化写 | 无 Lock,靠 SQLite `busy_timeout=5000` 跨连接协调 |
|
||||||
|
| 结果 | 进程内始终只有一个连接写 → 零 SQLite 锁竞争 | 12-24 连接并发写同一 db → 高频撑爆 busy_timeout → locked |
|
||||||
|
|
||||||
|
**根因一句话**:telemetry 把并发控制交给 SQLite 的跨连接锁(busy_timeout,高频不可靠),HarnessLog 把它拉到进程内(threading.Lock,可靠串行)。telemetry 已有 WAL + busy_timeout + try/except 降级,但缺"单连接 + 进程内 Lock"这层,故高频下仍锁。
|
||||||
|
|
||||||
|
## 2. 为什么不直接用 HarnessLog(架构溯源)
|
||||||
|
|
||||||
|
telemetry **不能**复用 HarnessLog 实例,三条硬隔离:
|
||||||
|
|
||||||
|
| 维度 | telemetry | HarnessLog |
|
||||||
|
|------|-----------|------------|
|
||||||
|
| DB 文件 | `logs/telemetry.db`(全局、跨 run/workspace) | `workspaces/<ws>/harness.db`(per-workspace 训练数据) |
|
||||||
|
| 分层 | `adapters/`(实现 core `TelemetryRecorder` Protocol) | `app/harness/`(应用层具体类) |
|
||||||
|
| 职责/表 | 每次 LLM 调用 raw I/O 可观测性,`llm_calls` 表 | 训练数据(predictions/traces/gate),无 `llm_calls` |
|
||||||
|
|
||||||
|
依赖方向禁止 `adapters` 依赖 `app/harness` 具体类(Clean Architecture)。telemetry 当初独立实现是**架构正确**的。
|
||||||
|
|
||||||
|
## 3. 决策:对齐同模式,不抽共享基座
|
||||||
|
|
||||||
|
真正的次优点是"单连接+Lock+WAL"这套可靠模式被**重复实现**(HarnessLog 一份、telemetry 一份且写错)。理想是抽共享基座,但受阻:
|
||||||
|
|
||||||
|
- 共享基座放 `adapters/` → app(HarnessLog)不依赖 adapters,用不了。
|
||||||
|
- 放 `core/` → 违反"core 不含 SQLite 具体实现"。
|
||||||
|
- 要抽须新开双方都能依赖的基础设施模块 + 改动刚改过 register_run 的 HarnessLog(回归风险)+ 改动面大。
|
||||||
|
|
||||||
|
**决策(Rejected: 抽共享基座)**:telemetry **对齐** HarnessLog 已验证的连接管理模式,两处加交叉引用注释。接受"同一可靠模式在两处应用"(模式复用,非逻辑重复),换取改动局限、零风险、不动 HarnessLog。共享基座记 future work(若第三处再出现同款 SQLite 写需求,届时抽)。
|
||||||
|
|
||||||
|
## 4. 改动(局限 `adapters/telemetry.py` 一个类)
|
||||||
|
|
||||||
|
- **构造 `__init__`**:建一个长连接 `sqlite3.connect(db_path, check_same_thread=False)`(asyncio.to_thread 在线程池不同线程调用,共享连接需此 flag + Lock 保证串行);`PRAGMA journal_mode=WAL`;建表一次(去掉懒建表 `_ensure_table`/`_table_ready`);建 `threading.Lock`。
|
||||||
|
- **`_write`**:改为 `with self._lock: self._conn.execute(INSERT); self._conn.commit()`(不再新建/关闭连接)。
|
||||||
|
- **保留**:`INSERT OR IGNORE`(call_id 主键幂等)、`try/except sqlite3.Error → 降级 warning`(遥测失败绝不冒泡拖垮 LLM 调用)、`async record_llm_call` 经 `asyncio.to_thread` 卸载阻塞写。
|
||||||
|
- **注释**:`telemetry.py` 与 `log.py` 各加一行交叉引用,标注共用"单连接+Lock+WAL"并发写模式。
|
||||||
|
|
||||||
|
## 5. 前序版本行为审计
|
||||||
|
|
||||||
|
| 现有行为 | 处置 |
|
||||||
|
|---------|------|
|
||||||
|
| `record_llm_call` async Protocol 接口 | **保留**(GovernedLLMClient 依赖签名) |
|
||||||
|
| 每次写新建连接 + close | **替换**(锁竞争根源)→ 单长连接 |
|
||||||
|
| 无进程内 Lock | **新增** threading.Lock |
|
||||||
|
| `asyncio.to_thread` 卸载 | **保留**(Lock 线程内持有,串行化) |
|
||||||
|
| `INSERT OR IGNORE`(幂等) | **保留** |
|
||||||
|
| try/except 降级不冒泡 | **保留**(核心哲学,遥测失败不拖垮主流程) |
|
||||||
|
| 懒建表 | **替换**为构造时建一次(长连接下无需懒建) |
|
||||||
|
| WAL + busy_timeout | 保留 WAL;busy_timeout 可保留(长连接下已无跨连接竞争,作纵深防御) |
|
||||||
|
|
||||||
|
## 6. 非功能四维
|
||||||
|
|
||||||
|
| 维度 | 保障 |
|
||||||
|
|------|------|
|
||||||
|
| **持久化** | 每次 `commit` 同步落 WAL → 零丢失(满足硬约束) |
|
||||||
|
| **幂等** | `INSERT OR IGNORE` + call_id 主键,重复写安全 |
|
||||||
|
| **续跑** | 不适用(遥测无状态;进程退出 WAL 自动恢复已 commit 的) |
|
||||||
|
| **原子性** | 单条 insert+commit 原子,无半写 |
|
||||||
|
|
||||||
|
## 7. 测试
|
||||||
|
|
||||||
|
- **并发写不锁死**(核心):多线程/多协程并发调 `record_llm_call`(如 32 并发 × N 条),断言全部落库、零 `database is locked`、零丢失(行数 == 写入数)。这是复现 bug 的真实场景测试。
|
||||||
|
- **幂等**:同 call_id 重复写,只 1 行。
|
||||||
|
- **降级不冒泡**:DB 错误(如目录不可写)时 `record_llm_call` 不抛,只 warning。
|
||||||
|
- 现有 `test_telemetry.py` 回归全绿。
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
type: design
|
||||||
|
node_id: design:telemetry-concurrency-fix
|
||||||
|
title: "遥测 SQLite 高并发写锁修复"
|
||||||
|
date: 2026-07-16
|
||||||
|
---
|
||||||
|
|
||||||
|
# 遥测 SQLite 高并发写锁修复
|
||||||
|
|
||||||
@@ -270,6 +270,11 @@
|
|||||||
"id": "review:preflight-final-review",
|
"id": "review:preflight-final-review",
|
||||||
"label": "训练前修复分支终审",
|
"label": "训练前修复分支终审",
|
||||||
"type": "review"
|
"type": "review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "design:telemetry-concurrency-fix",
|
||||||
|
"label": "遥测 SQLite 高并发写锁修复",
|
||||||
|
"type": "design"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Research Wiki 索引
|
# Research Wiki 索引
|
||||||
|
|
||||||
> 自动生成,更新时间:2026-07-16 11:06 UTC
|
> 自动生成,更新时间:2026-07-16 12:47 UTC
|
||||||
|
|
||||||
## design (36)
|
## design (38)
|
||||||
- [2026-07-06-core-agent-adapters-llm-design](designs/2026-07-06-core-agent-adapters-llm-design.md) `design:2026-07-06-core-agent-adapters-llm-design`
|
- [2026-07-06-core-agent-adapters-llm-design](designs/2026-07-06-core-agent-adapters-llm-design.md) `design:2026-07-06-core-agent-adapters-llm-design`
|
||||||
- [2026-07-07-app-harness-design](designs/2026-07-07-app-harness-design.md) `design:2026-07-07-app-harness-design`
|
- [2026-07-07-app-harness-design](designs/2026-07-07-app-harness-design.md) `design:2026-07-07-app-harness-design`
|
||||||
- [2026-07-07-core-evolution-design](designs/2026-07-07-core-evolution-design.md) `design:2026-07-07-core-evolution-design`
|
- [2026-07-07-core-evolution-design](designs/2026-07-07-core-evolution-design.md) `design:2026-07-07-core-evolution-design`
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
- [2026-07-11-question-gen-v2-design](designs/2026-07-11-question-gen-v2-design.md) `design:2026-07-11-question-gen-v2-design`
|
- [2026-07-11-question-gen-v2-design](designs/2026-07-11-question-gen-v2-design.md) `design:2026-07-11-question-gen-v2-design`
|
||||||
- [2026-07-12-per-category-pool-strategy-design](designs/2026-07-12-per-category-pool-strategy-design.md) `design:2026-07-12-per-category-pool-strategy-design`
|
- [2026-07-12-per-category-pool-strategy-design](designs/2026-07-12-per-category-pool-strategy-design.md) `design:2026-07-12-per-category-pool-strategy-design`
|
||||||
- [2026-07-16-preflight-fixes-design](designs/2026-07-16-preflight-fixes-design.md) `design:2026-07-16-preflight-fixes-design`
|
- [2026-07-16-preflight-fixes-design](designs/2026-07-16-preflight-fixes-design.md) `design:2026-07-16-preflight-fixes-design`
|
||||||
|
- [2026-07-16-telemetry-concurrency-fix-design](designs/2026-07-16-telemetry-concurrency-fix-design.md) `design:2026-07-16-telemetry-concurrency-fix-design`
|
||||||
- [Action Recognition 单题型首次训练实验设计](designs/2026-07-14-action-recognition-training-design.md) `design:2026-07-14-action-recognition-training-design`
|
- [Action Recognition 单题型首次训练实验设计](designs/2026-07-14-action-recognition-training-design.md) `design:2026-07-14-action-recognition-training-design`
|
||||||
- [main.py 推理入口 + 初始 Prompt 集设计](designs/2026-07-09-main-inference-entry-design.md) `design:2026-07-09-main-inference-entry-design`
|
- [main.py 推理入口 + 初始 Prompt 集设计](designs/2026-07-09-main-inference-entry-design.md) `design:2026-07-09-main-inference-entry-design`
|
||||||
- [main.py 推理入口 + 初始 Prompt 集设计](designs/main-inference-entry.md) `design:main-inference-entry`
|
- [main.py 推理入口 + 初始 Prompt 集设计](designs/main-inference-entry.md) `design:main-inference-entry`
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
- [论文主图:Self-Evolving Search Agent 推理训练闭环](designs/paper-main-figure.md) `design:paper-main-figure`
|
- [论文主图:Self-Evolving Search Agent 推理训练闭环](designs/paper-main-figure.md) `design:paper-main-figure`
|
||||||
- [赛题生成工具设计](designs/question-gen-synth.md) `design:question-gen-synth`
|
- [赛题生成工具设计](designs/question-gen-synth.md) `design:question-gen-synth`
|
||||||
- [赛题生成工具设计(Question Generation Synthesis)](designs/2026-07-09-question-gen-synth-design.md) `design:2026-07-09-question-gen-synth-design`
|
- [赛题生成工具设计(Question Generation Synthesis)](designs/2026-07-09-question-gen-synth-design.md) `design:2026-07-09-question-gen-synth-design`
|
||||||
|
- [遥测 SQLite 高并发写锁修复](designs/telemetry-concurrency-fix.md) `design:telemetry-concurrency-fix`
|
||||||
|
|
||||||
## finding (10)
|
## finding (10)
|
||||||
- [2026-07-11-benchmark-failure-taxonomy](findings/2026-07-11-benchmark-failure-taxonomy.md) `finding:2026-07-11-benchmark-failure-taxonomy`
|
- [2026-07-11-benchmark-failure-taxonomy](findings/2026-07-11-benchmark-failure-taxonomy.md) `finding:2026-07-11-benchmark-failure-taxonomy`
|
||||||
|
|||||||
@@ -135,3 +135,5 @@
|
|||||||
- [2026-07-16 11:06 UTC] 新增 review: 训练前修复分支终审 (review:preflight-final-review)
|
- [2026-07-16 11:06 UTC] 新增 review: 训练前修复分支终审 (review:preflight-final-review)
|
||||||
- [2026-07-16 11:06 UTC] 新增边: review:preflight-final-review --informs--> design:preflight-fixes
|
- [2026-07-16 11:06 UTC] 新增边: review:preflight-final-review --informs--> design:preflight-fixes
|
||||||
- [2026-07-16 11:06 UTC] 重建索引: 111 篇页面
|
- [2026-07-16 11:06 UTC] 重建索引: 111 篇页面
|
||||||
|
- [2026-07-16 12:47 UTC] 新增 design: 遥测 SQLite 高并发写锁修复 (design:telemetry-concurrency-fix)
|
||||||
|
- [2026-07-16 12:47 UTC] 重建索引: 113 篇页面
|
||||||
|
|||||||
Reference in New Issue
Block a user