How ShareChat engineers rebuilt a low-latency ML feature store on ScyllaDB after an initial scalability failure — and what they learned along the way 低遅延機械学習機能ストアの需要はこれまで以上に高くなっていますが、実際に規模で実装することは依然として課題です ShareChat エンジニア Ivan Burmistrov と Andrei Manakov が P99 CONF 23 段階で ScyllaDB に基づいて低遅延 ML 機能ストアを構築する方法を共有する際に明らかになりました。 これは、新しい製品を採用することで一日を節約するようなシンプルなケーススタディではありません。これは「教訓を学んだ」ストーリーであり、絶え間ないパフォーマンス最適化の価値を調べる - いくつかの重要なエンジニアリングの取り組みとともに。 オリジナルのシステムの実装は、同社のスケーラビリティ要件をはるかに下回りました。最終的な目標は、毎秒10億個の機能をサポートすることでしたが、システムはわずか100万個の負荷で失敗しました。 ShareChat:インドの主要ソーシャルメディアプラットフォーム チャレンジの範囲を理解するには、インドの主要なソーシャルメディアプラットフォームであるShareChatについて少し知ることが重要です。ShareChatアプリでは、ユーザーはビデオ、画像、曲など15以上の異なる言語でコンテンツを発見し、消費します。ShareChatはTikTokのようなショートビデオプラットフォーム(Moj)をホストし、ユーザーがトレンドタグやコンテストでクリエイティブになることを奨励します。 両アプリケーション間では、すでに月間アクティブユーザーが3億2500万人を超える急速に成長するユーザーベースにサービスを提供し、AIベースのコンテンツ推奨エンジンは、ユーザーの維持と関与を促進するために不可欠です。 ShareChat でのショッピング このストーリーは、短形ビデオアプリMojのML機能ストアの背後にあるシステムに焦点を当てています. それは、毎日アクティブなユーザ約2千万、月間アクティブなユーザ1億に完全にパーソナライズされたフィードを提供しています. フィードは1秒あたり8000件のリクエストを提供し、それぞれのリクエストで平均2000件のコンテンツ候補がランク付けされています(例えば、お勧めする10の最高のアイテムを見つけるために)。 シェアチャットのソフトウェアエンジニアであるIvan Burmistrov氏は次のように説明しています。 Post is one entity, User is another and so on. From the computation perspective, they are quite similar. However, the important difference is in the number of features we need to pick up for each type of entity. When a user requests a feed, we pick up user features for that single user. However, to rank all the posts, we need to pick up features for each candidate (post) being ranked, so the total load on the system generated by post features is much larger than that generated by user features. この違いは私たちのストーリーで重要な役割を果たしています。 Post is one entity, User is another and so on. From the computation perspective, they are quite similar. However, the important difference is in the number of features we need to pick up for each type of entity. When a user requests a feed, we pick up user features for that single user. However, to rank all the posts, we need to pick up features for each candidate (post) being ranked, so the total load on the system generated by post features is much larger than that generated by user features. この違いは私たちのストーリーで重要な役割を果たしています。 なぜ最初の機能ストアアーキテクチャはスケールに失敗したのか 最初は、ユーザ機能ストアをリアルタイムで構築することに焦点を当てたので、その時点でユーザ機能が最も重要だった。チームはその目標を念頭に置いて機能ストアを構築し始めたが、優先順位が変わり、ポスト機能も焦点となった。 リアルタイムに近い投稿機能がより重要でした。 ランクアップするポストの数は、数百から数千に増加 イヴァン氏は「この新しいシステムをテストに行ったとき、それは悲惨に失敗しました. 毎秒約100万個の機能で、システムは反応しなくなり、遅延が屋根を通過しました。 結局のところ、問題は、システムアーキテクチャがタイルと呼ばれる事前集計データバケットを使用した方法から生じた。例えば、彼らは、特定の時間帯または別の時間帯で投稿の好きな人の数を集計することができます。 ここでは、システムアーキテクチャの高レベルの見方をします。原始データ(気に入り、クリックなど)を含むいくつかのリアルタイムのトピックがあります。Flinksの仕事はそれらをテーブルに集め、それらをScyllaDBに書き込みます。その後、ScyllaDBからテーブルをリクエストし、それらを集め、フィードサービスに結果を返します。 初期のデータベースのスケジュールとテーリング構成は、スケーラビリティの問題を引き起こしました. Originally, each entity had its own partition, with rows timestamp and feature name being ordered clustering columns. 最初は、各エンティティには独自のパーティションがありました。 テーブルは、1分、30分、および1日のセグメントで計算され、1時間、1日、7日、または30日を求めるには、平均70個のテーブルをキャッチする必要があります。 この NoSQL データモデリング マスタークラスでもっと知る 数学をすると、なぜ失敗したのかが明らかになります. システムは毎秒約22億行を処理する必要がありました. しかし、データベースの容量はわずか100万行/秒でした. Early Feature Store Optimizations: Data Modeling and Tiling Changes (早期機能ストア最適化:データモデリングとテーリングの変更) その時点で、チームは最適化のミッションに従事しました。初期のデータベースのスケジュールは、すべての機能の行を一緒に保存し、特定のタイムスタンプのためのプロトコルバッファーとして連続化しました。アーキテクチャがすでにApache Flinkを使用していたため、新しいテーリングスケジュールへの移行は、Flinkのデータパイプラインの構築における高度な機能のおかげでかなり簡単でした。 チームはまた、5分、3時間および5日間の追加テーブルを1分、30分および1日テーブルに追加してテーブル構成を最適化しました。 データベース側でより多くの行/秒を処理するために、ScyllaDBの圧縮戦略を増加値からレバレードに変更しました。 ]. そのオプションは、関連する行を一緒に保ち、読み込みI/Oを減らすことで、彼らのクエリパターンにより適合しました。 コンパクト戦略についてもっと知る 残りの負荷をカスタマイズする最も簡単な方法は、ScyllaDBを4倍にスケーリングすることだったが、より大きなクラスターはコストを増やすことになり、それは彼らの予算に含まれていなかったので、チームはScyllaDBクラスターをスケーリングすることなくスケーリング能力を向上させることに集中し続けた。 データベースの負荷を減らすための機能ストアキャッシュローカリゼーションの改善 ScyllaDBの負荷を減らす一つの潜在的な方法は、ローカルキャッシュのヒット率を改善することだったので、チームはこれをどのように達成するかを研究することに決めた。明らかな選択肢は、クライアントからリクエストに関するいくつかの情報に基づいて特定のレプリカへのリクエストをリダイレクトするためのよく知られているテクニックである一貫したハッシュアプローチを使用することでした。 このシンプルな設定は機能しませんでした. 具体的には: クライアントのサブセットは、最悪の場合100%に上昇する巨大なキーリマッピングを引き起こしました。ノードキーはハッシュリングで変更できるため、自動スケーリングでリアルライフのシナリオを使用することは不可能でした。 Ingress は、gRPC ヘッダーという最も明らかなソリューションをサポートしていないため、リクエストのハッシュ値を提供することは困難でした。 遅延は深刻な劣化を経験し、何が尾の遅延を引き起こしているのかは不明でした。 ポッドのサブセットをサポートするために、チームはアプローチを変更しました。彼らは、2 つのステップのハッシュ機能を作成しました:最初にエンティティをハッシュし、その後ランダムなプレフィックスを追加しました。これにより、エンティティは欲しい数のポッドに分布しました。理論上、このアプローチは、エンティティが同じポッドに複数回マッピングされているときに衝突を引き起こす可能性があります。 Ingress は、gRPC ヘッダーを変数として使用することをサポートしていませんが、チームは解決策を見つけました:パスリリースを使用して、パス自体に必要なハッシュ キーを提供します。 残念ながら、遅延劣化の原因を特定するにはかなりの時間を要し、観測性の改善も必要でした。 期限を満たすために、チームはFeatureサービスを27の異なるサービスに分割し、クライアントですべてのエンティティを手動で分割しました。それは最もエレガントなアプローチではありませんでしたが、それはシンプルで実用的でした - そしてそれは素晴らしい結果を達成しました。キャッシュヒット率は95%に改善され、ScyllaDBのロードは秒あたり1840万行に減少しました。 しかしながら、この「古い学校」の展開を分断するアプローチはまだ理想的なデザインではなかった。27の展開を維持することは退屈で効率が低かった。加えて、キャッシュヒット率は安定していなかったし、スケーリングは、各展開で最小ポッド数を高く保つことによって制限されていた。 最適化の次の段階:一貫したハッシュ、機能サービス もう一回の最適化に備えて、チームは、機能サービスと共に展開されたEnvoy Proxyと呼ばれるサイドカーを使用して一貫したハッシュアプローチを再検討しました。Envoy Proxyは、遅延尾の問題を特定するのに役立つより良い観察性を提供しました。問題:Featureサービスへの異なるリクエストパターンはgRPC層とキャッシュに大きな負荷を引き起こしました。 その後、チームは機能サービスを最適化しました。 キャッシュ ライブラリ (VictoriaMetrics の FastCache) をフォークし、バッチ 書き込みとより良い追放を実装し、ムテックス コンテストを 100 倍減らしました。 Forked gprc-go and implemented buffer pool across different connections to avoid contention during high parallelism. 異なる接続を介してバッファプールプールを実装し、高パラレリズムの間に争いを回避しました。 Object pooling and tuned garbage collector (GC) パラメータを使用して、割り当て率と GC サイクルを減らす。 Envoy Proxy が 15% のトラフィックをコンセプトの証明で処理することにより、結果は有望でした: 98% のキャッシュ ヒット率で、ScyllaDB の負荷を 7.4M 行/秒に減らしました。 High Performance Feature Storeのスケーリングから学んだレッスン タイムラインの視点から見たこの旅の様子は、以下です。 最後に、Andreiはこのプロジェクトから得たチームのトップレッスンをまとめました(これまで): ShareChat チームがシステムデザインを劇的に変更したにもかかわらず、ScyllaDB、Apache Flink、VictoriaMetrics は順調に機能し続けました。 それぞれの最適化は、前回よりも難しく、影響が少なくなります。 シンプルで実用的なソリューション(機能ストアを27の展開に分割するなど)は本当に機能します。 最高のパフォーマンスを提供するソリューションは、常にユーザーフレンドリーではありません。たとえば、改訂されたデータベースのスケジュールは良いパフォーマンスを提供しますが、維持し理解するのが困難です。 時には、デフォルトのライブラリをフォークして、最適なパフォーマンスを得るために、特定のシステムに調整する必要があります。