很多客製化系統剛上線時跑得很順,但使用者一多、資料一累積,頁面就越來越慢,甚至在尖峰時段直接卡住。問題往往不在程式寫得不好,而是每一次請求都直接打到資料庫——同樣的查詢被重複執行成千上萬次,資料庫成了整個系統的瓶頸。這時候,「快取(Cache)」就是最有效、CP 值最高的解方。
快取的概念其實很單純:把「常被讀取、又不常變動」的資料,先存放在一個讀取速度極快的地方,下次有人要同樣的資料時,直接從這裡拿,不用再麻煩資料庫。就像便利商店把熱銷商品放在門口,而不是每次都跑回倉庫搬貨。
資料庫查詢通常要花上數十毫秒,從記憶體型快取讀取卻只要不到 1 毫秒。當熱門資料被快取命中(Cache Hit)的比例越高,資料庫的負擔就越輕,系統整體反應速度也越快。
Redis 是目前企業最常用的記憶體型資料庫,資料直接存在記憶體中,讀寫速度極快,常被拿來當作快取層。除了單純的鍵值(Key-Value)儲存,Redis 還支援字串、雜湊、列表、集合等多種資料結構,也能用來做排行榜、計數器、Session 管理、分散式鎖與訊息佇列,是客製化系統裡用途極廣的基礎建設。
快取不是「存進去」這麼簡單,關鍵在於「讀寫時資料怎麼同步」。以下是三種最常見的設計模式:
最常見也最直覺的做法。程式要讀資料時,先問快取有沒有;有就直接回傳(命中),沒有就去資料庫撈,撈完再寫回快取,下次就能命中。寫入或更新資料時,更新資料庫後把對應的快取刪除,讓下次讀取重新建立。優點是邏輯單純、容錯性好,缺點是第一次讀取一定會慢(Cache Miss),且要小心更新時的一致性。
每次寫入時,同時更新資料庫與快取,兩邊永遠保持一致。讀取時快取幾乎都會命中,資料一致性高,適合對正確性要求嚴格的場景。代價是寫入速度會稍微變慢,因為要同時寫兩個地方。
寫入時先更新快取就立刻回應,再由背景非同步批次寫回資料庫。寫入效能極佳,適合高頻寫入的場景(例如點讚數、瀏覽次數)。但風險是如果快取在寫回前當機,資料可能遺失,導入時要特別評估可靠性。
快取最怕的是「資料過期了卻還在用」。最基本的控制方式是設定 TTL(Time To Live),也就是每筆快取的存活時間,時間一到自動失效、重新從資料庫撈最新資料。TTL 要抓得剛好:設太短,快取命中率低、失去意義;設太長,使用者可能看到過時資料。實務上會依資料的「變動頻率」與「即時性需求」分級設定,例如商品價格設較短、分類選單設較長。
導入快取後,反而可能因為設計不當而出現新的問題,以下三個是最常見的:
大量查詢「根本不存在」的資料(例如惡意用不存在的 ID 攻擊),快取永遠不命中,每次都打到資料庫。解法是把「查無資料」這個結果也快取起來(存空值並設短 TTL),或用布隆過濾器(Bloom Filter)先擋掉不可能存在的查詢。
某一筆熱門資料的快取剛好過期,瞬間湧入的大量請求同時打到資料庫。解法是對重建快取的動作加上互斥鎖(只讓一個請求去重建、其他人稍等),或對熱點資料採用不過期策略並由背景定期更新。
大量快取在同一時間集體過期,資料庫瞬間被海量請求壓垮。解法是讓 TTL 加上隨機值、避免同時到期,並做好資料庫的限流與降級機制,必要時搭配多層快取。
不是所有資料都該進快取。最適合的是「讀多寫少、可容忍短暫不一致」的資料,例如商品目錄、文章內容、設定參數、熱門排行榜。反之,像金流交易、庫存扣減這種對即時性與正確性要求極高的資料,就要審慎評估,或搭配嚴格的一致性設計。
導入快取前,建議先用監控工具找出真正的效能瓶頸與熱點查詢,把快取用在刀口上,而不是一股腦全部快取。同時要建立明確的快取失效規則,避免資料更新後使用者還看到舊資料;並持續監控快取命中率,命中率太低代表策略需要調整。快取是把雙面刃——用對了系統飛快,用錯了反而增加維護複雜度與資料不一致的風險。
快取是客製化系統面對成長與流量時,最關鍵也最划算的效能優化手段。從 Cache-Aside 的基本模式、TTL 的合理設計,到雪崩、穿透、擊穿的防範,背後都是同一個原則:在「速度」與「資料一致性」之間取得平衡。山葵組在協助企業開發與優化客製化系統時,會依實際的流量特性與資料行為,量身設計合適的快取架構,讓系統在使用者成長的同時依然穩定快速。如果你的系統正面臨效能瓶頸,歡迎與我們聊聊。