三年前通过在客户端使用 SQLite 数据库缓存数据,成功为 Mac 和 Windows 系统的 Notion 应用加速(https://www.notion.so/blog/fa...),原生移动应用也使用了此 SQLite 缓存。今年将同样的改进带给了通过网页浏览器访问 Notion 的用户,本文深入探讨了如何使用sqlite3 的 WebAssembly(WASM)实现来提高浏览器中的 Notion 性能。
- 使用 SQLite 使所有现代浏览器的页面导航时间提高了 20%,对于因互联网连接等外部因素导致 API 响应时间特别慢的用户,差异更为明显,如澳大利亚用户页面导航时间加快了 28%,中国用户加快了 31%,印度用户加快了 33%。
核心技术:OPFS 和 Web Workers
- WASM SQLite 库使用[Origin Private File System(OPFS)]在会话间持久化数据,只能在[Web Workers]中用于其持久层,Notion 与[Webpack]结合,可轻松加载 Web Worker 并设置其创建或加载 SQLite 数据库文件,使用[Comlink]库管理主线程和 Worker 之间的消息传递。
基于 SharedWorker 的方法
- 每个标签页有自己专用的 Web Worker 可写入 SQLite,但只有一个标签页可实际使用,SharedWorker 负责管理“活动标签页”,当活动标签页关闭时,SharedWorker 会选择新的活动标签页,通过打开无限打开的 Web Lock 检测关闭的标签页,每个 Web Worker 使用[OPFS SyncAccessHandle Pool VFS]实现访问 SQLite 数据库,可在所有主要浏览器上运行。
为什么简单方法行不通
之前尝试更直接的方式(每个标签页一个专用 Web Worker 写入 SQLite 数据库),发现两种 WASM SQLite 实现都不能满足需求:
- 障碍 1:跨源隔离:OPFS via sqlite3_vfs 需要站点“跨源隔离”,涉及设置一些安全头,Notion 依赖许多第三方脚本,实现全跨源隔离不现实,通过[Origin Trials]在 Chrome 和 Edge 浏览器上向部分用户发送此变体以获取性能数据。
- 障碍 2:损坏问题:向部分用户开启 OPFS via sqlite3_vfs 时,一些用户出现严重错误,如页面显示错误数据,经检查是 SQLite 数据库损坏,怀疑是并发问题导致,多个标签页同时写入同一文件,即使使用事务性方法仍存在问题,通过添加[Web Locks]等方法降低损坏率,但仍不放心将其用于生产流量。Notion 桌面应用和移动原生应用没有此问题,桌面应用只有一个父进程写入 SQLite,移动应用一次只能打开一个页面。
- 障碍 3:替代方案只能在一个标签页中运行:评估[OPFS SyncAccessHandle Pool VFS]变体,此变体不需要 SharedArrayBuffer 可在 Safari、Firefox 等浏览器上使用,但一次只能在一个标签页中运行,在后续标签页打开 SQLite 数据库时会抛出错误,不能满足所有标签页受益于缓存的需求。
- 最终构建了上述 SharedWorker 架构,兼容这两种 SQLite 变体,使用 OPFS via sqlite3_vfs 变体可避免损坏问题,使用 OPFS SyncAccessHandle Pool VFS 变体可通过 SharedWorker 使所有标签页有缓存,最终选择了 OPFS SyncAccessHandle Pool VFS 变体,因其不需要跨源隔离,可推广到更多浏览器。
减轻回归
最初向用户推送此改进时,注意到一些回归问题,包括加载时间变慢:
- 页面加载变慢,因为用户必须下载和处理 WASM SQLite 库,阻塞了页面加载过程,通过将加载库改为完全异步,确保不阻塞页面加载来解决,确定从 SQLite 加载初始页面的加速不如下载库的减速重要。
- 某些设备(如指向 Notion 的手机浏览器)在从一个 Notion 页面导航到另一个页面时,95 百分位时间变慢,在移动团队的调查中发现一些设备从磁盘读取数据非常慢,因此在导航点击的代码路径中重新实现了“竞争”两个异步请求(SQLite 和 API)的逻辑,使两个实验组的导航时间 95 百分位相等。
结论
- 在浏览器中为 Notion 实现 SQLite 的性能改进面临挑战,学到一些经验:OPFS 本身对并发处理不佳,开发者应注意并围绕此设计;Web Workers 和 SharedWorkers 有不同能力,必要时可结合使用;2024 年春季在复杂的 Web 应用中完全实现跨源隔离并不容易,尤其是使用第三方脚本时。使用浏览器的 SQLite 为用户缓存数据后,页面导航时间提高了 20%,未观察到其他指标回归和 SQLite 损坏问题,将成功归因于 SQLite 的官方 WASM 实现团队以及 Roy Hashimoto 提供的实验方法。对在 Notion 从事此类工作感兴趣可查看开放职位。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。