Language⚡ パフォーマンス・速度

Discord

Discord、Read States(既読管理)サービスをGoからRustへ書き換え

GoRust

Goのガベージコレクションが2分ごとに大規模LRUキャッシュをスキャンしレイテンシスパイクを引き起こしていたため、Rustへ全面的に書き換えて解消した。

課題

Goの強制GCが2分ごとに数百万エントリのLRUキャッシュを走査しレイテンシスパイクを招いた

アプローチ

Read StatesサービスをRustで全面書き換え、Nightly版の非同期機能やTokio 0.2で最適化

効果

レイテンシスパイクが解消し、レイテンシ・CPU・メモリすべてがGo版を上回った

背景

DiscordのRead States(既読管理)サービスは、ユーザーが接続時・メッセージ送信時・既読時にアクセスするホットパス上に位置し、数十億件のRead Stateを扱う。各サーバーには数百万ユーザー分、合計数千万件のRead Stateをキャッシュし、毎秒数十万回のキャッシュ更新と毎秒数万回のCassandraへの書き込みが発生していた。このサービスはGoで書かれていたが、Goは最低2分ごとに強制的にガベージコレクションを実行する仕様があり、GC実行のたびに巨大なLRUキャッシュ全体をスキャンして参照を判定する必要があった。この処理により数分間隔でレイテンシスパイクが発生し、GCチューニングを重ねても根本的な解決には至らなかった。

移行のプロセス

チームはRead Statesサービス全体をRustで書き直すことを決断した。RustにはGoのようなランタイムやガベージコレクタがなく、キャッシュから削除されたメモリは即座に解放されるため、GCによるスキャン待ちが発生しない。当時Rust安定版の非同期処理サポートが不十分だったため、Nightlyチャンネルを採用し、機能が安定版に統合されるまで不安定性を受け入れて対応した。さらに複数の最適化を重ねた。

  • HashMapをBTreeMapに変更してメモリ使用量を最適化
  • メトリクスライブラリを並行処理対応版に交換
  • メモリコピー回数を削減
  • Tokio 0.2へのアップグレード

移行前後の変化

指標BeforeAfter
GCによるレイテンシスパイク2分間隔で発生発生しない

結果

移行の結果、複数の指標で改善が見られた。

  • キャッシュ容量8百万Read Stateの状態でも平均応答時間がマイクロ秒単位に改善し、レイテンシスパイクは完全に解消した
  • レイテンシ・CPU・メモリのすべてでRust版がGo版を上回った
  • Tokio 0.2導入後はCPU使用率も一貫して低くなった
  • LRUキャッシュの上限も8百万まで拡大でき、キャッシュヒット率も向上した

学び

Discordは「Rustだからという理由だけですべてを書き直すことはしない」と明言しており、Rustの型安全性や借用チェッカー、ツール生態系の利点を認識しつつ、意味のある箇所でのみ戦略的に採用する方針を取っている。

2020-02-01
他の事例も見る