feat(storage): move desktop SQLite ownership into a storage utility process
Main no longer opens desktop business SQLite. A single utility-process
storage owner holds the Assistant, Knowledge, attachment, document-result,
remote-binding and native model-ledger databases and runs whole domain
transactions; Main and Runtime callers use explicit async domain operations.
- Remove the Main readWithFallback path and the supervision worker's
separate business write connection; manifests are scanned read-only and
committed by the sole writer.
- Remote events are ACKed only after commit; process loss reports
unconfirmed writes instead of replaying them.
- Use native utilityProcess messaging with one in-flight bound (32) and
ordered waiting instead of capacity rejection and retries.
- Start the storage process before the window so startup overlaps.
- Keep short tool output in memory and store full output once in owned
storage; paging, branching, restart and deletion reuse that copy.
- Put required runtime launch files under goodbuddy-prefixed owned temp
directories and reclaim confirmed stale ones; stop writing duplicate
parsed.md.
- Add lint guards for SQLite ownership and architecture, concurrency
acceptance fixtures, and maintenance/architecture documentation.
Validation: typecheck and lint pass. Alternating baseline (edf019aa) vs
candidate runs show no regression on single-session, C01, C02, C04 and a
5-minute C06; Main p99 drops from ~21 ms to ~16.7 ms under concurrency.
Fresh portable copy (1.75M events) upgrade content unchanged; real-model
output paging passes with 4 external requests. Full npm test runs had
1-9 different timing-sensitive failures per run that pass when rerun.
Not covered: C03 review publication, C07 source body, app-level C05.
This commit is contained in:
@@ -5,11 +5,12 @@
|
||||
- **适用范围:** Desktop(Electron Main、Preload、Renderer)及其后台进程和线程。
|
||||
- **用途:** 新功能设计和代码评审的约束。多个 agent 并行修改代码时,以本文为统一标准。
|
||||
- **关联文档:** `docs/roadmap/product-performance-experience-improvement-plan.md`(测量记录与实施进度)。
|
||||
- **架构与验收:** [桌面存储与并发执行](./desktop-storage-direction.md)定义一次完整交付的职责和范围;[桌面并发验收](../quality/concurrent-desktop-acceptance.md)定义多项目、多会话、多子任务负载及证据。本文件是性能规范的唯一数值来源。
|
||||
- **参照:** VS Code 的进程分工和渲染做法。我们没有在同一台机器上实测过 VS Code,下面的预算按常见的流畅标准设定。
|
||||
|
||||
## 1. 性能预算
|
||||
|
||||
“流畅”用下面的数字判断,不靠感觉。“标准机”指不降速运行 `npm run perf:app`,“低端机”指加上 `GB_PERF_CPU_THROTTLE=4`。
|
||||
“流畅”用下面的数字判断,不靠感觉。“标准机”指记录了硬件环境、不降速运行的参考机;Renderer 降速场景使用 `GB_PERF_CPU_THROTTLE=4`。该降速仅作用于 Renderer,不等于真实低配整机,也不模拟慢 Main 或磁盘。预算适用于单场景及规定的并发负载。
|
||||
|
||||
| 指标 | 标准机 | 低端机 |
|
||||
| --- | --- | --- |
|
||||
@@ -21,7 +22,9 @@
|
||||
| Main 事件循环延迟,p99 | ≤ 30 ms | — |
|
||||
| DOM 节点数 | 不随会话数、消息数增长 | 同左 |
|
||||
|
||||
标准机的阈值由 `npm run perf:check` 检查。低端机的数值目前只做记录,等结果稳定后再加进阈值检查。
|
||||
当前 `npm run perf:check` 使用历史回归上限,部分数值宽于本表,也未覆盖全部并发入口。检查通过不能宣称本表达标。目标交付须在验收报告中分别给出规范预算和回归上限的结果;低端机降速目前只做记录,真实低配和新增自动检查的完成状态另行记录。
|
||||
|
||||
点击反馈、所需内容可用和后台工作完成分别计时。显示加载状态只能证明及时反馈;不能用它替代结果就绪。每个进程分别报告长任务/事件循环和排队耗时,禁止把多个独立探针相加为端到端延迟。
|
||||
|
||||
## 2. 原则
|
||||
|
||||
@@ -32,6 +35,7 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- 新代码不能在 Main 里使用 `DatabaseSync` 和同步文件 API。lint 已有白名单,名单只能减少。
|
||||
- 暂时必须留在 Main 的同步操作,单次耗时要能证明在 16 ms 以内,并在代码里注明理由。
|
||||
- 耗时可能超过 16 ms 的操作,必须支持取消和进度上报,不能一直占着 Main。
|
||||
- 目标交付后 Main 不再直接或通过 helper 打开桌面业务 SQLite;只读、失败回退、维护及退出均在检查范围内。当前迁移例外不是新增同步工作的许可。存储宿主可继续使用同步 SQLite,事务和计算仍需有界。
|
||||
|
||||
### P2 界面状态局部订阅
|
||||
|
||||
@@ -53,7 +57,8 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
|
||||
- `App.tsx` 只负责布局外壳、路由和把 store 实例通过 context 提供给子树。新增领域状态一律走上表四层。
|
||||
- 每个 store 的单元测试和 store 文件放在一起(`<domain>-store.test.ts`),覆盖顺序、失败重试和边界情况。
|
||||
- Main 侧对应:`<domain>` 的数据库访问集中在一个 repository 类里,IPC handler 只做校验和转发。
|
||||
- 领域数据库访问集中在 repository,目标架构中由存储宿主执行;Main 的 IPC handler 只做校验并等待领域操作。受管只读 Worker 与远端 Agent 按各自明确的连接归属执行,不能按源码目录把它们误判为 Main 访问。
|
||||
- 多项目/多会话流式更新只通知相关领域与实体;后台子任务不得让全部项目视图重渲染。隐藏视图保留草稿、选择和必要执行订阅,停止无关绘制与全量重读,切回时按已有摘要及增量恢复。
|
||||
|
||||
### P3 高频数据走专门通道,按帧合并
|
||||
|
||||
@@ -62,12 +67,16 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- 多条更新合并后,每帧最多提交一次;同步到全局状态的频率要更低,目前是 250 ms。
|
||||
- 高频路径上不能读取布局(`getBoundingClientRect`、`scrollHeight` 等),布局读取要集中到每帧一次。
|
||||
- 合并更新不能丢失、重复或打乱顺序。每条数据带序号,并有测试覆盖。
|
||||
- 显示合并与持久化分开。可以合并进度或替换过时刷新,不能静默丢弃消息、工具结果或需确认的事件;多生产者缓冲达到容量时等待或明确失败,不无限增加内存。
|
||||
|
||||
### P4 数据只有一份来源,只传变化部分
|
||||
|
||||
- 每类数据只有一个权威来源,由数据所在进程负责持久化。界面不把整份数据写回。
|
||||
- 跨进程的通知直接带上变化的内容,接收方不再“收到通知后重新查全量”。
|
||||
- 写入合并成批次和事务,不按每个事件单独开一个事务。
|
||||
- 一个数据库有一个业务写入所有者,完整事务不跨 IPC 拆成单条 SQL。Main 不保存第二份可写业务状态;依赖写入的读取等待对应提交,远程 ACK 等待所需事件与 checkpoint 提交成功。
|
||||
- 缓存必须说明归属、失效和释放条件,不按浏览过的项目/会话无限积累。查询返回摘要、分页或所需详情,保持完整信息可访问,不用截断历史满足预算。
|
||||
- GoodBuddy 自身的中转数据优先使用有界内存;已有完整记录或文件时复用引用,避免重复落盘。预览、最终回答与工具全文分别核实,不能以“已保存消息”推断全文已保存。需要超出内存保留的内容直接使用所属存储的唯一正文,分页读取同一内容,避免临时全文再复制到正式存储。必要文件及清理范围遵守[输出与临时资源设计](./desktop-storage-direction.md#output-and-temporary-resource-ownership)。
|
||||
|
||||
### P5 工作量和可见内容成正比
|
||||
|
||||
@@ -80,22 +89,26 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- 首屏之前只做显示应用壳和恢复上次界面所需的工作。
|
||||
- 其他初始化(Provider、Runtime、知识库、通知渠道等)按需执行,或等首屏显示后再空闲执行。
|
||||
- 后台模块在第一次使用时才加载。
|
||||
- 冷启动、首次进入功能和暖返回分别验收;预加载只使用有界空闲工作,不能为每个项目预建进程、连接或整份历史缓存。进程 spawn 不代表服务就绪,首次可用反馈与数据实际可用分开记录。
|
||||
|
||||
### P7 先测量,再修改
|
||||
|
||||
- 先跑测量,拿到数据再改代码;每项改动都要有前后对比,基线用临时 worktree 构建。
|
||||
- 每个版本至少跑 3 轮,交替运行;结果差异小于同一版本多次运行之间的波动时,不能说成“有提升”。
|
||||
- 测量用隔离的用户目录和本地假模型,不发出外部模型请求,结果里不含用户内容。
|
||||
- 必测多项目、多会话、多子任务及导入/检索/回顾发布重叠场景,负载和采样按[并发验收](../quality/concurrent-desktop-acceptance.md)。记录 offered load、实际吞吐、排队和排空、资源高水位;不得降低输入速率、串行化夹具或丢事件来取得流畅结果。
|
||||
- 基准使用固定版本、数据和环境。性能测量与真实模型功能验收分开;受发布构建限制时遵守发布手册,不以性能原则授权额外生产打包。
|
||||
|
||||
### P8 正确性优先于速度
|
||||
|
||||
- 事件顺序、持久化、取消、崩溃恢复和显示结果都不能因为性能而改变。
|
||||
- 异步改造要先确认“先写入、后读取”的顺序,没有测试覆盖就不合并。
|
||||
- 风险高、收益小,或者会改变现有功能的优化先不做,在计划文档里记录理由。
|
||||
- 取消请求、执行结算和容量释放不是同一时刻;调用方停止等待不能提前释放仍运行的工作。取消一个会话或子任务不得杀死仍服务其他任务的共享宿主。已提交事务如实报告成功,不伪装成回滚;未确认写入不盲目重放。
|
||||
|
||||
### P10 体验不倒退
|
||||
|
||||
性能优化只能让体验变好,不能用体验换速度。“行为不变”不仅指数据和结果,也包括用户能感知到的交互:
|
||||
性能优化必须保留数据、结果和用户交互语义,不能用体验换速度:
|
||||
|
||||
- **动画和过渡保持原样。** 平滑滚动(如“到底部”按钮)、展开收起、悬停、淡入淡出都不能因为优化而消失。任何瞬间写入 `scrollTop`/`scrollTo` 的代码都会打断正在进行的平滑滚动,必须在平滑滚动期间暂停。
|
||||
- **只渲染可见部分(P5)时,用户状态不能丢。** 行被卸载后,它的局部状态也会消失。以下行必须保持挂载,或者把状态存到行外:有焦点的行、用户展开过 `<details>` 或输入过内容的行、选中文本的两端、待回答的问题和待审批项、打开菜单或正在重命名的行。
|
||||
@@ -109,6 +122,24 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
- lint 规则和性能阈值是合并前的硬性条件,不靠人工记得。
|
||||
- 规则只能收紧,白名单和上限只能往下调。
|
||||
- 每个 agent 只提交自己改的文件和片段,不把别人未提交的改动带进自己的提交。
|
||||
- 检查直接 SQL 导入及已知间接打开函数/构造点,按实际宿主区分测试、存储与远端代码。白名单和上限需要与已提交基线比较,不能只靠注释约定;全局同步回退同样要被发现。
|
||||
- 普通 CI 与性能执行环境分别提供功能和预算证据;新增并发与首次进入场景纳入候选必需检查,缺少运行环境或结果时保留验收缺口,不由单测通过推断达标。
|
||||
|
||||
### P11 并发隔离与有界工作
|
||||
|
||||
同一会话的事件顺序和同一数据库的提交顺序必须保持;不同项目、会话、子任务的模型等待、重查询与清理不因此共用一把长时间全局锁。
|
||||
|
||||
- Main 全局控制队列不得等待模型、网络、原生启动或耗时清理。必要登记和状态提交可短暂串行,等待在所属操作范围内完成。
|
||||
- 重查询复用有界 Worker;必要写入不排在整个导入解析或模型请求之后。长事务先减少事务内无关计算,再测写入排队,不把阻塞简单搬进唯一存储队列。
|
||||
- 复用现有池、批处理和队列,记录在途数、排队数及字节上限。有限任务在持续竞争下仍须完成,繁忙会话不能无限抢占下一批;有实测饥饿才引入最小调度调整,不预建通用优先级框架。
|
||||
- 控制和取消不等待新的执行槽位;饱和时按业务语义等待或返回可恢复错误。禁止静默丢失、无限缓冲、无限重启或自动扩增进程。
|
||||
- 每个异步响应携带已有项目/会话/任务/操作归属;迟到响应不得覆盖新选择或新操作。归属检查先于改变共享请求代次、缓存或队列状态。
|
||||
|
||||
### P12 架构完整性与 KISS
|
||||
|
||||
一次性交付按最终职责边界验收,不以全面 Promise 化或局部热点迁出代替。目标范围内正常、后台、失败、重试、取消和退出路径均使用同一权威实现,清理原直接入口和回退。
|
||||
|
||||
保持现有技术栈、数据库文件及业务合同,复用已有资源机制。不得顺带新增分布式事务、持久 RPC receipt、通用恢复框架、每项目独立服务或产品审批模式。同步内存缓存读取可以保留,不把纯函数和每个 getter 远程化。设计新增机制必须指出当前失败、现有机制的不足与对应验证,不以“将来可能并发”代替证据。
|
||||
|
||||
## 3. 评审清单
|
||||
|
||||
@@ -125,21 +156,30 @@ Main 负责窗口、生命周期、权限和消息转发。数据库查询、文
|
||||
9. 动画、平滑滚动、过渡还在吗?有没有新增会打断平滑滚动的瞬间滚动写入?
|
||||
10. 只渲染可见部分的列表,滚走再滚回来,展开状态、输入内容、选区、焦点还在吗?
|
||||
11. 合并或延迟处理后,第一次响应(打字回显、点击反馈)有没有变慢?复制、导出读到的是不是屏幕上的最新内容?
|
||||
12. 多项目/多会话/多子任务同时运行时,取消或清理一个请求是否拖住其他任务?队列和缓存是否持续增长?
|
||||
13. 存储提交、远程 ACK、调用方返回和资源释放的顺序是否有真实路径及故障注入验证?
|
||||
14. 是否删除目标范围内的旧直接访问和同步回退?新增进程或调度机制是否复用现有设施并有实际必要性?
|
||||
15. 请求或输出是否产生非必要临时文件、重复全文?是否核实完整内容与预览的区别,并验证分页、引用释放、必要文件的创建失败清理及升级后的残留回收?
|
||||
|
||||
## 4. 当前差距
|
||||
|
||||
截至 2026-10-04(第一期结项,见 `docs/review/performance-phase-1-report-2026-10-04.md`),按原则对照的主要差距:
|
||||
截至 2026-10-05,以下状态结合[存储升级实施记录](../quality/storage-upgrade-implementation-2026-10-05.md)和[并发验收规范](../quality/concurrent-desktop-acceptance.md)核对。表中“已实现”只表示代码或定向测试已有证据,不表示 PERF-15、PERF-16 或 C01-C07 已完成。
|
||||
|
||||
| 原则 | 差距 |
|
||||
| --- | --- |
|
||||
| P1 | 同步数据库调用仍在 Main,没有独立数据进程;但热点读已在 worker,高频写单次 0.1–0.3 ms,WAL checkpoint 在后台线程,启动恢复按索引读取。文档解析、分块和大文件哈希已移到 worker。消息附加数据冗余(子任务工具输出、`tools` 与 `blocks` 重复),大会话读取约 170 ms |
|
||||
| P1 | Assistant、Knowledge、文件和 Runtime 生产域已由 `DesktopStorageClient` 接入 utility process;剩余缺口是全量间接访问扫描、异常/维护/退出路径核验,以及 Main 阻塞和存储排队的并发测量。文档解析、分块和大文件哈希仍在各自 worker。 |
|
||||
| P2 | `App.tsx` 约 7,900 行。输入区、隐藏路由、任务和结果事件、切换会话已不再整体重渲染 App;当前会话的流式吸收(每 250 ms)、队列和活动记录仍经过 App |
|
||||
| P3 | 已基本满足:流式输出和终端输出都已按帧合并 |
|
||||
| P4 | 活动记录已改为增量保存、按页读取(界面只保留已加载的页,计数和分组来自 Main 的汇总);部分 `*Changed` 通知之后还会重新查全量 |
|
||||
| P5 | 已基本满足:消息、会话、笔记、文件树、知识库文档表格都只渲染可见部分 |
|
||||
| P6 | 空配置启动到输入框可用约 0.96 秒;真实数据(2.4 GB)约 1.36 秒。本地工具环境已后台初始化,低频模块按需加载 |
|
||||
| P9 | 性能阈值还没有接入 CI,也没有真实低端机的基线 |
|
||||
| P3 | 流式输出和终端输出已有按帧合并;C01/C02/C06 的长期负载仍需验证是否出现排队增长、长帧或内容丢失。 |
|
||||
| P4 | 存储域已统一到各自 owner,输出 backing 和附件引用已接入;部分 `*Changed` 通知仍会重新读取全量,重复缓存和跨域通知的并发影响待测。 |
|
||||
| P5 | 消息、会话、笔记、文件树、知识库文档表格已有窗口化;C07 仍需记录冷/暖内容可用时刻和切换后的状态保留。 |
|
||||
| P6 | 空配置和真实数据启动有历史测量;当前 C07 尚未完成监督概览、图谱、历史选择和来源正文的完整冷/暖验收。 |
|
||||
| P9 | 已有 lint 和历史回归上限;间接 SQLite 打开、白名单单调收紧、并发指标缺失和 CI/性能环境接入仍待补齐。 |
|
||||
| P10 | 已有局部输入、刷新和项目切换修复;需要在 C01/C02/C06 中继续验证动画、焦点、滚动、草稿和取消语义没有因降载或合并而退化。 |
|
||||
| P11 | 存储请求和运行时路径已有有界容量与按域归属;C02、C04、C05、C06 尚未证明健康 peer 隔离、饱和恢复、队列排空和有限后台任务完成。 |
|
||||
| P12 | 目标边界和 KISS 约束已写入架构文档;S01-S07 已有生产组合,但删除旧直接入口、异常路径和全候选统一验收仍未完成。 |
|
||||
|
||||
2026-10-04 补充:监督回顾的来源初始化和继续前版本校验已移入现有 Worker 入口的独立连接,扫描不持有写锁,清单按 200 条提交,生产失败不回退 Main 同步扫描。最终发布事务仍在 Main;实现与实测边界见[监督者进度](../features/conversation-supervision/progress.md#2026-10-04-回顾初始化阻塞修复)。
|
||||
|
||||
2026-10-05 补充:监督者 UI 的概览、图谱、故事读取也已迁到现有只读 Worker;概览按需加载、列表窗口化及摘要缓存见[KISS 局部修复](../review/kiss-local-fixes-2026-10-05.md)。这与工具侧 Story Graph 读取是不同入口。Main 最终发布及部分大响应仍有超预算样本,不能把局部完成视为全项达标。存储服务目标另见[桌面存储方向](./desktop-storage-direction.md),本轮不实施全面迁移。
|
||||
2026-10-05 局部修复记录:监督者 UI 的概览、图谱、故事读取也已迁到现有只读 Worker;概览按需加载、列表窗口化及摘要缓存见[KISS 局部修复](../review/kiss-local-fixes-2026-10-05.md)。这与工具侧 Story Graph 读取是不同入口。Main 最终发布及部分大响应仍有超预算样本,不能把局部完成视为全项达标。
|
||||
|
||||
随后用户确定按[完整存储与并发架构](./desktop-storage-direction.md)一次性交付。当前已完成存储 owner、主要生产组合和部分输出迁移,但没有完成全面架构验收。P11/P12 与[并发验收矩阵](../quality/concurrent-desktop-acceptance.md)约束当前候选的剩余验证,不能作为已有实现或实测结果;上述第一期历史状态也不替代当前验收。
|
||||
|
||||
Reference in New Issue
Block a user