來自一萬筆項目 OPFS 保管箱的基準測試
排程中閱讀時間 7 分鐘 作者 NT²
大型本機保管箱不是靠一句口號證明的。它靠的是量測人們實際使用的路徑—— 解鎖後的第一頁、防抖搜尋、FTS 與篩選查詢——並誠實說明這些數字不代表什麼。
來自一萬筆項目 OPFS 保管箱的基準測試
主張很收斂:一個在大約一萬筆結構化項目下仍好用的保管箱,是你可以在裝置上量測的事——不是雲端替你證明的事。
我們在意的是解鎖後的第一頁延遲、使用者在搜尋框打字時的防抖路徑、FTS5 與一般篩選查詢之間的選擇,以及分頁列表視窗與全表灌入之間的記憶體差異。我們不在意發明一個「任何地方都永遠低於 X 毫秒」的單一數字,也拒絕把本機工程量測打扮成產品 SLA。
這篇文章是公開架構論述的後續:列表絕不能把閣樓載入 Svelte state、搜尋必須向 SQLite 提出正確的問題、保管箱檔案屬於 Origin Private File System,且 SQLite 工作必須離開 UI 執行緒。那些文章說明我們建了什麼。這一篇說明當 fixture 不再只是 demo 時,我們如何檢查這套堆疊是否仍然成立。
限制:行銷用的 FPS 是錯誤的儀器
密碼管理器與保管箱的效能敘事,常塌成三種失敗之一。
FPS 海報。 單一毫秒數字、沒有 fixture 描述、不分冷啟動與暖機,卻暗示每支手機、每個瀏覽器、每種保管箱大小都會對上截圖。那不是量測,是帶單位的氣氛。
雲端代理。 把 API 邊緣的延遲當成保管箱速度。對零知識、local-first 產品來說,這是類別錯誤。可選的同步可以搬運密文。它回答不了「在這台機器上解鎖後,到第一頁五十列列表列出現要多久」。閣樓在本機。有意思的路徑也在本機。
全表巧合。 幾百筆的 demo 保管箱,在 UI 把一切載入響應式 state 時「感覺很快」。到一萬筆時,同一習慣變成解鎖像匯入試算表。若你永遠只對小保管箱做基準,就永遠看不到架構失敗。
所以有用的問題不是抽象的「NT² 有多快?」,而是:在大型本機 fixture 上,哪些路徑仍與工作集成正比,哪些路徑會悄悄變成與保管箱大小成正比?
在變成數字問題之前,這先是方法問題。若還沒有凍結的公開實驗室報告,誠實能公開的是方法——我們量什麼、fixture 長什麼樣子,以及任何示意性時間不證明什麼。
設計:量測路徑,不是口號
我們把「一萬筆項目的 OPFS 保管箱」當成工程 fixture 形狀,而不是行銷徽章。
Fixture 形狀
我們在意的 fixture 大致如下:
- 每個保管箱的 SQLite 資料庫中約有一萬筆結構化資產項目。
- 資料庫檔案位於瀏覽器的 Origin Private File System(cooperative sync 存取),而不是以 IndexedDB 作為主要存放處。
- 項目是結構化欄位槽位——標題、類別、列表預覽、生命週期旗標——不是空白畫布文件。標題是列表中繼資料。Payload 在開啟前保持密封。
- 開發與工程量測有大型本機種子,讓保管箱能長過 demo 規模,而不必等個人多年歷史。種子的重點是形狀可重現,不是公開實驗室證書。
我們不主張每位使用者的保管箱都有一萬筆。我們主張的是:產品列表與搜尋路徑的設計,使得當保管箱達到這個數量級時,介面不必變成閣樓的第二份副本。
我們量什麼
四種量測比單一英雄指標更重要。
| 路徑 | 問題 | 為何重要 |
|---|---|---|
| 解鎖/篩選變更後的第一頁 | 到虛擬列表可用的第一頁薄列表列,要多久? | 解鎖與「開啟 Credentials」應大致付出一頁查詢的成本,而不是全表走訪。 |
| 搜尋防抖路徑 | 打字穩定後,到新一頁相符結果回來要多久? | 每個字元都不該觸發查詢;穩定輸入必須重新查詢 SQLite,而不是過濾巨大的用戶端快取。 |
| FTS 與篩選策略 | 自由文字是否走 FTS5 分支,純類別/生命週期篩選是否留在一般索引條件上? | 錯誤策略是無聲稅金:把 FTS 套在精確篩選上,或用記憶體內標題掃描做自由文字。 |
listRows 與全表的記憶體 | 常駐列表 state 是否與分頁視窗相關,還是隨保管箱大小成長? | 架構證明:分頁視窗對上全表灌入。 |
注意那張表缺了什麼。
我們不量「品牌動畫每秒幾幀」。我們不量邊緣 RTT 再稱之為保管箱速度。我們不解密每一份 payload,然後慶祝列表捲動有多快。列表列保持輕薄:識別碼、類別、標題、時間戳、可選預覽。開啟一筆紀錄是另一條路徑。
一次量測如何框定
一次有用的工程量測至少記錄:
- 瀏覽器與作業系統(以及 OPFS sync 存取是否可用——沒有它,保管箱檔案故事就不適用)。
- Worker 的冷啟動與暖機。 行程剛起來的第一次開啟要付 WASM/Worker 啟動成本。已解鎖工作階段內的後續分頁較暖。混在一起又不標註,是「47 ms」變成謊言的方式。
- 查詢類別。 無文字的第一頁;帶類別篩選的第一頁;走 FTS 的防抖自由文字;FTS 關閉的純篩選。
- 結果視窗。 預設分頁大小約五十列。總數來自 SQL。介面持有有上限的常駐視窗,不是整個閣樓的相符集合。
- 什麼保持密封。 若一次量測需要解密一萬份 payload,那就不是列表基準測試。
當我們引用本機工程量測的數量級時間時,它們是示意性的,不是服務水準協議。硬體不同。背景分頁不同。磁碟壓力不同。暖機 Worker 的中階筆電,不是冷啟動後的低階手機。任何撐不過這句話的數字,都不該成為產品主張。
flowchart TB
Fixture["OPFS SQLite 中約一萬筆項目"]
Paths["量測路徑"]
First["第一頁延遲"]
Debounce["防抖搜尋"]
Strategy["FTS 與篩選選擇"]
Memory["listRows 視窗對上全表"]
Caveats["硬體 · 瀏覽器 · 冷/暖 Worker"]
Fixture --> Paths
Paths --> First
Paths --> Debounce
Paths --> Strategy
Paths --> Memory
Paths --> Caveats
圖的重點就是整個主張:路徑進來,限制貼上。 沒有邊的單一漂浮毫秒不是基準測試。
取捨:誠實會讓故事比較不好講
公開方法而不是 FPS 海報,有真實的產品成本。
我們不能寄出一行吹噓。 「任何裝置都永遠低於 X 毫秒」更好發推。但只要有人在受限硬體上開冷啟動工作階段,或保管箱大小與附件中繼資料改變工作集,它就會立刻變成假的。選擇誠實,公開故事就會比較長:量測這些路徑;期待數量級行為;不要把本機 SQLite 與雲端 RTT 搞混。
我們接受數字會漂移。 瀏覽器版本會改變 OPFS 與 Worker 排程。WASM 載入成本會變。綱要成長會加欄位與索引。上季凍結的截圖不是合約。持續的工程量測,比刻在石頭上的部落格數字更重要。
我們接受有些讀者想要實驗室 PDF。 之後也許會公開更緊的實驗室筆記。在那之前,架構保證就是已用散文公開的那些:分頁列表 state、FTS 與篩選策略、保管箱檔案用 OPFS、SQLite 在 Web Worker。基準測試稽核這些選擇。它們不取代它們。
我們也接受開發者摩擦力。 把保管箱長到一萬筆是刻意的工作。種子工具存在,是為了讓工程師能操練大型 fixture 形狀。那不等於附釘死硬體與簽名報告的公開可重現套件。把兩者搞混,是方法論文變成假證書的方式。
我們換回的,是與架構對齊的回饋迴路。若第一頁延遲開始追蹤保管箱大小而不是分頁大小,列表路徑就退回全表習慣。若每個按鍵都出現搜尋卡頓,防抖或 Worker 排程就退步了。若純篩選手勢突然付出 FTS 成本,策略切換就退步了。那些是可行動的失敗。單一的行銷 FPS 數字不是。
還有第二個容易忽略的取捨:local-first 效能不是「裝置上無限 RAM」。 把閣樓留在 OPFS SQLite,是產品保持離線與私密的方式。只在 UI state 留視窗,是產品保持平靜的方式。基準測試的存在,是為了抓住有人「為了優化」又把閣樓拉回分頁的那一刻。
我們拒絕什麼
架構與量測共享同一組拒絕。
我們拒絕用行銷 FPS 取代路徑量測。 沒有 fixture、冷/暖標註與查詢類別的數字只是裝飾。
我們拒絕「任何地方都永遠低於 X 毫秒」。 該主張撐不過瀏覽器、硬體與工作階段狀態的變異。我們不會把它印成產品真相。
我們拒絕暗示雲端在替保管箱做基準測試。 邊緣延遲、同步批次時間、Durable Object 喚醒對可選的 Premium 同步有意思。它們不能證明一萬個標題的本機列表在離線時仍好用。
我們拒絕把全表灌入當成基準捷徑。 量「載入一切,再量捲動」證明的是錯誤架構。量測必須操練分頁的 listRows 與虛擬列表行為——否則量的是稻草人。
我們拒絕把「全部解密」當成列表速度。 開啟一筆項目可以解密一份 payload。捲動不行。
我們拒絕把無上限常駐視窗打扮成「正確的無限捲動」。 若視窗悄悄變成閣樓,記憶體基準終會供出介面早已知道的事。
我們拒絕公開我們沒有的精確度。 本機工程量測的數量級示意可以出現在工程討論中;它們不是 SLA。缺少凍結的公開實驗室報告,並不授權在 Markdown 裡發明一份。
這些拒絕讓「一萬筆仍然快」不會變成跑在堆疊前面的口號。
量測視窗,不是閣樓
大型保管箱要誠實,就得量測產品實際出貨的同一組邊界。
解鎖後的第一頁應看起來像對 OPFS SQLite 的一頁查詢,列表工作離開 UI 執行緒——而不是把閣樓歷史匯入 Svelte。防抖搜尋應重新詢問 SQLite:自由文字勝出時走 FTS5,類別與生命週期已夠時走一般篩選。常駐 state 應追蹤視窗。記憶體預設不應追蹤保管箱大小。
這與 local-first 系列已公開的堆疊一致:為什麼保管箱 SQLite 放在 OPFS 而不是 IndexedDB、SQLite 在 Web Worker、永遠不要把整個保管箱載入 Svelte state,以及 標題用 FTS5;篩選勝出時走資料表掃描。若要看該規模下的產品感受,見 一萬筆項目,仍然是保管箱 與 搜尋一萬個標題而不卡頓。
最後更新 2026-11-05
相關故事
- 標題用 FTS5;篩選勝出時走表掃描
閱讀時間 7 分鐘
- 結構描述 v60 與漸進式遷移
閱讀時間 7 分鐘
- 永遠不要把整個保管箱載入 Svelte state
閱讀時間 7 分鐘