perf(db): relax frequent local writes, checkpoint WAL off Main, index startup recovery (schema 58)
This commit is contained in:
@@ -951,7 +951,36 @@ Renderer 主线程降速 4 倍(Main 不降速,每次 reload 后重新应用
|
||||
|
||||
测试:随机动作序列(含翻页、截断、刷盘和注入的保存失败),在真实数据库上与改动前的整份列表逻辑逐步对比,最终数据库与整份 `replace` 的结果一致(含重复 ID 和移到最前);启动后界面不超过一页;保存载荷只取决于变化的记录(含未加载的);随机数据集(含重复 ID)下,分页加汇总渲染出的计数、分组、标题、状态和清空数量与整份列表渲染一致;重启后遗留的进行中记录被标为中断,说明文本相同;清空后数据库、界面和汇总都为空;SQL 变更语义与参考实现一致。`perf:app` 60 项阈值全部通过。
|
||||
|
||||
未做:汇总按已加载的会话查询,加载 5,000 个以上会话时只取前 5,000 个;分组里“会话活动数”统计的仍是已加载的记录,和以前统计已显示的记录一致。
|
||||
后续(2026-10-04):汇总覆盖全部已加载会话,超过 5,000 个时分多次请求再合并,不再截断(单元测试:5,003 个会话分 5,000 + 3 两次请求,全部有标题和状态)。分组里“会话活动数”统计的仍是已加载的记录,和以前统计已显示的记录一致。
|
||||
|
||||
### 7.2.2 Main 写库与启动:真实数据测量(2026-10-04,PERF-15)
|
||||
|
||||
用便携版 `dist/GoodBuddy-windows-x64/data` 的副本测量(源目录从不被打开):`assistant.sqlite` 2.4 GB,175.5 万条任务事件(其中 148 万条子任务进度、1.1 GB),4,812 条消息(metadata 约 400 MB,单条最大 4 MB),2.4 万条活动记录。新增 `npm run perf:write`(`build/run-write-perf.cjs`,按真实数据中最忙一秒的速率 270 条/秒回放真实事件),`perf:startup` 新增 `real` 变体(`GB_PERF_STARTUP_REAL_DATA`,每次启动用新副本,外网请求全部拦截)。
|
||||
|
||||
**测到的问题:**
|
||||
1. 每次写库都按 `synchronous = FULL` 同步落盘,单次约 2.4 ms,与写入内容无关:子任务进度、工具、文本批、会话保存、活动记录都一样。
|
||||
2. SQLite 自动 checkpoint 发生在越过 1,000 页的那次提交里:大库上每隔几百次写入就有一次卡住 Main 100–350 ms(3 轮里 18 次超过 50 ms)。
|
||||
3. 启动时两条恢复语句、一条图片操作恢复语句各扫描全部消息的 metadata:在 Main 上同步约 2.3 秒,启动到输入框可用 3.7 秒。
|
||||
|
||||
**改动:**
|
||||
- **频繁的本地写入改用 `synchronous = NORMAL`**:本地运行事件(生命周期事件 `status`/`done`/`error` 除外)、渲染进程 500 ms 一次的会话保存、活动记录增量。WAL 模式下仍是原子提交,应用崩溃不丢;断电最多丢最近一段这类写入,任何 FULL 提交及其之前的数据不受影响。远程事件(Agent ACK 依赖其持久性)、通道投递、任务状态等其余写入仍为 FULL。
|
||||
- **WAL checkpoint 移到 worker 线程**:复用只读查询 worker 的入口,每 250 ms 做一次 PASSIVE checkpoint;运行期间 Main 的自动 checkpoint 阈值提到 4,000 页作为兜底(持续写入时被动 checkpoint 追不上 WAL 重置点),worker 退出即恢复默认 1,000。
|
||||
- **启动恢复按索引读候选消息**:第 58 版 schema 新增部分索引 `messages_recovery_idx`(流式中,或 metadata 含未结束的工具、子任务、图片操作状态);没有用户队列项时跳过按 queueItemId 的全表删除。metadata 只由 `JSON.stringify` 写入,状态总以紧凑形式出现,精确判断仍在候选行上用原逻辑执行。一次性迁移在升级 worker 中执行,真实数据约 2 秒。
|
||||
|
||||
**结果(真实数据):**
|
||||
|
||||
| 指标 | 之前 | 之后 |
|
||||
| --- | --- | --- |
|
||||
| 单次写库 p50(子任务 / 工具 / 文本 / 会话保存 / 活动记录) | 2.2–2.6 ms | 0.1–0.3 ms |
|
||||
| 写入期间 Main 最长阻塞(270 条/秒,3 轮) | 8–346 ms,超过 50 ms 共 37 次 | 2–34 ms,超过 50 ms 0 次 |
|
||||
| 启动:Main 上的初始化(`assistant-db-init`) | 2,564 ms | 232 ms |
|
||||
| 启动:到输入框可用(中位数,4 次) | 3,702 ms | 1,359 ms |
|
||||
| 启动:侧栏出现会话 | 4,081 ms | 1,749 ms |
|
||||
| 启动期间 Main 最长阻塞 | 2.5–2.8 s | 236–257 ms |
|
||||
|
||||
部分索引的代价:metadata 更新时如果新旧值都命中候选条件,要多维护一次索引(4 MB metadata 的更新 36 → 59 ms,233 KB 的 0.7 → 1.6 ms);不命中时没有额外开销。启动时剩下的约 0.6 s 在本地工具环境探测(已在后台初始化,不阻塞 Main)。
|
||||
|
||||
未做:读取最大的会话(407 条消息、42 MB metadata)在 worker 上约 170 ms,不阻塞 Main,但切换到这个会话时要等这么久;需要按需加载 metadata,单独立项。
|
||||
|
||||
### 7.3 体验倒退复查(2026-10-03)
|
||||
|
||||
@@ -984,7 +1013,7 @@ Renderer 主线程降速 4 倍(Main 不降速,每次 reload 后重新应用
|
||||
|
||||
### PERF-15 存储接口异步化
|
||||
|
||||
- **优先级 / 状态:** P1 / 待开始
|
||||
- **优先级 / 状态:** P1 / 进行中(热点读走 worker;高频写降为 NORMAL、checkpoint 移出 Main、启动恢复按索引,见 7.2.2;接口异步化未开始)
|
||||
- **范围:** 在原进程内把 `AssistantDatabase`、`KnowledgeDatabase` 等对外接口改为异步,
|
||||
调用方全部 `await`;会话列表只返回摘要,搜索下推到 SQL,活动记录改为增量追加。
|
||||
远程 Runtime 实时事件改用已有的 `appendRemoteTaskEventsBatch` 批量写入,不再逐条同步事务。
|
||||
|
||||
Reference in New Issue
Block a user