Docker Build and Push / build-image (push) Successful in 1m26s
一、结论订正 (第二次, 前两次均被测量方式误导) 初次把 ~14s 延迟归因于 nginx 未关 proxy_buffering —— 实测被推翻: 加了 proxy_buffering off / proxy_read_timeout 1h 并热重载, nginx -T 确认生效, 延迟毫无变化。 (该配置已回滚: advanced_config 清空、生效 conf 还原、nginx 已重载。保留它反而会让 OTA 大包 下载失去缓冲保护, 属净负收益。) 随后把 ~15-30s 延迟归因于客户端 db.watch/渲染滞后 —— 同样被推翻: 错误在于测量方法, 用 CDP 反复读 document.body.innerText (长列表页开销大) 去轮询, 且把 ssh + docker exec 的 ~6s 开销算进了延迟。 改为**页面内自计时**后 (50ms 轮询本地库 + MutationObserver 观察 DOM, 各自记录 performance.now), 一次干净测量的结果: - psql 命令总耗时 (ssh + docker exec + UPDATE): 2138ms - 数据出现在客户端本地库: 2251ms (≈ 命令返回后 113ms) - 界面更新: 2580ms - 同步数据到达 -> 渲染完成: 329ms 即端到端约 1~2s, 其中 UI 渲染只占 ~0.33s, useDbSignal 的表级 watch 工作正常, 无需改用 useQuery。服务端侧的 powersync 日志也一致 (checkpoint_complete 与改库同一秒, stream_ms 达 42 分钟、close_reason: client closing stream, 长连接稳定)。 二、补回滚开关 文档验收标准里的"回滚开关"此前并未实现。useDbSignal 现在读取 localStorage['iboard_realtime'] === '0' 时完全不订阅, 退回手动刷新, 用于线上出问题时的快速止损 (重启应用生效)。 三、文档 §7.0 改为"端到端约 1~2s (达标)", 写入可复现的页面内自计时测量方法, 并留档两次被推翻的结论及各自错因, 避免后人重复踩坑。§6.2 验收标准同步更新。
71 lines
2.6 KiB
JavaScript
71 lines
2.6 KiB
JavaScript
// 本地库变更订阅 (任务模块即时同步, 见 docs/V0.2/离线编辑模块/即时同步方案.md)
|
|
//
|
|
// 作用: 当指定表在本地 SQLite 里发生变化时回调 —— 变化可能来自
|
|
// 1) 本端写入 (乐观更新之后)
|
|
// 2) PowerSync 下行推送 (其它端 / 服务端改动)
|
|
// 两者最终都落到本地库, 所以订阅本地库即可覆盖跨端场景, 且离线时同样有效。
|
|
//
|
|
// 为什么不用手写"信号 SQL"求和: SDK 的 watch 已支持按表名过滤 (tables)
|
|
// + 节流 (throttleMs), 由 SDK 判断是否真的变化, 比自己做 count/max 汇总可靠。
|
|
//
|
|
// 用法:
|
|
// useDbSignal(['Task', 'BoardColumn'], () => load())
|
|
// useDbSignal(TASK_TABLES, onChange, { throttleMs: 300, enabled: someFlag })
|
|
import { useEffect, useRef } from 'react'
|
|
import { getPowersyncDb } from './db.js'
|
|
|
|
// 无意义的占位查询: 只借 watch 的"变更通知"能力, 不关心结果
|
|
const SIGNAL_SQL = 'SELECT 1 AS s'
|
|
|
|
export default function useDbSignal(tables, onChange, options = {}) {
|
|
const { throttleMs = 250, enabled = true } = options
|
|
|
|
// 回调走 ref, 避免调用方每次渲染都重新订阅
|
|
const cbRef = useRef(onChange)
|
|
cbRef.current = onChange
|
|
|
|
// 表名数组每帧都是新引用, 用内容做依赖
|
|
const tablesKey = Array.isArray(tables) ? tables.join(',') : ''
|
|
|
|
useEffect(() => {
|
|
// 回滚开关: 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()
|
|
let disposed = false
|
|
// watch 订阅成功后会立刻给出一次当前结果, 而调用方挂载时已经主动 load 过,
|
|
// 这里跳过首次通知, 避免重复请求
|
|
let first = true
|
|
|
|
try {
|
|
db.watch(
|
|
SIGNAL_SQL,
|
|
[],
|
|
{
|
|
onResult: () => {
|
|
if (disposed) return
|
|
if (first) { first = false; return }
|
|
cbRef.current?.()
|
|
},
|
|
onError: (e) => {
|
|
if (!disposed) console.warn('[db-signal] watch 出错:', e)
|
|
},
|
|
},
|
|
{ tables: tablesKey.split(','), throttleMs, signal: ac.signal },
|
|
)
|
|
} catch (e) {
|
|
// 订阅失败不应影响页面主流程 (退化为"手动刷新")
|
|
console.warn('[db-signal] 订阅失败:', e)
|
|
}
|
|
|
|
return () => {
|
|
disposed = true
|
|
ac.abort()
|
|
}
|
|
}, [tablesKey, throttleMs, enabled])
|
|
}
|