【架構師的自我修養】01 - [Wix] Scaling to 100M:當快取變成毒藥?打破 Cache 迷思
平常在遇到效能瓶頸或是預防流量增長希望加速系統反應時間,我們會有一種膝跳反射式的直覺在說: 流量很大?加個 Cache 就對了。好像 Redis 或 Memcached 是效能問題的萬靈丹一樣 這是【架構師的自我修養】系列的第一篇筆記。這個系列記錄了我從 Awesome Scalability 找尋經典技術文章並閱讀後進行自我辯證跟探討的過程,不只看 "怎麼做",更要探討 "為什麼"
原文連結:Scaling to 100M: To Cache or Not to Cache? - Wix Engineering
平常在遇到效能瓶頸或是預防流量增長希望加速系統反應時間,我們會有一種膝跳反射式的直覺在說: 流量很大?加個 Cache 就對了。好像 Redis 或 Memcached 是效能問題的萬靈丹一樣
這是【架構師的自我修養】系列的第一篇筆記。這個系列記錄了我從 Awesome Scalability 找尋經典技術文章並閱讀後進行自我辯證跟探討的過程,不只看 "怎麼做",更要探討 "為什麼"
今天的主題來自 Wix Engineering 的經典案例。這篇文章檢討了對資料庫效能的刻板印象,盲目引入快取不僅可能是多此一舉,甚至在某些場景下,快取反而成為系統的雷
初始技術債
Wix 早期的架構是典型的 Tomcat + Hibernate + Ehcache + MySQL,不過選擇這套架構只是因為早期的BE前一份工作有用這 tech stack 的經驗
這邊就有個疑問,為什麼一開始就要放 Ehcache,這其實源自於工程界一個普遍的假設:
MySQL(或大多數RDBMS)太慢了,無法應對成長中的流量。為了保護資料庫,我們必須在前面擋一層 In-Memory Cache
聽起來非常合理,而這也是大多數工程師(包括我)在設計初期的直覺。我們認為 Cache 是為了加速讀取並減輕 DB 負載,但 Wix 的故事告訴我們,這個假設在特定場景下是完全錯誤的
非預期的錯誤: 解藥變成毒藥
轉折點發生在某次系統更新後出現了 Data Corruption,當 Wix 的工程師退版並修復了資料庫中的錯誤數據,使用者看到的頁面依然是壞掉的
最終問題出在了 Ehcache
- Black Box: Ehcache 缺乏直觀的管理介面,工程師無法手動處理壞掉的資料
- 病毒式的同步: 當他們試圖重啟部分伺服器來清除狀態時,這些剛啟動的乾淨節點,自動從其他還沒重啟的節點同步了錯誤的快取資料,讓錯誤像病毒一樣在集群中不斷複製,無法根除
最終,他們被迫選擇了最屈辱的解法:同時重啟所有伺服器,才徹底清除了錯誤狀態
資料庫其實比想像中快
在經歷了 Downtime 的慘痛教訓後,Wix 團隊決定挑戰那個核心假設。他們回頭去測試 MySQL 的極限,結果得出了一個反直覺的結論
只要滿足以下三個前提:
- 給予足夠的記憶體(讓 DB 能夠利用 Buffer Pool / Disk Cache)
- 使用 Primary Key 或 Index 進行單列讀取 (Single row read)
- 不使用 Joins
即便在擁有一億筆資料的大表中,MySQL 依然能達到 Sub-millisecond 的讀取速度
對於簡單的 Key-Value 式查詢(例如 SELECT * FROM users WHERE id = ?),RDBMS 的效能完全足以應付高流量,根本不需要 Cache。我們以為的「慢」,很多時候是因為我們濫用了 join,或是沒給 DB 足夠的記憶體
引入 Cache 的四個指引
Wix 由他們的經驗整理出了四項評估是否需要 Cache 的 Trade-offs 思考點
- How do you invalidate the cache?
這是思考架構的時候常見的兩難,如何確保 DB 更新時能跟 Cache 同步,最終一致性(Eventual consistency)的延遲是否能接受 - How do you see the values in the cache (black box vs. white box cache)?
你是把資料放進一個黑箱嗎?出錯時你能多快 Debug?至少比起 Ehcache,現在常用的 redis 可以查詢現有的資料,同時我也看過自幹 in-memory cache 的系統,提供了不少無法穩定 reproduce 的 bug - What happens in a cold start of your system? Can the system cope with traffic if the cache is empty?
如果處理不了代表系統很脆弱,隨時可能發生 Cache Avalanche - What is the performance penalty of using the cache?
Cache cluster 同時也代表了每次查詢增加的一次網路流量,不論是否 cache hit,因此也要審慎評估 Miss Rate
Netflix 為了解決問題 3,自己實作了一套跨 region 的 cache warmer 方案,雖然解決了問題但帶來很大的系統複雜度,這也是值得權衡的一點
當然隨流量增加肯定無法逃離 memory 速度遠大於 disk 這件事,但現在是否真的抵達那個階段是需要多加考量的
什麼時候真的需要 Cache
Wix 證明了簡單查詢不需要 Cache,反過來說當簡單查詢不堪使用的時候便是引入 cache 的場景
- 簡單查詢也不堪負荷 (Hot Key):
- 原因:單一 DB 節點有物理極限(IOPS、網路頻寬、Lock 競爭),無法承受瞬間爆量集中訪問
- 案例:名人貼文、秒殺商品
- 解法:必須使用 Cache(如 Redis)
- 複雜計算
- 案例:多表 Join、聚合運算(Aggregation)、複雜排序
- 原因:這類查詢對 DB 計算成本過高
- 解法:Cache 預先計算好的結果
實戰應用:短網址系統的混合架構
既然簡單查詢不需要 Cache,那 System Design 面試中常見的短網址系統 (URL Shortener) 該怎麼設計?我們不需要 Redis 了嗎?這時候更該釐清不同的極端場景來考慮是否使用 Cache
- Hot Key: 例如某個名人發了一條連結,或是秒殺商品,單一 DB 節點超過負荷,無法承受瞬間百萬級的 QPS,這時候必須用 Cache
- Long Tail: 網路上絕大多數的短網址是幾年沒人點一次的,如果把這些資料都塞進 Redis,是極大的資源浪費
既然同樣的請求卻存在不同場景,採取混合模式是在設計像 Bit.ly 這樣的系統時比較適當的想法,這邊採用 LRU (Least Recently Used) 策略:
- 熱點資料 (Hot Data):因為頻繁被存取,會一直維持在 LRU Cache 的頂端。這部分流量由 Redis 擋下,保護 DB 不被瞬間流量衝垮
- 長尾資料 (Cold Data):因為無人存取,會被 LRU 機制自然淘汰
- 穿透查詢:當使用者真的點擊了一個冷門連結,請求會穿透 Cache 打到 DB。這時回歸 Wix 的教訓: 因為是簡單的 Primary Key Lookup,MySQL 可以輕鬆處理這些請求
結語:做減法的智慧
讀完 Wix 的這段歷程,我最大的體悟是:在實作產品的過程中,我也常掉入為了解決某個小問題而認為不得不引入更複雜的架構,但事實上,能用最簡單的工具解決問題,往往才是最高明的設計
「To Cache or Not to Cache?」點出了學會克制 Over-engineering 的衝動,並在理解每個組件(如 MySQL)的真實極限後,做出最精準的決策,更是架構師的一種修煉
希望這篇筆記能幫助你在未來的系統設計中,多一份「不加 Cache」的底氣
Comments ()