1726864958
2024-09-20 18:09:15
さらに前進
最初の実験で、zstandard ストリーミングが zlib ストリーミングよりも優れていることが証明されましたが、残された疑問は、「これをどこまで押し上げることができるか」でした。最初の実験では、zstandard のデフォルト設定を使用し、圧縮設定をいろいろ試すことで、圧縮率をどこまで押し上げることができるかを知りたいと考えました。
それで、どこまで進んだのでしょうか?
チューニング
Zstandard は高度に設定可能で、さまざまな圧縮パラメータを微調整できます。私たちは、圧縮に最も大きな影響を与えると思われる 3 つのパラメータ、チェーンログ、ハッシュログ、ウィンドウログに注力しました。これらのパラメータは、圧縮速度、メモリ使用量、および圧縮率の間でトレードオフを提供します。たとえば、チェーンログの値を増やすと、一般的に圧縮率は向上しますが、メモリ使用量と圧縮時間が増加します。
また、決定した設定で、圧縮コンテキストがホストのメモリに収まるようにしたいと考えました。余分なメモリ使用量を吸収するためにホストを追加するのは簡単ですが、追加のホストにはコストがかかり、ある時点で、利益が減少することになります。
全体の圧縮レベルは6、チェーンログとハッシュログは16、ウィンドウログは18に落ち着きました。これらの数字は、 ここで確認できるデフォルト設定 ゲートウェイ ノードのメモリに十分収まります。
Z標準辞書
さらに、zstandard の辞書サポートを利用してデータをさらに圧縮できるかどうかも調査したいと考えました。zstandard に何らかの情報を事前に設定しておくことで、最初の数キロバイトのデータをもっと効率的に圧縮できます。
ただし、これを行うと、コンプレッサー (この場合はゲートウェイ ノード) とデコンプレッサー (Discord クライアント) の両方が相互に正常に通信するために同じ辞書のコピーを持つ必要があるため、複雑さが増します。
使用する辞書を生成するには、データが必要でした… そしてたくさんのZstandardには辞書を生成する組み込みの方法があります(zstd –train)をデータのサンプルから抽出するため、大量のサンプルを収集する必要がありました。
注目すべきは、ゲートウェイがペイロードの2つのエンコード方式をサポートしていることです。JSONと ETFまた、JSON 辞書は ETF ではそれほどパフォーマンスが良くないため (逆も同様)、エンコード方法ごとに 1 つずつ、合計 2 つの辞書を生成する必要がありました。
辞書にはトレーニング データの一部が含まれており、辞書をクライアントに発送する必要があるため、辞書を生成するサンプルに個人を特定できるユーザー データが含まれていないことを確認する必要がありました。120,000 件のメッセージを含むデータを収集し、ETF と JSON エンコードで分割して匿名化し、辞書を生成しました。
辞書が構築されると、ゲートウェイ クラスターを展開することなく、収集したデータを使用してその有効性を迅速に評価し、反復できるようになります。
圧縮を試みた最初のペイロードは「READY」でした。ユーザーに送信される最初の(そして最大の)ペイロードの 1 つである READY には、ギルド メンバーシップ、設定、既読状態(どのチャネルを既読/未読としてマークするか)など、接続ユーザーに関するほとんどの情報が含まれています。ベースラインを確立したデフォルトの zstandard 設定を使用して、2,517,725 バイトの 1 つの READY ペイロードを 306,745 バイトに圧縮しました。トレーニングしたばかりの辞書を使用して、同じペイロードを 306,098 バイトに圧縮しました。これは、約 600 バイトの増加です。
最初は、これらの結果に落胆しましたが、次に、TYPING_START と呼ばれる小さなペイロードを圧縮してクライアントに送信し、「XXX が入力中です…」という通知を表示できるようにしました。この状況では、636 バイトのペイロードは、辞書なしで 466 バイト、辞書ありで 187 バイトに圧縮されます。zstandard の動作方法により、小さなペイロードに対して辞書を使用すると、はるかに良い結果が得られました。
ほとんどの圧縮アルゴリズムは、すでに圧縮されたデータから「学習」しますが、ペイロードが小さい場合は学習するデータがありません。ペイロードがどのようになるかを事前に zstandard に通知することで、バッファが完全に埋められる前に、最初の数キロバイトのデータをどのように圧縮するかについて、より情報に基づいた決定を下すことができます。
これらの結果に満足したので、ゲートウェイ クラスターに辞書サポートを導入し、実験を始めました。ダーク ローンチ フレームワークを利用して、zstandard と辞書付きの zstandard を比較しました。
生産テストでは次の結果が得られました。
READY ペイロード サイズは、Web ソケット経由で送信される最初のメッセージの 1 つであり、辞書の恩恵を受ける可能性が最も高いため、特に注目しました。上の表に示すように、READY の圧縮によるメリットは最小限であったため、辞書が小さいペイロードでより有利になることを期待して、より多くのディスパッチ タイプの結果を調べました。
残念ながら、結果は少し複雑でした。たとえば、この記事全体で比較してきたメッセージ作成ペイロード サイズを見ると、辞書によって状況が悪化していることがわかります。

最終的に、辞書の実験を継続しないことに決めました。圧縮辞書がわずかに改善されることで得られるメリットは、ゲートウェイ サービスとクライアントに追加される複雑さのほうが上回っていました。データは Discord のエンジニアリングの大きな原動力であり、データがすべてを物語っています。これ以上の労力を費やす価値はありませんでした。
バッファのアップグレード
最後に、オフピーク時に zstandard バッファを増やすことを検討しました。Discord のトラフィックは日周パターンに従っており、ピーク需要を処理するために必要なメモリは、1 日の残りの時間に必要なメモリよりも大幅に多くなります。
一見すると、ゲートウェイ クラスターを自動スケーリングすると、オフピーク時にコンピューティング リソースを無駄に消費することがなくなります。ただし、ゲートウェイ接続は長時間持続する性質上、従来の自動スケーリング方法は当社のワークロードには適していません。そのため、オフピーク時にはメモリとコンピューティング リソースが大量に余ります。この余剰コンピューティング リソースが余っていると、次のような疑問が生じます。これらのリソースを利用して、圧縮率を高めることはできるでしょうか?
これを理解するため、ゲートウェイ クラスターにフィードバック ループを構築しました。このループは各ゲートウェイ ノードで実行され、接続しているクライアントによるメモリ使用量を監視します。次に、zstandard バッファをアップグレードする必要がある新規接続クライアントの割合を決定します。バッファがアップグレードされると、windowlog、hashlog、chainlog の値が 1 つ増加します。これらのパラメータは 2 の累乗として表現されるため、これらの値を 1 つ増やすと、バッファが使用するメモリ使用量が約 2 倍になります。
デプロイしてフィードバック ループをしばらく実行してみたところ、当初期待していたほど良い結果は得られませんでした。下のグラフに示されているように、24 時間にわたってゲートウェイ ノードのアップグレード率は比較的低く (最大 30%)、予想していた 70% を大幅に下回りました。

少し調べてみると、フィードバック ループが最適に動作しない原因の 1 つがメモリの断片化であることがわかりました。フィードバック ループは実際のシステム メモリ使用量を調べていましたが、BEAM は接続されたクライアントを処理するために必要な量よりも大幅に多くのメモリをシステムから割り当てていました。このため、フィードバック ループは、使用可能なメモリよりも少ないメモリしか使用できないと判断していました。
これを緩和するために、私たちはBEAMアロケータの設定を微調整する実験を少し行いました。具体的には、 ドライバー割り当て アロケータは、(衝撃的に) ドライバのデータ割り当て。ゲートウェイプロセスが使用するメモリの大部分は、C言語で実装されたzstandardストリーミングコンテキストです。 ニフNIFメモリ使用量は、 ドライバーの割り当て。 私たちの仮説は、 ドライバー割り当て アロケータを使用して、zstandard コンテキストのメモリをより効率的に割り当てたり解放したりすることで、断片化を減らし、全体的なアップグレード率を向上させることができます。
しかし、アロケータ設定を少しいじった後、フィードバック ループを元に戻すことにしました。最終的には適切なアロケータ設定が見つかったかもしれませんが、アロケータを微調整するために必要な労力と、これによってゲートウェイ クラスターにもたらされた全体的な複雑さが相まって、これが成功した場合に得られるメリットを上回りました。
実装と展開
当初の計画では zstandard をモバイル ユーザーのみに考慮していましたが、帯域幅の改善が十分に大きかったため、デスクトップ ユーザーにも提供できるようになりました。zstandard は C ライブラリとして提供されるため、ターゲット言語 (Android の場合は Java、iOS の場合は Objective C、デスクトップの場合は Rust) でバインディングを見つけて、各クライアントに接続するだけで済みました。Java の場合、実装は簡単でした (zstd-jni) およびデスクトップ (zstd セーフ) ではバインディングが既に存在していましたが、iOS の場合は独自のバインディングを作成する必要がありました。
これはリスクの高い変更であり、問題が発生した場合に Discord が完全に使用できなくなる可能性があるため、ロールアウトは実験の枠内で行われました。この実験には 3 つの目的がありました。問題が発生した場合にこれらの変更を迅速にロールバックできるようにすること、「ラボ」で確認した結果を検証すること、およびこの変更がベースライン メトリックに悪影響を及ぼしているかどうかを判断できるようにすることです。
数か月かけて、すべてのプラットフォーム上のすべてのユーザーに zstandard を正常に展開することができました。
#Discord #が #Websocket #トラフィックを #削減した方法