iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Software Development

遠古聖遺物改造工程:遺留系統全面重構實務指南系列 第 26

[Day 26] 如何減輕服務壓力?需要快取嗎?

  • 分享至 

  • xImage
  •  

如何減輕服務壓力?需要快取嗎?

前一章已透過效能測試找出目標系統的容量、瓶頸與失敗方式。接下來要根據測試結果減少造成瓶頸的工作,再用相同條件確認調整是否有效。快取(Cache)只是其中一種方法,適合重複讀取或重複運算,不會自動改善所有效能問題。

導入快取後,同一項資料會同時存在原始來源與暫存副本。讀取速度可能提高,系統也要開始處理副本過期、更新失效、容量限制及快取故障。只有節省的處理成本大於新增的複雜度,快取才是合適的選擇。

先確認要減少哪一種工作

壓力測試應該指出哪一段先超過門檻,以及等待時間、處理量與錯誤如何變化。只有看見整體回應變慢,還不足以決定加入快取。可以先按照瓶頸採取直接處理方式,再判斷重複工作是否值得保存結果。

測試觀察 要確認的原因 優先處理方式 快取可能提供的幫助
相同輸入反覆讀取同一內容 來源存取成本高,且短時間內結果相同 改善存取方式、縮小讀取範圍及合併重複讀取 重複使用先前結果,減少來源存取次數
相同輸入反覆執行昂貴運算 演算法、轉換或輸出產生耗時 改善演算法、移除重複步驟及預先計算 保存可重用的運算結果
相依項目回應緩慢或具有呼叫限制 每次操作都等待相同外部結果 合併呼叫、設定逾時及調整互動方式 在允許過期的期間內減少呼叫
寫入衝突或狀態更新排隊 多項工作競爭同一更新範圍 縮小一致性範圍、限制同時工作及調整流程 一般讀取快取通常無法消除寫入瓶頸
工作佇列、連線或執行單元已飽和 進入量超過可處理量,或重試放大負載 加入容量限制、反壓(Backpressure)、受控拒絕或調整部署容量 只有快取能實際減少該項工作時才有幫助
單次操作本身持續變慢 程式路徑、資料規模或相依狀態改變 先分析該次操作的各階段時間 缺少重複使用時,快取只會增加一次查找

如果問題來自缺少索引、無上限的資料讀取、未釋放的相依項目或持續重試,應該先修正直接原因。把昂貴且錯誤的工作結果放入快取,只會暫時降低它的發生次數,並且讓錯誤結果更難清除。

判斷內容是否適合快取

快取最適合「經常重複使用、產生成本高、變動頻率較低,而且允許短暫不是最新狀態」的內容。評估時應該以每一類資料或運算結果為單位,不能用「整個系統需要快取」取代個別判斷。

適合加入候選清單的內容通常符合下列條件:

  • 相同輸入會在短時間內產生相同結果,而且測試紀錄顯示它會被重複使用。
  • 讀取來源、呼叫相依項目或重新運算的成本,明顯高於查找及解析快取的成本。
  • 已核准需求允許結果在限定時間內不是最新狀態。
  • 可以完整描述影響結果的輸入,並且能建立不會互相混用的快取鍵。
  • 原始內容更新時,可以找出需要刪除、更新或停止使用的快取項目。
  • 快取遺失後,系統仍能從原始來源重新建立內容。

下列情況通常不適合直接快取:

  • 每次讀取都必須取得當下最新狀態,短暫過期也可能產生錯誤決策。
  • 內容更新速度接近或高於讀取速度,失效與重新載入的成本可能大於節省的工作。
  • 輸入組合幾乎不會重複,造成大量項目只寫入一次就到期。
  • 結果依照未納入快取鍵的狀態而改變,例如時間、語系、功能設定或已確認的存取範圍。
  • 單一結果過大,傳輸、序列化及保存成本抵銷原本的效能改善。
  • 原始來源已經足夠快,快取查找只增加額外路徑與故障點。

命中率不能在導入前直接假設。可以先從執行紀錄計算相同輸入的重複比例,再以代表性資料完成小型概念驗證(Proof of Concept, POC)。如果不同輸入幾乎各自只出現一次,就算單次來源讀取很慢,快取也未必能減輕壓力。

選擇最接近重複工作的快取位置

快取放得越接近工作入口,越可能省下完整處理路徑,但能安全共用的內容也越受限制。選擇位置前,要先確認系統是否具有相對應的互動方式與部署構成。

快取位置 適合情況 可以減少的工作 主要限制
用戶端私有快取 系統使用 HTTP,而且內容只供同一用戶端重用 傳輸及系統端的完整處理 無法由系統端集中控制所有副本,必須正確設定有效時間與重新驗證方式
HTTP 共用快取或反向代理 多項 HTTP 要求可以安全共用相同回應 進入系統後的路由、讀取、運算及輸出產生 個人化或依請求條件變化的內容必須正確隔離,失效方式也受所選工具影響
處理程序內的本機快取 同一執行單位會重複使用內容,而且各副本短暫不同步仍可接受 跨程序查找及原始來源存取 多個執行單位會各自載入副本,容量、命中率與失效狀態也各自獨立
共用分散式快取 多個執行單位需要共用結果、集中設定到期或共同失效 原始來源存取與重複運算 每次查找仍有通訊及序列化成本,並新增需要部署、保護及監控的相依項目
多層快取 已證明少數熱門內容需要本機速度,同時又要共用第二層結果 本機層減少共用快取查找,共用層減少原始來源工作 兩層都要處理容量、到期與失效,整體一致性及故障情境更複雜

如果系統使用 HTTP,應該先確認協定本身的快取語意。MDN 的 HTTP 快取文件區分私有快取與共用快取,並說明 Cache-ControlVary 及條件式要求的用途。Cache-Control: private 限制回應只能由私有快取保存,no-store 才是禁止保存。no-cache 代表重用前必須向來源重新驗證,不等於完全不保存。

同一網址如果會因語系、內容編碼或其他請求資訊產生不同回應,就要用 Vary 表達影響結果的標頭。變化維度過多會讓快取鍵幾乎無法重用。這時應該重新檢查快取位置與內容範圍,不能無限制地把所有請求資訊加入鍵值。

是否需要 Redis

Redis 可以作為共用分散式快取,但「需要快取」不等於「需要 Redis」。如果目標系統只有一個執行單位、本機快取已能涵蓋重複工作,而且短暫遺失內容可以重新建立,先加入外部快取服務可能沒有足夠效益。

出現下列需求時,可以把 Redis 納入概念驗證:

  • 多個執行單位需要讀取相同快取內容,不能接受每個單位各自暖機。
  • 更新後需要集中刪除快取項目,或要統一管理每個鍵的存活時間(Time to Live, TTL)。
  • 需要設定明確容量上限與淘汰策略,避免快取內容持續成長。
  • 需要利用原子操作協調同一鍵的載入,限制大量同時未命中造成的重複工作。
  • 代表性測試證明額外通訊與序列化成本仍低於原始來源工作。

概念驗證也要計入 Redis 無法使用、連線耗盡、項目遭淘汰、版本更新及監控維護的成本。Redis 在本章的責任是保存可重新建立的副本,原始來源仍保存正式狀態。Redis 的 Cache-Aside 文件也以來源更新後刪除快取鍵、讀取未命中時重新載入作為基本流程。

先定義快取契約再撰寫程式

快取契約要說明甚麼內容可以被重用、何時失效、失敗時如何處理,以及如何證明結果仍然正確。這些規則如果只散落在程式中,後續很難判斷某個鍵能否安全刪除或延長有效時間。

快取鍵要包含所有結果條件

快取鍵可以由功能名稱、資料版本、輸入條件及隔離範圍組成,例如:

report-summary:v3:locale=zh-TW:period=2026-08

v3 代表快取資料結構或計算規則版本。新版本無法讀取舊內容時,可以更換版本前綴,讓舊鍵自然到期。語系與期間會影響結果,因此必須納入鍵值。如果結果還會受到其他功能設定影響,也要納入相對應版本或條件。

鍵值不能包含密碼、權杖、私人金鑰或不必要的個人資料。記錄系統、錯誤訊息及監控工具經常會收集快取鍵,把敏感內容放入鍵值會擴大暴露範圍。如果內容會因身分或存取範圍而不同,應該使用無法直接還原敏感內容的穩定識別方式加以隔離,並且保留原有的存取檢查。

到期與淘汰要分開設計

存活時間限制一個項目可以重用多久,應該根據可接受的過期時間、更新頻率及重新建立成本決定。所有項目使用相同的任意時間,可能讓重要內容過期太久,也可能讓穩定內容頻繁重新載入。

淘汰則是在容量達到限制時選擇先移除哪些項目。項目尚未到期,也可能因容量不足而遭淘汰。Redis 的鍵值淘汰文件提供 noevictionallkeys-lruallkeys-lfu 與只處理具有到期時間之鍵值的策略。選擇時要按照實際存取分布測試,不能把策略名稱當成適用結論。

容量規劃至少要記錄項目數量、平均與較大項目的序列化大小、預期熱門資料範圍及容量上限。到期時間與容量淘汰都只能移除副本,不能用來刪除唯一資料。

快取內容要能辨識版本與損壞

保存複合內容時,要定義序列化格式、資料結構版本與必要欄位。讀取到未知版本、解析失敗或缺少欄位時,應該將它視為未命中並移除損壞項目,再從原始來源重建。不能把解析錯誤直接當成正常空值,否則功能結果會在快取啟用後改變。

選擇載入與失效方式

旁路快取(Cache-Aside)讓系統程式自行管理快取與原始來源,是常見且容易逐項導入的方式:

  1. 先使用完整快取鍵查找內容。
  2. 命中有效內容時,直接回傳快取副本。
  3. 未命中時,從原始來源讀取並驗證結果。
  4. 以限定存活時間寫入快取,再回傳相同結果。
  5. 原始內容更新成功後,刪除對應快取鍵,讓下一次讀取重新載入。

到期時間是避免副本無限期過期的最後限制,不能取代更新時的主動失效。如果需求只允許一分鐘的過期狀態,設定一分鐘存活時間仍不保證更新後立刻一致。需要立即反映更新時,應該在更新流程中刪除或更新快取,並驗證失效失敗時的處理方式。

一項更新可能影響多個彙整結果。此時要記錄來源內容與快取鍵或標籤之間的關係,不能只刪除最明顯的一個鍵。受影響範圍難以正確列出時,可以使用較粗的版本前綴讓整組內容失效,代價是下一次讀取要重新建立更多項目。

更新與未命中載入同時發生時,也可能出現競爭:讀取流程先取得舊內容,更新流程完成並刪除快取,先前的讀取流程卻在刪除後把舊內容寫回。風險超過容許範圍時,應該使用來源版本進行條件式寫入、讓鍵值包含資料修訂版本,或協調同一鍵的載入與失效順序,並以測試重現這項競爭。

避免未命中放大原始來源壓力

快取正常時減少的工作,可能在內容到期或快取故障時一次回到原始來源。設計時要用冷快取、同時到期與故障情境確認來源仍受保護。

情況 造成的壓力 處理方式
大量要求查找不存在的內容 每次都未命中並重複讀取原始來源 先驗證輸入。允許短暫過期時,可以暫存「不存在」的結果,並使用較短存活時間
熱門鍵到期後同時被讀取 多項工作同時重建相同內容,形成大量同時未命中(Cache Stampede) 合併同一鍵的載入工作,只允許一項工作重建,其餘等待同一結果。等待及鎖定都要有逾時
大量鍵在相同時間到期 原始來源在短時間接收所有重新載入工作 按照允許範圍加入到期時間差異,分批預先載入,並限制重新建立的同時工作量
少數鍵承受大部分讀取 單一快取分割或連線路徑仍可能成為瓶頸 個別觀察熱門鍵,評估本機第一層、結果拆分或受控複本是否能改善,而且不破壞失效規則
快取整體無法使用 所有讀取直接回到原始來源 設定快速失敗、限制降級時的同時讀取,並在來源容量不足時受控拒絕或提供已核准的降級結果

等待舊值可以作為某些讀取的降級方式,但前提是需求明確允許。對必須即時一致或影響重要狀態變更的內容,不能為了維持回應速度就回傳已過期結果。

把快取故障納入正常設計

快取是效能元件,不應在沒有明確決策的情況下成為功能成立的必要條件。每一類快取內容都要定義故障時採取哪一種結果:

  • 直接讀取原始來源,但限制逾時、同時工作與重試次數,避免降級造成來源過載。
  • 在需求允許的期限內暫時使用過期副本,並清楚記錄降級狀態。
  • 對不能使用過期結果的功能快速失敗,回傳可辨識結果,不把錯誤資料當成成功。
  • 對刪除失敗的鍵保留重試與告警,並使用有限存活時間限制影響。
  • 對內容格式不相容或損壞的項目視為未命中,不能持續重試解析相同內容。

如果快取失效就會使原始來源超過容量,單純「發生錯誤時繞過快取」並不安全。降級路徑要與容量限制、反壓及受控拒絕一起測試。恢復時也要限制重新載入速度,避免所有項目同時暖機形成第二次尖峰。

快取內容仍要遵守原始資料的保護要求。除非已有明確需求與保護措施,不應保存密碼、權杖、私人金鑰或不必要的個人資料。如果結果包含已確認的存取限制,快取命中不能略過原本的檢查,清除與保存期限也要符合原始內容的處理規則。

用相同測試證明快取有效

導入前要先寫下改善假設,例如「將重複彙整結果保存五分鐘,可讓來源讀取次數降低 70%,同時維持既有功能結果」。完成後使用與前一章相同的環境、資料、操作比例、負載階段及門檻,比較未啟用與啟用快取的結果。

至少要觀察下列指標:

  • 記錄命中、未命中、到期、主動失效及容量淘汰的次數與比例。
  • 分開量測快取命中、快取未命中與原始來源讀取的回應時間。
  • 比較原始來源的讀取或運算次數、等待時間、錯誤與容量狀態。
  • 比較系統的回應時間百分位、吞吐量、錯誤比例、積壓與恢復時間。
  • 記錄快取查找、寫入、刪除、解析與連線失敗,不能只計算成功命中。
  • 檢查更新到快取失效的時間,以及讀到過期內容的次數與最長期間。
  • 依功能情境與資料類型分開計算,避免少數高命中且低價值的內容掩蓋真正瓶頸。

功能測試要涵蓋命中、未命中、到期、來源更新、同時未命中、容量淘汰、快取故障與資料版本變更。效能測試則要分開保存冷快取與暖快取結果。只測量全部預先載入後的最佳情況,無法證明部署、故障恢復或內容更新期間仍符合門檻。

高命中率不等於改善成功。如果命中內容本來就很便宜,或序列化與通訊成本過高,來源壓力與整體回應可能沒有明顯改善。相反地,少量但成本極高的工作即使命中比例不高,也可能值得快取。最終決定要同時看功能正確性、節省的來源工作、整體容量與新增維護成本。

完成快取設計的檢查

  • 是否已用效能測試指出要減少的工作,而非只根據整體回應時間決定加入快取?
  • 每一類候選內容是否具有足夠重複率、明確產生成本及可接受的過期時間?
  • 是否已比較直接改善、用戶端、HTTP 共用、本機與分散式快取等可用方式?
  • 如果選擇 Redis,概念驗證是否證明共用能力的效益高於額外通訊與維護成本?
  • 快取鍵是否包含所有影響結果的條件,而且沒有敏感內容?
  • 是否已分開定義存活時間、容量上限、淘汰策略、資料版本與主動失效方式?
  • 原始內容更新時,是否能找出並清除所有受影響的快取項目?
  • 是否已處理不存在內容、熱門鍵到期、大量同時到期及快取整體故障?
  • 降級路徑是否具有逾時、反壓及容量限制,不會把全部壓力轉回原始來源?
  • 功能與效能測試是否涵蓋冷快取、暖快取、失效、競爭、故障及版本變更?
  • 是否以相同負載比較調整前後的功能結果、來源工作量、回應時間、處理量與錯誤?
  • 是否記錄重新評估條件,讓資料規模、使用方式或部署構成改變後能重新驗證?

重點整理

  • 減輕服務壓力要先找出真正瓶頸。快取只適合能被重複使用,而且產生成本高的讀取或運算結果。
  • 候選內容必須具有可接受的過期範圍、完整快取鍵及可執行的失效方式,不能只根據讀取頻率判斷。
  • 快取位置要按照系統實際互動與部署構成選擇。越靠近工作入口,可能省下越多處理,也越需要確認內容能否安全共用。
  • Redis 適合多個執行單位需要共用內容、集中管理到期與協調載入的情境,但新增的相依與維護成本也要納入概念驗證。
  • 存活時間限制副本可以過期多久,容量淘汰則決定空間不足時移除哪些項目,兩者不能互相取代。
  • Cache-Aside 要在未命中時從原始來源載入,並在來源更新後主動失效。更新與載入同時發生的競爭也要納入測試。
  • 熱門鍵同時未命中、大量鍵一起到期或快取故障,都可能把壓力一次轉回原始來源,因此降級路徑也需要容量限制與反壓。
  • 快取是否有效要以相同功能與效能測試比較。命中率只是其中一項指標,還要確認來源工作量、回應時間、處理量、錯誤及資料正確性。

上一篇
[Day 25] 系統能承受多少負載?應該如何進行壓力測試?
下一篇
[Day 27] 如何管理專案版本?
系列文
遠古聖遺物改造工程:遺留系統全面重構實務指南30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言