原文:Threads

摘要

@chunhao2026 的〈System Design Interview 系列〉WhatsApp 篇。即時通訊要同時做到「訊息不丟」和「送得快」,直覺解法是給每個用戶一條 durable queue(持久化佇列,訊息寫進磁碟、沒被取走就一直留著)。WhatsApp 的設計拆解直接否決這條路:不是 durable queue 不好,而是以十億用戶的規模,每人一條的單位成本會爆掉。真正的解法是把「保證送達」和「即時推送」拆成兩條獨立路徑:先把訊息寫進不會丟的 Inbox table,再用一條快但允許丟失的 Pub/Sub 通道做即時推送。撐住可靠性的不是 Redis 有多快,而是「先持久化、再推送」這個寫入順序。

重點

  • Per-user Kafka topic 的成本算不過來:每個 topic 的 metadata overhead 約 50KB,十億用戶就是 50TB+ 的 metadata,還沒算訊息內容。規模一大,per-unit cost 就是瓶頸。
  • 不要找「更輕的 pub/sub」來取代,要拆職責:即時推送和保證送達根本不該走同一條路徑。
  • 即時推送交給 Redis Pub/Sub:at-most-once(最多送一次,收不到就算了),單台 instance 可撐約 100K updates/sec、27% utilization。輕量,但會丟。
  • 保證送達交給 Inbox table:每個用戶一個收件匣資料表,訊息先寫進去。使用者離線或推送掉了,重新連線時從 Inbox 撈回來即可。
  • 寫入順序是關鍵:先寫 Inbox,再 publish 到 Pub/Sub(durable-write-first)。順序反過來,推送成功但 Inbox 寫失敗,訊息就真的消失了。
  • Pub/Sub 只是錦上添花:它負責即時體驗,丟了沒關係,因為底層永遠有一份保證存在的副本。
  • 這個 pattern 不限聊天:通知系統、即時同步、live update,任何需要「保證送達 + 即時體驗」的系統都能套用:先寫進一個不會丟的地方,再用一條快但可能丟的通道做 real-time push。

個人洞見

跟同系列的 Uber 篇 一樣,面試時的加分點是先指出「直覺解法在規模下為什麼不行」,再講拆解後的架構。這篇的核心可以濃縮成一句:可靠性來自寫入順序,不是來自元件速度。另外,「先寫進一個不會丟的地方」這個思路,跟 用 repo 目錄當任務佇列 的輕量做法是同一個精神:持久化層先站穩,再談即時性。