Skip to content

perf(dashboard): 缓存群矩阵并提供轻量元数据 - #687

Open
deepcoldy wants to merge 1 commit into
masterfrom
agent/cache-group-matrix-presentation
Open

perf(dashboard): 缓存群矩阵并提供轻量元数据#687
deepcoldy wants to merge 1 commit into
masterfrom
agent/cache-group-matrix-presentation

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

背景

Dashboard 的群元数据读取目前每次都会 fan-out 到全部在线 daemon;每个 daemon 再翻页读取飞书群列表,中央 Dashboard 随后为每个群补齐全部 Bot 的 memberBots。这使群列表请求同时承担大量飞书请求、矩阵构造和网络传输开销。

在 devbox 的实际数据上:

  • 964 个群、47 个在线 Bot
  • 完整 GET /api/groups 约 6.51 MB、约 3.9 秒
  • 一次刷新至少需要 64 次飞书分页请求
  • 完整矩阵包含 45,308 条 memberBots,真实在群关系为 2,634 条
  • chatId/name/avatar 的展示投影约 264 KB

这也会让只需要群名/头像的客户端被迫下载完整管理矩阵。

改动

  • 为中央群矩阵增加 30 秒内存快照:
    • 并发刷新 single-flight 合并
    • TTL 内复用成功结果
    • 刷新失败时短暂沿用最近成功快照
    • 群创建、加 Bot、退群、解散、oncall 变更、群主转移及 Bot roster 变化后主动失效
  • GET /api/groups?view=compact 仅返回群展示字段 chatId/name/avatar
  • GET /api/groups?refresh=1 支持已认证 Dashboard 手动强制刷新;公开只读请求不能绕过缓存
  • GET /api/sessions 从现有快照补全群聊 chatDisplayName
    • 冷缓存仅后台预热,不阻塞会话热路径
    • 不修改持久化 Session,不批量产生 SSE 更新事件
    • 不覆盖 p2p 名称或已有群名
  • 默认 GET /api/groups 响应结构保持不变,Dashboard 群管理页和内部 groups-matrix/overview-snapshot 继续使用完整矩阵
  • Dashboard 前端的“强制刷新”会显式请求服务端刷新,而不只是绕过浏览器侧 3 秒缓存

影响面

  • 改动位于中央 Dashboard 聚合/展示层,不涉及各 CLI adapter、PTY/Tmux backend 或 Session 持久化格式
  • 所有 CLI、群会话和历史会话共用相同展示投影
  • compact 接口只暴露原公开群接口已有的名称和头像字段
  • 默认完整接口保持兼容;旧客户端仍可继续调用原接口,新客户端可选择轻量视图

验证

  • pnpm build:通过(含 TypeScript、Dashboard bundle、dist audit)
  • pnpm vitest run test/dashboard-groups-matrix-snapshot.test.ts test/groups-action-helpers.test.ts test/daemon-internal-api.test.ts test/dashboard-public-redact.test.ts:4 files / 144 tests 通过
  • 配套 desktop 客户端:
    • node / cli / web 三套 TypeScript 配置通过
    • Dashboard bridge 相关 3 files / 16 tests 通过
  • 尝试执行 pnpm test 全量单测;当前 Codex 运行环境没有系统级 node / npm,导致 codex-app-threads 的隔离子进程报 env: node: No such file or directoryplugin-init 的 npm fixture 报 spawnSync npm ENOENT。本 PR 相关测试及正式构建均已通过。

@deepcoldy
deepcoldy marked this pull request as ready for review July 31, 2026 10:16
@deepcoldy

Copy link
Copy Markdown
Owner Author

Codex 复审结论:P2 成立,建议修后再合

当前 gh 登录身份与 PR 作者相同,GitHub 不允许对自己的 PR 提交 “Request changes”;因此以普通 PR 评论记录阻塞性复审结论。

Claude2 报告的 role-invalidation P2 成立,严重度定为 P2 合理;缓存成功路径的竞态与 compact/refresh 的鉴权、脱敏边界未发现阻塞问题。

P2:角色写入未失效群矩阵快照

buildGroupsMatrix() 把 daemon /api/groups 返回的 hasRole 缓存在中央 30s 快照中,但以下成功写入路径都直接透传响应,没有调用 groupsMatrixSnapshot.invalidate()

  • PUT /api/roles/:larkAppId/:chatId
  • DELETE /api/roles/:larkAppId/:chatId
  • POST /api/role-profiles/:profileId/apply(实际 apply,非 preview)
  • 同一能力的内部 HMAC 路由 PUT/DELETE /__daemon/groups/:chatId/roles/:appId

roles 页保存、删除或 apply 后会立刻重新请求普通 /api/groups;该页两个刷新按钮同样没有 refresh=1。因此 30s TTL 内会继续显示旧的“已配置/未配置”徽标,loadRoleProfileContext() 还会按旧 hasRole 决定是否加载有效角色上下文。用户的写入本身成功,但读己之写和手动刷新都失效。

建议把 role mutation 也接入统一的成功后失效回调(只在真正成功且非 preview 的 mutation 后触发),同时覆盖 browser 与 HMAC 两条入口,并加路由级回归测试。这个问题没有数据丢失或安全影响,但属于可稳定复现的管理 UI correctness 回归,因此 P2 / 修后再合适当。

缓存状态机复核

  • 同 generation 并发请求和 force 正确 single-flight。
  • 构建期间 invalidate():成功完成的旧构建会写 validUntil=0,失效后的调用者等待它结束后再重建;失效不会在成功路径丢失。
  • 冷缓存失败继续抛错;暖缓存失败沿用旧快照并设 5s retry window,符合降级目标。
  • 一个非阻塞边界:若“构建中 invalidate + 该构建失败”,catch 会把旧快照重新标成 5s 有效,普通失效后调用也会先吃这 5s stale window。它是 stale-on-error 策略的自然结果,不另升 finding;建议补测试/注释明确该语义。

compact / refresh 安全复核

  • compactGroupsMatrix() 从空对象按白名单只构造 chatId/name/avatar,是匿名 redactGroupsForPublic() 已允许字段的严格子集,不会把 memberBots/hasRole/oncallChat/ownerId/error 带出去。
  • /api/groups 仍先经过 dashboard auth decision;匿名只有 publicReadOnly 开启时可读。
  • 强刷条件是 authed && refresh === "1",匿名或 stale-token 的公开请求即使带 refresh=1 也只能走普通缓存,未找到绕过底层扇出的路径。

实际验证

  • pnpm build:通过
  • pnpm test:740 files passed / 1 skipped;11389 tests passed / 6 skipped
  • 相关聚焦测试(snapshot、group actions、auth、public redaction):89 tests 全绿
  • 额外手工状态机脚本覆盖 invalidate-during-build、same-generation force、invalidate + failed refresh:行为与上述结论一致
  • git diff --check:通过

影响面:改动位于中央 dashboard 聚合/展示层,所有 CLI、PTY/Tmux、话题/群/adopt 会话共享该读模型;未触及 CLI adapter、后端、持久化或 SSE 写路径。未做 live daemon 部署(本轮只复审、无代码修改)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant