跳至主要内容

系統設計系列文20:Feed 走向大型系統的 Fan-out 做法

· 閱讀時間約 5 分鐘
阿昇
Software Engineer

今天回頭加強 Feed 功能,看看原本陽春的做法會有什麼問題。

先回顧原本的做法​

Day 3 我們做出了最陽春的 Feed 邏輯:

SELECT *
FROM posts
WHERE author_id IN (
SELECT followee
FROM follows
WHERE follower = :user_id
)
ORDER BY created_at DESC
LIMIT 20;

流程是:Feed 被打開的當下,才去查詢「這個人追蹤了誰」,接著撈這些人的貼文,排序後回傳。這種「讀取當下才即時組裝」的做法,叫做 Fan-out on Read。

在使用者少的時候完全沒問題,但假設 Pikachu 追蹤了 2,000 個人呢?

Fan-out on Read 在大規模下的問題​

每一次 Pikachu 打開 Feed、往下滑、甚至只是重整頁面,Server 都要進行這些內容:

查詢 Pikachu 追蹤的 2,000 個人
↓
向這 2,000 個作者查詢最新貼文
↓
把結果全部混在一起排序
↓
取出前 20 筆回傳

而尷尬的是,這 2,000 個人的貼文內容,在這幾秒鐘之間幾乎不會改變,但我們卻要為了每一次 Feed Read 都重新算一次。

使用者越活躍、越常滑 Feed,這個昂貴的查詢就被重複執行越多次,Database 的負擔也隨之飆高。

問題的本質是:

我們把「組裝 Feed」這件昂貴的事,放在使用者最頻繁觸發的「讀取」路徑上。

Fan-out on Write:把工作挪到寫入的時候做​

既然讀取的頻率遠高於發文的頻率,那能不能反過來想:

在有人發文的當下,就把這篇貼文直接送到每一個追蹤者的 Feed 裡

這樣可讓「讀 Feed」變成一次單純的查詢,而不是每次都重新運算,這就是 Fan-out on Write。

Fan-out on Write 架構圖

實作上,每個使用者會有一份自己專屬、已經排好序的 Feed(例如存在 Redis 的 List 裡)。當 Charmander 發布一篇新貼文:

Charmander 發布 Post
↓
查出 Charmander 的所有 Follower
↓
把這篇 Post 直接寫進每個 Follower 的 Feed List

之後 Pikachu 打開 Feed,Server 只需要做一件事:

直接讀取 Pikachu 自己的 Feed List
↓
回傳

從「DB 撈資料」變成「Cache 查表」,Read 的成本大幅下降。

Fan-out on Write 的代價​

Fan-out on Write 把成本從讀取端搬到了寫入端,這個 Trade-off 在大部分情況下很划算,因為大部分使用者的追蹤者數量都是合理範圍。

但如果發文的人剛好是川投顧和馬投顧,或是超級巨星、流量網紅之類擁有幾千萬追蹤者的帳號呢?

他只是發了一篇貼文,系統卻要因此產生幾千萬次寫入。

把這篇貼文塞進每一個追蹤者的 Feed List 裡,這不只會有效能問題,甚至可能瞬間打爆 Message Queue 與 Feed Store 的寫入能力,這就是典型的「Hotspots(熱點)」問題。

業界做法:Hybrid Fan-out​

面對這類問題,比較直覺的想法就是分類處理。

一般使用者適合 Fan-out on Write,可是熱門帳號會把 Fan-out on Write 拖垮,業界的做法通常不是二選一,而是依帳號類型混用兩種策略,也就是 Hybrid Fan-out:

  • 一般使用者發文:走 Fan-out on Write,發文當下就推送到所有 Follower 的 Feed,讓多數人讀 Feed 時又快又便宜。
  • 熱門帳號發文(例如追蹤數超過某值):不主動 Fan-out,貼文只寫入一次,留到讀取端再處理。

Follower 讀 Feed 時,除了直接讀自己的 Feed List(已經包含一般朋友的貼文),還要額外用 Fan-out on Read 的方式,即時查詢自己追蹤的熱門帳號有沒有新貼文,兩邊的結果在讀取當下合併、排序後才回傳給使用者。

三種做法比較​

Fan-out on ReadFan-out on WriteHybrid Fan-out
何時計算讀取當下即時運算發文當下就推送依帳號類型混用
Read 效能慢,使用者越活躍負擔越重快,直接查表多數情況快
Write 成本低,只需寫入一次高,追蹤者越多成本越高一般帳號低,熱門帳號改為讀取時處理
最大風險追蹤數多的使用者拖慢自己的 Read熱門帳號一發文就炸掉寫入端實作複雜度最高
適合場景追蹤者數量分佈平均、規模較小一般社群平台的多數帳號追蹤者數量落差極大的大型社群平台

Twitter 或 Facebook 這類擁有大量「一般使用者」與少數「超級大帳號」並存的平台,正是採用 Hybrid Fan-out 的典型案例。

小結​

這次把 Feed 從最陽春的 Fan-out on Read,一路演化到能同時應付一般使用者與熱門帳號的 Hybrid Fan-out:

  • Fan-out on Read → 讀取貴,適合小規模
  • Fan-out on Write → 讀取便宜,但熱門帳號會把寫入端炸掉
  • Hybrid Fan-out → 一般帳號 Write,熱門帳號 Read,兩邊在讀取時合併

不過「熱門帳號」的問題其實不只發生在 Feed 上,一篇爆紅的貼文、一個瞬間湧入大量流量的使用者,會用各種形式讓系統的某個角落被打爆,即使整體系統其實還有餘裕。


系列導覽​