面試官:系統有萬億條消息怎么存儲?
我們如何設計一個能存儲數萬億條信息的系統?
Discord 的消息存儲演進給我們提供了真實案例參考。
下圖顯示了 Discord 消息存儲的演變過程:MongoDB -> Cassandra -> ScyllaDB
圖片
第一階段
2015 年,Discord 的第一個版本建立在單個 MongoDB 之上。2015 年 11 月左右,MongoDB 存儲了 1 億條消息,其內存無法再容納數據和索引。延遲變得不可預測。消息存儲需要轉移到另一個數據庫。這時 Cassandra 被選中。
第二階段
2017 年,Discord 擁有 12 個 Cassandra 節點,存儲了數十億條消息。
2022 年初,Discord 擁有 177 個 Cassandra 節點,存儲了數萬億條消息。此時,延遲再次變得難以預測,維護的成本也變得過于昂貴。
造成這一問題有幾個原因:
- Cassandra 使用 LSM 樹作為內部數據結構。讀取比寫入更昂貴。在一臺擁有數百名用戶的服務器上,可能會有很多并發讀取,從而導致熱點問題。
- 維護集群(如壓縮 SSTables)會影響性能。
- 垃圾回收會導致明顯的延遲
第三階段
這時,Discord 重新設計了消息存儲的架構:
- 采用集中式的數據服務,其使用單體 API來訪問,并用 Rust 重寫。
- 采用基于 ScyllaDB 的存儲。ScyllaDB 是用 C++ 編寫的 Cassandra 兼容數據庫。
新架構的優勢在于:
- 用 C++ 而不是 Java 編寫,消除了垃圾回收暫停的干擾。
- 按核分片模型(Shard-per-Core model)提供更好的負載隔離,防止熱分區在節點間產生級聯延遲。
- 優化了反向查詢性能,以滿足 Discord 的需求。
- 節點減少到 72 個,同時將每個節點的磁盤空間增加到 9 TB。
為了進一步保護 ScyllaDB,Discord 針對數據服務還做了以下優化:
- 在 Rust 中構建中間數據服務,限制并發流量峰值。
- 數據服務位于應用程序接口和數據庫之間,可聚合請求。
- 即使多個用戶請求相同的數據,也只需查詢一次數據庫。
- Rust 提供了快速、安全的并發功能,是這種工作負載的理想選擇。
優化后的系統性能大大提高:
- ScyllaDB 的 p99 讀取延遲為 15 毫秒,而 Cassandra 為 40-125 毫秒。
- ScyllaDB 的 p99 的寫延遲為 5 毫秒,而 Cassandra 為 5-70 毫秒。
該系統可輕松應對世界杯流量高峰。
本文參考 Discord blog。