Files
Endoleap/PERFORMANCE.md
T

8.4 KiB
Raw Blame History

画布打开与空闲性能检查(2026-11-06)

本次证据

用户提供的开发日志中,画布页面路由多数约 20ms;物料元数据读取约 200–1,646ms,完整画布 GET 出现 190ms、2.4s、5.1s、11.4s 等样本,PATCH 约 1.4–4.1s。路由渲染与远程数据读取是两个阶段,不能以路由 200 很快判断画布已经可用。

日志只有 Next application-code 总时间,包含代理请求上游、响应体传输及 JSON 处理,不能据此断言是数据库 SQL 或本机渲染卡顿。既有鉴权令牌没有读取,本轮没有对用户线上画布做测试写入或生成。

本次修改

  • 画布编辑器改为独立动态模块,路由客户端先启动元数据/快照读取,同时加载编辑器代码。元数据和快照继续并行读取,不引入第二段等待。
  • 成功的 Canvas GET 在同源代理直接流式转发,不在 Next 中先缓冲完整 JSON,再解析并重新编码。ETag、schema、HttpOnly 会话续期和 no-store 保留;登录兑换响应仍按原逻辑移除凭证。
  • Canvas 流式响应的 Server-Timing 分别报告上游响应头等待和代理准备时间。它们不包含后续完整响应体传输时间,不把 proxy_headers 当作整个请求完成时间。
  • 空闲 AI 任务发现改为每 30 秒;有任务或尚待合并结果时每 1.2 秒轮询。发起生成、取消、应用结果会立即唤醒;隐藏标签页暂停网络查询,切回立即检查。
  • 没有新事件/状态时不再替换 runs/activeEdges 状态,避免空闲轮询反复触发画布渲染。
  • 侧栏请求 include_subgraphs=true 并完成分页,以 parent_board_id 建立可展开目录。它读取物料摘要,不下载每个子画布快照。创建、解散和撤销关系变更后刷新目录。

验证方法与边界

  • 单元测试覆盖子图分页、层级、缺失父项、循环防护、搜索路径、当前子图祖先,以及元数据/快照同时开始读取。
  • DOM 回归使用实际目录组件和 AI hook,验证展开/收起、当前路径、空闲 30 秒调度、空响应无重渲染、隐藏暂停及可见恢复;保留原有生成、取消、跨图连线和事务回归。
  • API 冒烟让模拟上游发送快照开头后暂停,在收到代理首段后才释放剩余响应,以证明代理没有等待完整 JSON;同时检查版本、schema 和会话续期。
  • 生产构建在临时副本进行,不改动当前开发服务器产物。最终执行结果记录于 VALIDATION.md。

本轮没有真实登录会话的公网前后对照,也没有浏览器视觉/手感验收,因此不能给出“线上打开从 11.4 秒降至某个时间”的结论。若更新后仍等待数秒,应结合 Network 中 Server-Timing 的 upstream 值、响应体下载时间和体积进一步追踪公网链路/后端;前端减少重复工作不等于消除了上游延迟。

2026-11-06:传输中断与误保存实测

旧 15 秒 deadline 同时约束响应头和整个响应体。流式响应先发出 200 后,正文仍可能被该 deadline 中断;此前客户端把 JSON 读取失败吞成 null,随后误报 invalid_canvas_snapshot。已改为响应头 15 秒、正文总计 90 秒、单次无数据等待 30 秒,并保留客户端取消。读取中断和 JSON 格式错误分别报告,不制造空快照。

[canvas-transfer] 只记录 trace_id、资源路径、毫秒、字节数、chunk 数、结果/错误类型;不记录令牌、Cookie、节点内容或图片数据。first_chunk_ms 为收到响应头后到首段正文的时间;transfer_ms 为代理消费正文的时间,包含链路和下游背压,不能独自区分网络节点。

用户真实父画布样本(约 477,400 字节,466 KiB):

样本 响应头前 正文 合计 结果
e342dd5b 707ms 25.925s 26.632s 读到 364,538 字节后 ResponseAborted
000949da 632ms 32.119s 32.751s 完整 477,441 字节
e33e4a53 713ms 1.951s 2.663s 完整 477,385 字节
7b5e0787 956ms 15.407s 16.363s 读到 282,618 字节后 ResponseAborted
4f77aafc 657ms 22.591s 23.248s 完整 477,385 字节

同一画布大小基本不变,正文平均吞吐从约 14.5 KiB/s 到约 239 KiB/s 波动。慢样本大部分时间在正文传输,而非页面路由(17–39ms)或首响应前。ResponseAborted 表示连接/消费端被取消,不能等同于我们设置的 TimeoutError,也不能仅凭它判定用户主动点击取消。开发热更新可取消旧请求,但具体请求的取消来源尚未证实。

子画布只返回 727 字节,正文约 1ms;首响应等待约 0.27–1.46s。未携带凭据的健康检查三个新连接样本:DNS 1.6–1.9ms、TCP 累计约 190–197ms、TLS 累计约 465–1,454ms、首字节累计约 723–1,842ms。健康检查只能作小响应链路基线,不能代替登录后的快照或 SQL 测量。

为什么只看页面也会 PATCH

实际浏览器 [canvas-dirty] 抓到多次仅 viewport=true,nodes=[]、edges=false 的保存。原保存器把视角变化与内容变化一同比较,因此平移、缩放也上传整个约 466 KiB 快照。已改为内容指纹决定是否 PATCH;只改变视角时保存在独立本机键中,不上传,也不产生内容恢复冲突。节点/连线发生真实修改时仍按版本保存完整快照。初始化的无来源视口回调、微小浮点差异和非用户尺寸测量均不作为服务器内容修改。

新的 [canvas-save] 样本中,客户端请求体读取 1.5–7.5ms、JSON 编码 1.6–2.1ms、上传到上游并等待响应头 334–1,440ms、响应体读取解析 0.6–0.8ms;请求约 477,370 字节。中间阶段包含上传、后端排队/SQL/提交和返回链路,前端不能进一步给出各自占比。

尚需服务端对照

  • Core canvas GET 调用 lockMaterial,源码先对 workspace 行 FOR UPDATE,且事务延续到 reply 返回。这是可能的同空间排队点;当前慢请求首响应前不足 1 秒,不能将其当作 22–32 秒正文慢的已证实主因。
  • 需 ECS 本机与公网读取同一快照的分段对照,并记录数据库取数、JSON 编码、写响应耗时及出口/网关限速。Gitea 与 API 解析到不同 IP,不能把 Gitea 大文件测试冒充同服务器对照。
  • 上传源码仍支持将图片内嵌为 data URL,可能放大快照;该父画布的实际图片占比尚需只统计字节、不输出内容的检查。迁到 managed assets 可让结构与图片独立读取,但不得为此在打开页面时暗中发起修改。
  • 此次修复没有宣称公网传输速度已经稳定,也未修改 Core 或用户线上图片存储。

同步服务端对照:2026-11-06 13:23 UTC

父画布 trace 1462ea3c:13:23:04.973 → 13:23:21.652 UTC,首响应 219.8ms,正文 16,458.6ms,共 477,388 字节。服务端记录位于 /tmp/endoleap-canvas-diag-20261106T131903Z(只含计数、TCP 状态与聚合等待,不含 HTTP 正文/令牌)。

  • 公网 TCP RTT 约 172ms,累计重传从 1 增至 31,拥塞窗口最低 2 个报文段,发送队列最高 138,240 字节;包丢失/重传导致 CUBIC 反复收缩窗口,解释了长正文传输和吞吐波动。
  • 内部 Core→Traefik 链路出现接收窗口背压,但最后完成约 481KB 的应用响应;不是数据库在逐字节生成 16 秒的数据。
  • 对齐窗口的 PostgreSQL 聚合记录没有持续 Lock 等待;只捕获一次约 154ms 的活动。只读 EXPLAIN ANALYZE 的本机快照体积计算执行 6.23ms、规划 11.4ms,不能将几十秒归因于该 SQL。
  • 同期 eth0 出口每秒约 14–62KB,无宿主队列丢弃增量;这不足以判定具体哪一段运营商或云端限速产生丢包,未擅自更改共享反代/内核网络配置。
  • 数据库聚合确认父图含 9 个节点、2 条边、2 张内嵌图片。JSON 文本约 477,646 字节,图片 src 合计 474,073 字符,约占 99.3%。原先设计让两张图片阻塞整张画布的数据读取。

修复采用普通 personal SaaS 附件模式:一份原始附件、一份按需显示预览,快照仅引用附件。前端添加真实附件上传及显式旧图迁移;Core PR #15 提供单一 1024px 预览、权限检查和有界缓存。原图不替换,AI 继续取原始内容。图片独立加载后,结构读取不再受两张完整图片的传输时间阻塞。生产部署和指定画布迁移后的实际字节数/时间须重测后记录,不能把预计缩减当作已完成实测。