107 lines
14 KiB
Markdown
107 lines
14 KiB
Markdown
# 画布打开与空闲性能检查(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 继续取原始内容。图片独立加载后,结构读取不再受两张完整图片的传输时间阻塞。生产部署及指定画布的迁移、重开实测见下方记录。
|
||
|
||
|
||
后端 PR #15 已由用户合并为 b2ae48e,分支/PR #936/#937、主线 #940、部署 #941 均成功。Endoleap main 570cd53 接入,776b665 补充迁移确认状态。
|
||
|
||
### 生产迁移与重开验收(2026-11-06,Asia/Shanghai)
|
||
|
||
用户明确点击“优化图片加载”后,父图版本 252 → 253。数据库 JSON 文本从 477,397 降至 3,708 字节(减少 99.22%);HTTP 紧凑响应为 3,471 字节,两种序列化口径不同。两张图片均不再内嵌,原始附件共 355,519 字节,其 SHA-256 与迁移前解码字节全部一致。8 个父图节点的 ID、几何及非图片引用内容哈希、边数组哈希全部一致。基线和验收摘要在服务器诊断目录 migration-baseline.json / migration-audit.json,仅含聚合和哈希。
|
||
|
||
- 22:27:35 的迁移保存:请求 3,456 字节,成功 200,耗时 992ms。
|
||
- 22:28:54 的成功重开,trace bb47dc97:首响应 218.3ms,正文 1.3ms,总计 219.6ms,3,471 字节。对照此前 trace 1462ea3c 的 16,678.4ms / 正文 16,458.6ms;属于两次实测,不代表稳定延迟保证。
|
||
- 子图读取 260.4ms;两张独立图片分别 36,958 / 125,154 字节、551.2 / 1,354.9ms,均完整完成,不再阻塞画布结构下载。这些不是浏览器绘制耗时。
|
||
- 22:28:49 另有一次 1,863.4ms 后、零正文的 ResponseAborted,随后出现新的页面初始化与成功请求。日志不能单独证明取消的具体原因,不将它描述为 15 秒超时或无错误。
|
||
- 重新打开后没有新增 canvas-save / canvas-dirty 日志,服务端仍为版本 253;原图与结构哈希再次一致。本次只读重开未触发 PATCH。
|
||
|
||
本次修复消除了内嵌图片对整个快照的阻塞、统一短截止造成的正文中断与视角变化触发全量保存。公网链路仍可能抖动;已确认 TCP 重传及拥塞窗口收缩,但未定位具体物理丢包跳点,也未更改共享反代或内核配置。不能声称网络丢包已被修复。
|
||
|
||
迁移验收后按用户要求删除临时开发功能:页面不再显示“优化图片加载”,前端不再包含其迁移执行入口、状态和 Base64 转附件工具。已上传的原图与画布引用保持不变,后续正常图片上传和预览继续使用附件接口。
|
||
|
||
## 跨分区拖放的串行等待
|
||
|
||
用户 22:35:17 / 22:35:20 操作分别先 PATCH 坐标(626.5 / 213.9ms),再 POST members(约 502 / 407ms),两次串行等待约 1.13 / 0.62 秒;前端 exclusive 全程显示整屏遮罩并禁止拖动。后端事务审计确认两笔成功操作,各涉及两画布,响应约 9.5 / 9.2KB,没有图片内嵌造成的大体积。历史记录不能拆分当时网络/SQL/锁等待耗时,不能把整段耗时归因于 SQL。
|
||
|
||
改动将 members 同步改为局部状态和串行事务队列;自动保存暂缓,其他元素本地编辑继续,成功响应与持久化基线做三方合并后再保存编辑。Core PR #16 增加源画布局部坐标的 positions 字段及 member_positions 能力,坐标和归属在一个事务保存,支持时省掉前置 PATCH。版本及幂等保护保留。DOM/Hook 延迟响应、丢失响应和版本冲突场景已验证;尚未将模拟的一次 POST 当作线上耗时改善。
|
||
|
||
Core PR #16 已由用户合并为 2207c85;push #946、PR #947、主线 #949、部署 #950 均成功。前端 a240507 已在 main。23:13:30(Asia/Shanghai)开始安排指定父图 icon 拖放的真实验收;需要刷新以重新检测能力,实测结果待记录核对后补充。
|
||
|
||
## 首次上传及能力协商复核
|
||
|
||
23:15–23:16 的真实日志中,新 JPEG 原图 393,301 字节、1280×2276,上传约 8.5 秒;其预览 125,545 字节,首响应等待 5,913.9ms,正文传输 748.1ms,总计 6,662ms。同期 profile/workspaces/小快照也出现约 5–7 秒等待首响应,不能将全部时间归因于图片处理。随后无认证公网健康探测也复现秒级等待:curl 约 1.73/4.24 秒;Node 新建连接 DNS 20ms、TCP 累计 200.4ms、TLS 累计 3,574.6ms、总计 4,808.3ms,同连接复用请求仅 275.4/187.1ms。ECS 内部健康请求约 120.35/13.92/1.81ms。该对照表明冷连接/公网路径存在明显成本,但不是历史每个请求的同期分段追踪,不能声称已定位全部 8.5 秒或修复公网丢包。
|
||
|
||
能力协商 2 秒截止与约 1,989ms 的服务端路由耗时接近,且真实三次移动前仍有仅 position 变化的 PATCH。现移除短截止并禁止把网络错误永久缓存为“不支持”;慢响应和失败预取回归均保持原子成员移动。后端三笔事务各移动 1 个节点,总数、非位置内容与版本递增均正确。
|
||
|
||
首次上传改为本地即时预览 + 后台原图上传,服务端确认后保存引用,远端预览加载完才切换显示。该改动消除空画布等待,不将即时显示误报为网络上传已完成或服务端延迟已消失。实际交互复测待用户操作后补充。
|
||
|
||
用户随后指出只读预览不等于已添加元素。已修正为选图时就创建可编辑的本地节点,后台上传仅补齐引用;位置、尺寸、名称、删除及撤销状态都以本地最新状态为准。未准备好的文件节点和关联边不传给 Core;其他编辑仍可保存。该修正不依赖公网延迟降低,也不将本地添加误报成后台文件已上传。
|