Discordチームは、同社のシステムが、大規模なデータ構造を扱う場合のBEAM -- Earlang Open Telecom Platform(OTP)のコアにある仮想マシン -- がパフォーマンス上の限界に達したことから、同時ユーザ最大1,100万という同システムのスケールアップ性能を、Rustで記述したElixirインターフェイスに頼ることになった。
マルチプラットフォームのボイスオーバIP(VoIP)アプリケーションプラットフォームメーカであるDiscordは、同社のシステムを最大500万の同時ユーザまでスケールアップ可能にするために、Erlang仮想マシン上でElixirを運用していることで知られている。最近になって、そのユーザベースが1,100万人にまで拡張され、毎秒数百万というイベントを処理するに至った。同社が実装を試みている最新の最適化では、ElixirとBEAMが提供可能であると思われるものよりも大きなデータ構造を扱うために、さらに高いパフォーマンスが求められていた。
BEAM VMは非常に高速で、日々高速化されています。可能な場所では永続的なデータ構造の活用を試みていますが、当社の運用規模では、これらの巨大なリストを十分に速く更新することはできませんでした。
特にDiscordのエンジニアが必要としたのは、並列的に発生する大量の変化をサポートすると同時に、影響を受けたインデックスのリストを返すことの可能な、ソートされたデータ構造だった。そのためチームは、MapSet
、List
、Ordset
といったElixirのデータ構造の中で、彼らの要件を満たしているものはどれかを調査し始めた。しかしながら、特に250,000以上のアイテムのコレクションに対するワーストーケースのパフォーマンスを考慮した場合、それらはいずれも要求を満足するものではなかった。入手可能なサードパーティパッケージを対象に実施した同様の調査でも、ほぼ同じ理由から、有用な結果は得られなかった。
ここにおいてDiscordのエンジニアたちは、全く違うアプローチを取ることにした。Native Implemented Function(NIF)を使用して目標を達成しようと考えたのだ。NIFは、BEAM仮想マシン内から、あたかも別のElixirあるいはErlang組み込み関数であるかのように、ネイティブコードを使用可能にするメカニズムである。NIFを開発する言語としてRustが自然な選択であったのは、その"ゼロコスト抽象化"アプローチと、BEAM VMのクラッシュやメモリリークの可能性を最小限に抑える強力な安全性保証によるものだ。さらに、Rustエコシステムには、Rustを使ってBEAM NIFを簡単に記述できるオープンソースライブラリであるRustlerが既に用意されていた。Rustlerは、インターフェース用のボイラープレート、エンコードとデコード、エラー管理のためのコードを生成する機能を備えている。
数回のイテレーションの後、Discordのエンジニアたちは、SortedSetのRustによる実装を作成することで、6.5倍という大幅なパフォーマンス改善と、ワーストーケースで160倍という莫大なパフォーマンス改善を実現できたのだ。その後、この新たなSortedSetは、Discordのすべてのギルド(guild) -- 大まかに言えばユーザグループ -- の基盤として使用されている。
今回のプロジェクトは、RustがBEAM VMに効果的に統合可能であることを示したものだ。パフォーマンスや安全性はもちろん、大規模運用における信頼性を、Elixirのような高レベル関数言語でプログラミングするメリットを失うことなく手にすることができる。