1.5億用戶、萬億數據,爆款社交平臺的兩次大型數據庫遷移
2017年,Discord 在技術博客中提到,由于 RAM 中無法再容納數據和索引,延遲開始變得不可預測,急速增長的數據存儲亟待遷移。他們希冀找到一款可擴展、容錯且維護成本相對較低的數據庫,以實現存儲數十億條消息的目標,最終完成了從 MongoDB 到 Cassandra 的遷移。
技術人員都希望,現行數據庫能夠滿足不斷增長的存儲需求,同時保持較低的維護需求??上КF實往往事與愿違—— Discord 使用的 Cassandra 集群出現嚴重的性能問題,技術人員耗費越來越多的精力,致力于維護數據庫,而非改進性能。
時隔六年,Discord 消息存儲再面臨性能挑戰,于是將數據庫遷移至 ScyllaDB。
這兩次數據庫遷移原因幾何?Discord 如何選型?遷移過程中問題又將如何解決?
讓我們一同在下文中尋找答案。
2017:從MongoDB到Cassandra
Discord 的增長速度和用戶生成的內容數量超出了我們的預期,用戶越多,聊天信息也就越多。2016年7月,平臺消息量已達4000萬條,12月增至1億條,而截至這篇文章發布(2017年1月)時,信息量已超過1.2億條。
我們很早就決定永久存儲所有聊天記錄,以便用戶隨時返回查看,并在任何設備上使用他們的數據。而這些龐大數據的增長速度及規模都在持續攀升,并且需要隨時保持可用。
這一點如何做到?我們的回答是遷移至 Cassandra 數據庫。
1.Discord 最初版本
2015年初,Discord 的最初版本在不到兩個月的時間成功創建。在當時情況下,MongoDB 是快速迭代的最佳數據庫之一。Discord 的所有內容都存儲在單個 MongoDB 集群中,這是有意為之,但同時我們也做好一切后續規劃,以便將所有數據輕松地遷移到新數據庫(我們確信不會使用 MongoDB 分片,因為它使用復雜,且穩定性不佳)。
實際上,這是我們公司文化的一部分:快速構建以驗證產品功能,但始終為更強大的解決方案留有后路。
消息存儲在 MongoDB 集合中,使用 channel_id 和 created_at 的單一復合索引。2015 年 11 月左右,存儲消息數量達到 1 億條,此時,預期問題發生了:RAM 無法再容納數據和索引,延遲開始變得不可控,是時候遷移到更合適的數據庫了。
2.選擇合適的數據庫
選擇新數據庫之前,必須了解我們的讀/寫模式,以及我們目前解決方案出現問題的原因。
- 我們的讀取極其隨機,讀寫比例大約為 50/50。
- 管理語音聊天的重型 Discord 服務器幾乎不發送任何消息,這意味著它們每隔幾天只發送一兩條消息,這種服務器在一年內發送的信息幾乎不到 1000 條。問題是,雖然信息量很小,但卻增加了向用戶提供數據的難度。只要向用戶返回 50 條信息,就會在磁盤上產生許多隨機尋道,導致磁盤緩存被驅逐。
- 管理文字私聊的重型 Discord 服務器發送的信息數量相當可觀,每年可輕松達到 10 萬至 100 萬條,請求數據通常都是最新的。問題是,由于這些服務器通常只有不到 100 名成員,因此請求數據的速度很低,磁盤緩存中也不太可能具備這些數據。
- 大型公共聊天 Discord 服務器會發送大量信息,其中,成千上萬的成員每天發送數千條信息,每年可發送數百萬條信息。它們幾乎總是在請求最近一小時內發送的信息,并且請求頻率很高。因此,數據通常都在磁盤緩存中。
- 在接下來的一年里,我們將為用戶提供更多隨機讀取數據的方式:查看過去 30 天內的提及消息,并跳轉到歷史記錄的對應內容,查看并跳轉到標記消息,以及全文搜索。以上這些功能都意味著更多的隨機讀??!
隨后,我們來定義下需求:
- 線性可擴展性:不希望以后重新考慮解決方案或手動重新分揀數據。
- 自動故障轉移:我們不希望被夜間打擾,因此系統出現問題時,要盡可能讓 Discord 自動修復。
- 低維護:一旦配置完畢,它就能正常工作。我們只需在數據增長時添加更多節點即可。
- 經證明可行:我們喜歡嘗試新技術,但不能太新。
- 可預測性:當 API 的響應時間95%超過 80ms 時無需警報,我們也不想在 Redis 或 Memcached 中緩存消息。
- 不使用 blob 存儲:如果不斷對 blob 存儲數據進行反序列化并新增數據,那么每秒寫入數千條消息的速度將大打折扣。
- 開源:將命運掌握在自己手里,不依賴第三方公司。
Cassandra 是唯一能滿足所有要求的數據庫。添加節點即可擴展,同時添加過程中可以容忍節點丟失,不會對應用程序產生任何影響。Netflix 和蘋果等大公司已部署使用了數千個 Cassandra 節點。相關數據連續存儲在磁盤上,這樣減少了數據訪問尋址次數,并且數據便于在集群中分布。Cassandra 由 DataStax 支持,但仍然是開源的,由社區驅動。
既然做出了選擇,我們就需要證明它確實可行。
3.數據建模
如何向新手介紹 Cassandra?最好的方式就是將其描述為一個 KKV 存儲器,它的主鍵由兩個 K 組成。
第一個 K 是分區鍵,用于確定數據所在的節點以及在磁盤上的位置。分區中包含多行記錄,每行記錄由第二個 K(即聚類鍵)確定。聚類鍵既是分區內的主鍵,也是行的排序方式,可以將分區看作有序字典。這些屬性結合在一起,即可實現非常強大的數據建模。
前文提到, MongoDB 使用 channel_id 和 created_at 索引信息, 因為所有查詢都在頻道(channel)中進行,所以channel_id 被設為分區鍵,但 created_at 并不是一個很好的聚類鍵,因為不同消息的創建時間可能相同。
好在Discord 的所有 ID都是雪花算法(可按時間排序),因此我們可以用它們來代替created_at。由此主鍵變成了(channel_id,message_id),其中 message_id 是雪花算法。這意味著加載頻道時,可以告知 Cassandra 掃描消息的準確范圍。
下面是消息表的簡化模式(省略了約 10 列)。
CREATE TABLE messages (
channel_id bigint,
message_id bigint,
author_id bigint,
content text,
PRIMARY KEY (channel_id, message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);
Cassandra 的 schema 與關系數據庫的模式有很大差別,Cassandra 的 schema 更改成本相較更低,而且不會對性能造成任何臨時性影響,因此,我們獲得了 blob 存儲和關系型存儲的最佳效果。
當我們開始將現有信息導入 Cassandra 時,日志立刻出現告警,提醒分區的大小超過了 100MB。這是怎么回事?Cassandra 宣稱可以單個分區可支持 2GB!顯然,理論性能并不意味著實際應用效果。
在壓縮、集群擴展等操作過程中,大分區會給 Cassandra 帶來很大的 GC 壓力。同時,大分區還意味著其中的數據無法分布在集群中。由于 Discord 頻道(channel)將存在數年,且持續增長擴大,所以必須限制分區的大小。
我們決定按時間分類信息,參考Discord 上最大頻道,確定如果在一個桶中存儲約 10 天的消息,就可以輕松地將容量控制在 100MB 以下。桶必須從消息 ID 或時間戳中歸并。
DISCORD_EPOCH = 1420070400000
BUCKET_SIZE = 1000 * 60 * 60 * 24 * 10
def make_bucket(snowflake):
if snowflake is None:
timestamp = int(time.time() * 1000) - DISCORD_EPOCH
else:
# When a Snowflake is created it contains the number of
# seconds since the DISCORD_EPOCH.
timestamp = snowflake_id >> 22
return int(timestamp / BUCKET_SIZE)
def make_buckets(start_id, end_id=None):
return range(make_bucket(start_id), make_bucket(end_id) + 1)
Cassandra 分區鍵可復合,因此新主鍵變成了((channel_id, bucket), message_id)。
CREATE TABLE messages (
channel_id bigint,
bucket int,
message_id bigint,
author_id bigint,
content text,
PRIMARY KEY ((channel_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);
查詢通道中最近的消息,需要生成一個從當前時間到 channel_id 的桶范圍(它也是雪花算法,必須比第一條消息更早)。然后,按順序查詢分區,直到收集到足夠多的信息。
這種方法的缺點是,不活躍的 Discord 頻道需要查詢多個分區才能收集到足夠多的信息。不過該方法在實踐中證明可行,因為對于活躍的 Discord 頻道來說,通常在第一個分區中就能找到足夠的消息,且這種情況占多數。
將消息導入 Cassandra 的過程非常順利,我們已做好了遷移到生產環境的準備。
4.驚險的啟動
將一個新系統引入生產環境總是令人恐懼的,因此最好在不影響用戶的情況下進行測試。我們將代碼設置為 MongoDB 和 Cassandra 的雙重讀/寫。
啟動后,我們的錯誤(bug)跟蹤器立即收到錯誤信息,提示稱 author_id 為空(null)。怎么回事?這是一個必填字段!
讓我們先一起回顧下問題產生的背景。
5.最終一致性
Cassandra 是 AP 數據庫,這意味著它犧牲了強一致性以換取可用性,而這正是我們想要的。在 Cassandra 中,“先讀后寫”是一種反模式(讀取比寫入成本更高),因此,即使只訪問某些列,在 Cassandra 上本質也會成為一個更新插入操作。你也可以向任何節點寫入數據,它將根據每一列的情況,使用“last write wins”的策略自動解決沖突。這對我們有什么影響?
編輯/刪除 race condition 的例子
以上動圖例子中,在一個用戶編輯消息的同時,另一個用戶刪除了同一條消息。由于 Cassandra 寫入時執行更新插入操作,因此我們最終發現記錄中只有主鍵和文本外,缺少其余數據。
有兩種可能解決方案處理這個問題:
- 編輯信息時,寫回整條信息。這有可能找回已刪除的信息,但也增加并發寫入其他列時發生沖突的機會。
- 找出已損壞的信息并從數據庫中刪除。
我們采用了第二種方法,即選擇一個必填列(本例中為 author_id),如果該列為空,則刪除該消息。
解決這個問題時,我們注意到我們的寫入效率非常低。由于 Cassandra 被設計為最終一致性,因此它執行刪除操作時不會立即刪除數據,必須將刪除復制到其他節點。即使其他節點暫時不可用,也要執行刪除操作。
Cassandra 將刪除作為一種名為“墓碑”的寫入形式來處理。在讀取時,它會跳過遇到的墓碑。墓碑的存活時間可配置(默認為 10 天),過期后會在壓縮過程中被永久刪除。
刪除列和將空值(null)寫入列完全是一回事。它們都會生成墓碑。由于 Cassandra 中的所有寫入都是更新插入,這意味著即使第一次寫入空值也會生成墓碑。實際上,我們的整個消息模式包含 16 個列,但平均每條消息長度僅有 4 個值。這導致插入新一行數據時,大部分時間都在無緣無故地向 Cassandra 寫入 12 個墓碑。
解決這個問題的辦法很簡單:只向 Cassandra 寫入非空值。
6.性能
眾所周知,Cassandra 的寫入速度比讀取速度快,我們的觀察結果也是如此:寫入速度低于1毫秒,讀取速度低于 5 毫秒。無論訪問什么數據,觀察結果都一致,并且性能在一周的測試中始終如一。意料之中,我們得到了所期望的數據庫。
通過 Datadog 查看讀寫延遲
快速、一致的讀取性能可以通過以下例子表現:在數百萬條信息的頻道中跳轉到一年前的某條消息。
跳轉到一年前的聊天記錄
7.巨大的意外
一切都很順利,我們將 Cassandra 切換為主數據庫,并在一周內淘汰掉 MongoDB 。Cassandra 完美地運行了約 6 個月,直到有一天變得反應遲鈍。
我們注意到 Cassandra 持續出現 10 秒鐘的 GC 全停頓("stop-the-world "GC),但原因未知。我們開始定位問題,發現加載 Discord 頻道需要 20 秒。一個名為“Puzzles & Dragons Subreddit”的公共 Discord 服務器是罪魁禍首。由于它是一個開放的服務器,我們加入進去一探原因。
令人驚訝的是,頻道里只有一條信息。同時我們發現,他們使用我們的 API 刪除了數百萬條信息,只在頻道中留下了 1 條信息。
上文(談及最終一致性時)提到過, Cassandra 是如何使用墓碑處理刪除操作。當用戶加載該頻道時,即使只有一條消息,Cassandra 也必須掃描數以百萬計的消息墓碑(產生垃圾的速度比 JVM 收集垃圾的速度更快)。
我們通過以下方法解決了這個問題:
- 因為我們每晚都會在消息集群上運行 Cassandra 修復(反熵進程),所以將墓碑的生命周期從 10 天縮短到 2 天。
- 修改查詢代碼,以跟蹤空桶,并在未來避免在頻道中出現空桶。這意味著,如果用戶再次進行此查詢,即使是最壞的情況下,Cassandra 也只能掃描最近的數據桶。
8.未來發展規劃
目前正在運行一個復制系數為 3 的 12 節點集群,并將根據意外所需繼續添加新的 Cassandra 節點,我們相信在很長一段時間內這個集群可以持續高效運行。
但隨著 Discord 的不斷發展,在遙遠的未來,有可能每天需要存儲數十億條消息。Netflix 和蘋果公司運行著數百個節點的集群,因此我們知道這個階段不用過多顧慮。不過,我們還是準備了一些未雨綢繆的計劃。
1)近期計劃
- 將我們的消息集群從 Cassandra 2 升級到 Cassandra 3。Cassandra 3 有一種新的存儲格式,可將存儲大小減少 50% 以上。
- 新版本的 Cassandra 更善于在單個節點上處理更多數據。我們目前在每個節點上存儲近 1TB 的壓縮數據。我們相信,只要將其提升到 2TB,就能安全地減少集群中的節點數量。
2)長期計劃
- 探索使用 C++ 編寫的 Cassandra 兼容數據庫 Scylla 。在正常運行期間,我們的 Cassandra 節點占用的 CPU 并不多,但在非高峰時段,當我們運行修復(一個反熵進程)時,CPU 占用就非常高。同時,在上一次修復后,修復持續時間和寫入的數據量也會增加。Scylla 宣稱能大大縮短修復時間。
- 建立一個系統,將未使用的頻道歸檔到谷歌云存儲的文件中,并按需加載回來。其實我們不希望這樣做,所以這一計劃未必會落實。
9.結論
盡管經歷“巨大的意外”,但我們的切換過程一直很順利。每日信息總量從 1 億多條增加到 1.2 億多條,也一直保持著良好的性能與穩定性。
由于這個項目的成功,我們已經將其余的實時生產數據轉移到 Cassandra,并且也取得了成功。
2023:從Cassandra到 ScyllaDB
2023年,Discord 使用的 Cassandra 集群出現嚴重的性能問題,技術人員耗費越來越多的精力,致力于維護數據庫,而非改進性能。
時隔六年,Discord 消息存儲再面臨性能挑戰,存儲遷移刻不容緩。
1.Cassandra的存儲痛點
Discord 將信息存儲在名為 cassandra-messages 的數據庫中。顧名思義,它運行 Cassandra 以存儲信息。2017年,Discord 運行12個 Cassandra 節點,存儲數十億條信息。
截至2022年初,以上系統擁有177個節點和數萬億條消息,Cassandra 出現了嚴重的性能問題。由于不可預測的數據庫延遲等問題,技術團隊必須隨時保持聯系,減少運維操作,避免增加系統運行成本。
痛點從何產生?讓我們來看以下這條消息。
CREATE TABLE messages (
channel_id bigint,
bucket int,
message_id bigint,
author_id bigint,
content text,
PRIMARY KEY ((channel_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);
以上 CQL 語句是消息模式的最小版本。Discord 使用的每個 ID 都通過Snowflake 生成,因此可按時間排序。根據消息發送的通道以及一個存儲桶(一個靜態時間窗口)來劃分消息。這種分區意味著,在 Cassandra 中,給定通道和存儲桶的所有消息都將存儲在一起,并跨三個節點(或設置的任何復制系數)進行復制。
在 Cassandra 中,讀取比寫入的成本高。寫入操作被附加到提交日志中,并寫入名為內存表(memtable)的內存結構中,最終被刷新到磁盤。然而,讀取需要查詢內存表(memtable)和可能的多個磁盤文件(SSTable),操作成本相當高。
用戶與服務器交互時,大量并發讀取使分區出現熱點,一般將其稱為“熱分區”。一旦數據集的規模與這些訪問模式結合,導致 Cassandra 集群出現問題。
熱分區經常導致整個數據庫集群延遲。通道與存儲桶組合接收大量流量,隨著節點服務流量越發吃力,節點延遲就越發嚴重。
由于節點速度無法跟上,對該節點的其他查詢受到影響。由于我們使用仲裁一致性級別執行讀寫操作,因此服務于熱分區的節點的所有查詢都會影響,延遲增加,從而對最終用戶產生更廣泛的影響。
集群維護任務也經常引起麻煩。我們很容易在壓縮上落后,Cassandra 會壓縮磁盤上的SSTable以獲得更高的性能讀取。這樣一來,我們的讀取成本不僅會更高,并且由于節點試圖壓縮,還會產生級聯延遲。
我們經常執行一種被稱為“八卦舞蹈”的操作,即停止一個節點的輪換,讓它在不占用流量的情況下進行壓縮,將其重新加入輪換,從 Cassandra 獲取切換提示,然后重復這個操作,直至壓縮積壓的信息被清空。由于 GC 暫停會導致顯著的延遲峰值,我們還花了大量時間調優 JVM 的垃圾收集器和堆設置。
2.架構遷移
消息集群并非唯一的 Cassandra 數據庫,我們還具備其他幾個集群,每個集群都表現出類似的缺點(雖然可能沒有那么嚴重)。
ScyllaDB 引起了我們的興趣,這是一個用 C++ 編寫的與 Cassandra 兼容的數據庫。它承諾提供更好的性能、更快的修復、通過核分片架構實現更強的工作負載隔離,以及無垃圾回收,聽起來相當吸引人。
當然ScyllaDB也存在不足之處,由于采用 C++ 編譯而不是 Java,所以它沒有垃圾收集器。過去,我們的團隊在 Cassandra 上的垃圾收集器上遇到許多問題,包括影響延遲的 GC 暫停、一直到超長的連續 GC 暫停,以至于操作員必須手動重新啟動,問題節點才能恢復健康狀態。這些問題導致技術團隊必須隨叫隨到,同時,這些問題也是影響消息集群穩定性問題的根源。
在對 ScyllaDB 進行試驗,并觀察到測試中的改進成效后,我們決定遷移所有數據庫。雖然這項決定本身可以用一篇博客來介紹,但簡而言之,截至2020年,我們已經將所有數據庫遷移到 ScyllaDB,除了一個數據庫—— cassandra-messages 。
為什么我們還未開始遷移?首先,集群規模巨大,包括數以萬億計的消息和近200個節點,任何遷移操作都非常復雜。此外,我們希望調整新數據庫時,其性能達到最佳狀態。我們還想在生產環境中積累使用 ScyllaDB 的更多經驗,了解其缺陷。
針對我們的用例,還改進了 ScyllaDB 的性能。我們在測試中發現,反向查詢的性能不足以滿足需求。以與表排序相反的順序進行數據庫掃描時,例如按升序掃描消息時,則執行反向查詢。ScyllaDB 團隊優先對其進行改進,實現了高性能的反向查詢,清除了我們遷移計劃中的“最后一個數據庫”的障礙。
我們擔心,在系統上添加新數據庫不太可能有翻天覆地的性能改進,“熱分區”的問題依然存在于 ScyllaDB ,因此,我們寄希望于投資改進數據庫上游系統,以助于數據庫屏蔽和提升數據庫性能。
3.使用數據服務提供數據
針對 Cassandra ,我們遇到了熱分區的難題,分給特定分區的高流量會引起無限并發,進而導致級聯延遲,延長后續查詢時間。如果能夠控制熱分區的并發流量,就可保護數據庫免于重負。
為了完成這項任務,我們編寫了所謂的數據服務——位于 API 單體和數據庫集群之間的中介服務。編寫數據服務時,我們選擇了一種在 Discord 應用越發廣泛的語言:Rust!它能在保障安全性的前提下,提供可與 C/ C++ 媲美的高速度。
Rust 的主要優勢之一是無懼并發——該語言使編寫安全并發代碼變得容易。它的庫也非常適合完成我們的其他工作,Tokio生態系統是構建異步I/O系統的監視基礎,并且該語言也對 Cassandra 和 ScyllaDB 提供驅動程序支持。
此外,Rust 編譯器提供的幫助、清晰的錯誤消息、語言結構以及對安全性的重視,編寫代碼變得很有趣。Rust 程序一旦通過編譯,就可以運行,這讓我們很滿意。最重要的是,我們用Rust 進行重寫(meme/模因信譽非常重要)。
我們的數據服務位于 API 和 ScyllaDB 集群之間。每個數據庫查詢大約包含一個gRPC 端點,并且故意不包含業務邏輯。數據服務一大特點是請求合并,如果多個用戶同時請求同一行,我們將只查詢一次數據庫。首個發出請求的用戶會觸發數據服務中的工作任務,后續請求將檢查該任務是否存在并訂閱它,該工作任務將查詢數據庫并將該行返回給所有訂閱者。
這就是 Rust 的強大之處:它使編寫安全的并發代碼變得輕松。
讓我們想象一下,大型服務器有一條@所有人的重要公告:用戶將打開應用程序并閱讀消息,向數據庫發送大量流量。以往,這可能會導致熱分區,并且可能需要工程師隨時保持待機,以幫助系統恢復。數據服務能夠顯著減少數據庫的流量峰值。
第二個神奇之處在于數據服務的上游。為實現更有效的合并,我們實現了一致的基于哈希的數據服務路由,為每個數據服務請求提供路由鍵。對消息來說,這是一個通道ID,因此對同一通道的所有請求都將轉到服務的同一實例,這種路由方式有助于進一步減少數據庫的負載。
這些改進頗有助益,但并未解決所有問題。,但它們并不能解決我們所有的問題。Cassandra集群上仍然存在熱分區和延遲增加,只是不那么頻繁了,這為我們贏得了一些時間,以便準備最優ScyllaDB集群并執行遷移。
4.大規模數據遷移
我們的遷移需求非常簡單:在不停機的情況下遷移數萬億條消息,并快速完成。正如上文所述,雖然 Cassandra 的情況有所改善,但還常常出現問題。
第一步很簡單:使用超級磁盤存儲拓撲配置一個新的 ScyllaDB 集群。
使用本地 SSD 提高速度,并利用 RAID 將數據鏡像到持久磁盤,由此我們同時獲得了連接本地磁盤的速度和持久磁盤的持久性。集群建立后,就可以開始向其遷移數據了。
我們的初版遷移技術旨在快速獲取價值。我們將開始使用全新的 ScyllaDB 集群來處理新數據,在切換時間內遷移歷史數據。這項操作增加了復雜性,但每個大型項目都無法避免額外的復雜性,對吧?
然后,向 Cassandra 和 ScyllaDB 雙重寫入新數據,并同時開始準備 ScyllaDB 的 Spark 遷移器。這需要大量調整,一旦設置完成,我們預計三個月能夠完成遷移。
這個時間期限讓我們并不滿意,因為我們希望更快獲取價值。所以,我們組織了一場團隊會議,集思廣益,思考如何增速——我們已經編寫了一個快速和高性能的數據庫,可以對其進行拓展。因而我們選擇參與了一些模因驅動工程,用Rust重寫數據遷移器。
某一天的下午,我們擴展了數據服務庫以便執行大規模數據遷移。它從數據庫讀取令牌范圍,通過 SQLite 在本地檢查,然后將它們送入 ScyllaDB 。連接改進后的新遷移器后,我們重新預估工期:9天!如果可以這么迅速地遷移數據,就可以放棄基于時間的復雜方式,一次性切換所有數據。
啟動遷移器并讓其保持運行,以每秒320萬的速度遷移信息。幾天后,遷移進度達到100%,但我們意識到它停留在99.9999%的完成度。遷移器在讀取數據的最后幾個令牌范圍時超時了,因為它們包含了 Cassandra 中未壓縮過的巨大墓碑范圍。我們將這個令牌范圍壓實,幾秒鐘后,遷移完成!
執行自動數據驗證,即通過向兩個數據庫發送一小部分讀取數據并比較結果,一切看起來都很好。在全生產流量的情況下,集群表現良好,而 Cassandra 卻遭受越來越頻繁的延遲問題。我們團隊聚集在現場,打開開關使 ScyllaDB 成為主數據庫。
5.數據庫遷移效果
Discord 在2022年5月切換了消息數據庫,遷移效果如何?
運行的177個 Cassandra 節點減少到72個 ScyllaDB 節點,每個 ScyllaDB 節點有9tb的磁盤空間,高于每個 Cassandra 節點平均 4tb 的磁盤空間。
我們的尾部延遲也大大改善了。例如,在 Cassandra 上獲取歷史消息的p99在40-125ms之間,ScyllaDB 的p99延遲在15ms之間,消息插入性能從 Cassandra 上的5-70ms p99上升到 ScyllaDB 上穩定的5ms p99。
2022年底,全球觀眾都在收看世界杯。技術人員發現,Discord 的監控圖表可以展示決賽的進球情況。這為技術團隊提供了一個在會議期間觀看足球比賽的借口——不是“在會議期間看足球比賽”,而是“主動監控系統性能”。
以上的信息發送數量圖,描繪了世界杯決賽的精彩發展,圖中的每個峰值分別代表比賽中的重要節點。
1. 梅西罰進點球,阿根廷1比0領先。
2. 阿根廷再次得分,以2比0領先。
3. 中場休息。當用戶談論比賽時,會持續15分鐘。
4. 姆巴佩為法國隊進球,90秒后再次進球將比分扳平!
5. 規則結束了,這場重要的比賽將進入加時賽。
6. 在加時賽的前半段沒有太多的事情發生,但我們到了中場休息,用戶開始聊天。
7. 梅西再次得分,阿根廷隊領先!
8. 姆巴佩反擊將比分扳平!
9. 加時賽結束了,我們要進點球了!
- 在整個點球大戰中,興奮和壓力不斷增加,直到法國隊失誤,而阿根廷隊沒有!阿根廷贏了!
每秒合并消息數
世界各地的人們都在觀看這場激動人心的比賽,與此同時,Discord 使用基于 rust 的數據服務和 ScyllaDB ,不費吹灰之力便承擔了比賽產生的巨大流量,同時為用戶提供交流平臺。
參考資料
- HOW DISCORD STORES BILLIONS OF MESSAGEShttps://discord.com/blog/how-discord-stores-billions-of-messages
- HOW DISCORD STORES TRILLIONS OF MESSAGEShttps://discord.com/blog/how-discord-stores-trillions-of-messages
作者丨Stanislav Vishnevskiy & Bo Ingram
編譯丨onehunnit