頁面載入太慢怎麼辦?先給舊資料,背景再更新
蒸餾精華第 2 段講快取策略。我追問了「舊資料是誰的」和「能不能用 Playwright 預熱快取」,搞懂了四個層次的快取設計。
竇加《舞蹈課》(The Dance Class),1874 年。竇加(Edgar Degas)以偏離中心的構圖捕捉芭蕾排練室的真實瞬間,舞者們在角落練習、休息、整理衣裙,編舞大師朱爾・佩羅持杖指導。此畫融合印象派的光影觀察與古典素描的精準,是竇加芭蕾系列中最著名的作品之一。現藏於紐約大都會藝術博物館。
使用者點開你的頁面,等了三秒還是空白,然後關掉走人。問題不是你的資料不存在,而是你堅持等「最新的」才顯示。其實你手上有一份舊資料,雖然過期了,但比空白好一萬倍。
Stale-While-Revalidate 快取策略
核心觀念只有一句話:過期的資料 > 空白的等待。
使用者要資料
│
├─ 有快取(即使過期)→ 立刻回傳,畫面秒出
│ 同時背景去抓最新的 → 存起來
│
└─ 沒快取 → 同步抓取(只有第一次要等)
快取分三層,越上面越快:
| 層級 | 速度 | 特性 |
|---|---|---|
| Memory(記憶體) | 最快 | 程式還在跑就有,重啟就消失 |
| File(檔案) | 快 | 程式重啟還在,換機器就沒了 |
| Network(網路) | 最慢 | 真的去打 API,但拿到最新的 |
四個層次的快取設計
追問「舊資料是誰的」和「第一次能不能也不等」之後,整理出完整的演進路線:
| 策略 | 第一個人要等嗎 | 說明 |
|---|---|---|
| 沒快取 | 每次都等 | 最簡單,最慢 |
| Stale-While-Revalidate | 第一次要等 | 之後都秒開 |
| 伺服器端共享快取 | 最最最第一次要等 | 所有人共享同一份 |
| + 快取預熱 | 沒人需要等 | 部署時先跑腳本填好快取 |
我的追問與發現
「所有人看到同一份舊資料?還是各自看各自的?」
看快取放哪裡。放用戶端(瀏覽器、本機),每個人第一次都要等,各存各的。放伺服器端(Cloudflare KV 之類),所有人共享同一份快取,只有最最最第一次要等。
「伺服器端的『最最最第一次』,我不能先用腳本創造快取嗎?」
可以,這叫快取預熱(Cache Warming)。部署完成後、開放流量前,跑一個腳本主動打 API 把結果寫進快取。不需要 Playwright 那麼重的工具——除非你的快取需要經過瀏覽器渲染(例如截圖),否則一個 fetch 腳本就夠了。
你可以怎麼用
- 找出你的網站或工具裡「每次都要等 API 回應」的頁面
- 加一層檔案快取:第一次抓到的結果存下來,下次直接用,背景更新
- 如果是多人使用的服務,把快取從用戶端搬到伺服器端(Cloudflare KV、Redis)
- 部署流程加一步「預熱」:部署完先跑腳本填快取,再開放流量
延伸連結
- API 一直掛怎麼辦?三層容錯讓使用者無感 — 快取解決「太慢」,那 API 直接掛掉呢?
- 該綁住 AI 還是放飛它?看錯誤代價決定自由度 — 快取是對「慢」的容忍,狀態機是對「不可預測」的控制
一句話帶走
過期的資料比空白的等待好一萬倍——先給舊的,背景再更新。
📚 完整學習對話紀錄(想看完整脈絡可展開)
原文(蒸餾精華 第 2 段)
讓 CLI/UI 感覺即時的快取策略:
Memory(最快) → File(忽略過期) → Network(最慢)
getModelsInstant():
有快取 → 立即回傳(即使過期)→ 背景靜默更新
沒快取 → 同步抓取(首次使用)
網路失敗 → 靜默忽略,用舊資料
Claude 的白話解釋
使用者要資料時,先把舊的立刻給他看,背景再悄悄去抓新的。
使用者要資料
↓
有快取(即使過期了)→ 先回傳舊的,畫面秒出
↓ 同時
背景偷偷去 API 抓最新的 → 存起來,下次就是新的了
核心觀念:過期的資料 > 空白的等待。
三層快取的優先順序:
| 層級 | 速度 | 說明 |
|---|---|---|
| Memory(記憶體) | 最快 | 程式還在跑就有 |
| File(檔案) | 快 | 程式重啟還在 |
| Network(網路) | 最慢 | 真的去打 API |
跟第 1 段的關係:第 1 段處理「API 掛了怎麼辦」,第 2 段處理「API 太慢怎麼辦」。一個管可靠性,一個管速度感,互補的。
我的提問:所有人看到同一份舊資料?還是各自看各自的?
這段我想了很久。到底對每一個真實用戶來說:
- 是大家一打開時共同讀取到一個歷史舊畫面,一有更新就換掉?
- 還是每個人第一次都要等,第二次才看到自己上回的快取?
Claude 的回答:看快取存在哪裡
兩種都對,取決於快取放的位置:
快取在用戶端(各存各的)
用戶 A 第一次來 → 沒快取 → 等待 API → 存到 A 的快取
用戶 A 第二次來 → 讀 A 自己的快取(秒開)→ 背景更新
用戶 B 第一次來 → 沒快取 → 也要等
用戶 B 第二次來 → 讀 B 自己的快取(秒開)→ 背景更新
蒸餾裡的做法是 CLI 工具,快取在每個人自己電腦上,不共享。
快取在伺服器端(所有人共享)
伺服器每隔一段時間更新快取
↓
用戶 A 第一次來 → 讀伺服器快取(秒開)→ 沒人需要等
用戶 B 第一次來 → 讀同一份快取(也秒開)
如果是網站服務(放在 Cloudflare KV 之類的),就能做到所有人都看同一份快取。
| 快取放哪 | 第一個人要等嗎 | 大家共享嗎 |
|---|---|---|
| 用戶端(本機) | 每人第一次都要等 | 不共享,各存各的 |
| 伺服器端(共享) | 只有最最最第一次要等 | 共享,大家看同一份 |
核心精神不變:寧可先給舊的,也不要讓人乾等。差別只在快取放的位置決定了「誰的舊」。
我又追問:伺服器端的「最最最第一次」,我不能用 AI 跑 Playwright 先創造快取嗎?
既然可以自動化,為什麼要讓任何人等?
Claude 的回答:可以,這叫快取預熱
完全可以。這就叫快取預熱(Cache Warming):服務上線前,先用腳本跑一遍,把快取預先填好。連「最最最第一次」都不用等。
部署流程:
1. 程式碼部署完成
2. 跑預熱腳本 → 主動打一遍 API → 把結果寫進快取
3. 開放流量
→ 所有用戶從第一秒起都有快取可讀,零等待
用 Playwright 可以,但其實不需要那麼重。Playwright 是模擬瀏覽器,適合測 UI。預熱快取只需要打 API 拿資料,一個簡單的 fetch 腳本就夠了。除非你的快取內容需要經過瀏覽器渲染才能產生(例如截圖、預覽圖),那 Playwright 才是對的工具。
完整的圖變成:
| 策略 | 第一個人要等嗎 |
|---|---|
| 沒快取 | 每次都等 |
| Stale-While-Revalidate | 第一次要等 |
| 伺服器端共享快取 | 最最最第一次要等 |
| + 預熱 | 沒人需要等 |
我學到的
一個快取策略,因為追問了「舊資料是誰的」和「能不能預熱」,從單純的技術知識變成了四個層次的設計選擇。最大的收穫是:技術方案不是固定的,你的追問決定了它能走多遠。