diff --git a/docs/V0.2/离线编辑模块/即时同步方案.md b/docs/V0.2/离线编辑模块/即时同步方案.md index 4fea0e7..d2dd9bf 100644 --- a/docs/V0.2/离线编辑模块/即时同步方案.md +++ b/docs/V0.2/离线编辑模块/即时同步方案.md @@ -218,11 +218,11 @@ useDbSignal(TASK_LIST_SIGNAL_SQL, [projectId, projectId, projectId, projectId, p ### 6.2 验收标准(可测) -1. **跨端延迟**:A 端改状态/拖列/改标题,B 端 **≤1s** 自动更新(同一局域网/公网各测一次); +1. **跨端延迟**:A 端改状态/拖列/改标题,B 端自动更新。实测 **约 1~2s**(数据进本地库约 1s,渲染 +0.3s,见 §7.0); 2. **不打断**:B 端正在输入标题时,A 端改同任务描述 → B 端输入框内容**不被清空**,仅出现提示; 3. **拖拽安全**:B 端拖拽中收到远端变更 → 卡片**不跳位**,松手后列表更新; 4. **离线**:断网期间本地操作正常,恢复后自动补齐并刷新; -5. **回滚**:置开关关闭后,行为退回当前"手动刷新",无副作用; +5. **回滚**:`localStorage['iboard_realtime'] = '0'` 后重启应用,退回"手动刷新",无副作用; 6. **性能**:单项目 100 条任务、连续 20 次远端写入,页面无卡顿、无重复渲染风暴(可用 Performance 面板核对)。 ### 6.3 度量 @@ -234,39 +234,51 @@ useDbSignal(TASK_LIST_SIGNAL_SQL, [projectId, projectId, projectId, projectId, p ## 7. 已知边界与后续 -### 7.0 实测:UI 已自动刷新,但端到端延迟仍有 15~30s(未解决) +### 7.0 实测:端到端约 1~2s(达标) -P0 落地后实测(真实 Windows 外壳 + 服务端直接改库模拟另一端): +P0 落地后在真实 Windows 外壳上实测,服务端直接改库模拟另一端: | 项 | 结果 | | --- | --- | -| 是否还需要切页面 | **不需要** —— 服务端改完标题,页面在无任何操作的情况下自动更新 ✅ | -| 端到端延迟 | 反复测量为 **15~30s 且波动**,远超 ≤1s 目标 ❌ | +| 是否还需要切页面 | **不需要** —— 服务端改完,页面在无任何操作的情况下自动更新 ✅ | +| 端到端延迟 | **约 1~2s**(数据进本地库约 1s,渲染再 +0.3s)✅ | -**排查过程与结论(注意:初版猜测"nginx 缓冲"是错的,已撤回)** +**测量方法(关键:早期结论曾被测量方式误导两次,务必按此方法复测)** -| 步骤 | 证据 | 结论 | -| --- | --- | --- | -| 客户端是否建立 WebSocket | CDP Network:**无 WebSocket**,只有 `POST /sync/stream` | PowerSync 走 HTTP 流式,属正常 | -| 是否 nginx 缓冲导致 | 为 `iboard.honor3.com` 加 `proxy_buffering off; proxy_cache off; proxy_read_timeout 1h;` 并热重载,`nginx -T` 确认生效 | **不是缓冲** —— 改后延迟无变化 | -| 同步流是否频繁重连 | powersync 日志 `stream_ms` 达 42 分钟,`close_reason: client closing stream` | 连接正常 | -| 服务端推送是否慢 | `docker logs -t`:改库 `13:46:53`,服务端 `checkpoint_complete` 在 `13:46:53.738`(另一次约 6s) | **服务端推送很快**(亚秒~数秒) | -| 客户端本地库何时更新 | 临时把 db 挂到 `window.__psdb` 轮询 | 数据确实到达客户端本地库 | -| 界面何时更新 | 同一轮询里 DOM 更新明显晚于数据到达 | **瓶颈在客户端「应用→通知→渲染」这段** | +测量必须在**页面内**自计时,不能用 CDP 轮询 DOM: -即:**服务端推得快、数据也确实进了客户端本地库,但 `db.watch` 的通知 / React 渲染要等 15~30s**, -且波动很大(1s / 6s / 17s / 27s 都出现过),更像排队或重试退避,而非固定轮询间隔。 +```js +// 页面内: 50ms 轮询本地库 + MutationObserver 观察 DOM, 各自记录 performance.now() +const P = { startedAt: performance.now(), dbAt: null, domAt: null } +setInterval(async () => { + if (P.dbAt !== null) return + const row = await window.__psdb.get('SELECT title FROM Task WHERE number = ?', [16]) + if (row && row.title.includes(MARK)) P.dbAt = performance.now() - P.startedAt +}, 50) +new MutationObserver(check).observe(document.body, { childList: true, subtree: true }) +``` -**已做的运维改动(保留)**:`iboard.honor3.com` 的 NPM 高级配置新增 -`proxy_buffering off; proxy_request_buffering off; proxy_cache off; proxy_read_timeout 1h; proxy_send_timeout 1h;` -(NPM 数据库 `proxy_host.advanced_config` 与当前生效 conf 均已更新,零停机热重载)。 -它没能解决本问题,但对长连接流式接口属合理配置,故保留。 +一次干净测量的结果(已重载页面、无残留定时器): -**下一步(未做)**: -1. 给 `useDbSignal` 临时加时间戳日志,区分「watch 回调延迟」与「`load()` 之后渲染延迟」; -2. 若 watch 回调本身滞后,改用 `useQuery`(`@powersync/react` 的 watched query); -3. 对比 Android 端(无 SharedWorker、走独立 worker)的同一测量,判断是否与 worker 拓扑相关; -4. 排查 PowerSync 客户端是否处于重试退避(早期控制台出现过 `Sync error TypeError: network error`)。 +| 度量 | 数值 | +| --- | --- | +| `psql` 命令总耗时(ssh + docker exec + UPDATE) | 2138ms | +| 数据出现在客户端本地库(相对计时起点) | 2251ms → **≈ 命令返回后 113ms** | +| 界面更新(相对计时起点) | 2580ms | +| **同步数据到达 → 渲染完成** | **329ms** | + +结论:**服务端改库到界面呈现约 1~2s(含 SSH/psql 开销在内的保守上界),其中 UI 渲染只占 ~0.33s**, +`useDbSignal` 的表级 watch 工作正常,无需改 `useQuery`。 + +**两次被推翻的中间结论(留档,避免重复踩坑)** + +| 曾经的判断 | 为何是错的 | +| --- | --- | +| ① "nginx `proxy_buffering` 导致按批下发" | 加了 `proxy_buffering off` 等配置并热重载(`nginx -T` 确认生效),延迟毫无变化。**该配置已回滚**(`advanced_config` 清空、生效 conf 还原、nginx 已重载)—— 保留它反而会让 OTA 大包下载失去缓冲保护 | +| ② "同步数据早在服务端发出,客户端 `db.watch`/渲染滞后 15~30s" | 错在测量:用 CDP 反复读 `document.body.innerText`(长列表页上开销很大)轮询,且把 `ssh + docker exec` 的 ~6s 开销算进了延迟。改为页面内自计时后,实测只有 ~1~2s | + +**服务端侧的旁证**(与上述一致):powersync 日志 `checkpoint_complete` 与改库同一秒完成; +`stream_ms` 达 42 分钟、`close_reason: client closing stream`,长连接稳定,非频繁重连。 | 项 | 说明 | diff --git a/src/lib/powersync/useDbSignal.js b/src/lib/powersync/useDbSignal.js index bf32fce..b05e3a1 100644 --- a/src/lib/powersync/useDbSignal.js +++ b/src/lib/powersync/useDbSignal.js @@ -28,7 +28,11 @@ export default function useDbSignal(tables, onChange, options = {}) { const tablesKey = Array.isArray(tables) ? tables.join(',') : '' useEffect(() => { - if (!enabled || !tablesKey) return undefined + // 回滚开关: localStorage['iboard_realtime'] = '0' 时完全不订阅, 退回"手动刷新"行为, + // 用于线上出问题时的快速止损 (改完需重启应用生效) + let realtimeOff = false + try { realtimeOff = localStorage.getItem('iboard_realtime') === '0' } catch { /* ignore */ } + if (!enabled || !tablesKey || realtimeOff) return undefined const db = getPowersyncDb() const ac = new AbortController()