こんにちは。LINE VOOM AI組織のサーバー開発者、Chanwoo ParkとYousung Yangです。
本記事ではAIに使用されるリアルタイム埋め込みを提供するサーバーを構築するにあたり、性能向上とインフラ費用削減を両立させたプロセスと結果についてお伝えします。
この記事は、AIに限 らず、大量のデータをリアルタイムで提供するサーバーの構築を必要とする、あらゆる分野に通じる内容となっています(埋め込み自体の生成方法については触れていません)。サーバー構築時に常に課題となる性能向上とコスト削減に、この記事が参考になることを願います。
この記事では、具体的な数値をなるべく省略しました。開発者が扱うデータはそれぞれ違うので、具体的な数値を提示するより、問題のアプローチ方法と解決策を詳しく説明し、みなさんがそれを簡単に再現して効果を体感できるように記事を構成しました。
プロジェクトの紹介
今回のプロジェクトを一文で説明すると、「大量の埋め込みをリアルタイムでAIモデルに提供するためのプロジェクト」です。AIモデルのニーズに合った埋め込みをリアルタイムで提供するサーバーを構築するにあたり、高TPSと高速な応答速度を実現し、それと同時にインフラコストを最小限に抑えることを目標としました。
プロジェクトの目標
プロジェクトの目標を重要度の高い順に一つずつ具体的に紹介します。
1. 高TPS(transactions per second)達成
今回のプロジェクトで最も重要な目標は、要求されるTPSを達成することでした。TPSが達成されないとサービスが成り立たないためです。AIモデルが要求するTPSは、数値だけを見ると他のLINEサービスでも見られる通常のレベルでしたが、細かく分析してみると他のサービスのTPSに数十倍、数百倍を掛けたようなレベルでした。その理由については、後述の埋め込みのデータ特性で説明し ます。
2. 高速な応答速度
どれだけ良いサービスでも、応答時間が遅くてユーザーを待たせてしまうと、決して良いユーザーエクスペリエンスを提供できません。後述しますが、データサイズが大きく、応答速度が遅かった、それを短縮することが重要でした。
3. インフラコスト削減
高TPSを実現するためには、必然的に大規模なインフラを使用することになります。となると、コスト削減の重要性が増し、プロジェクトの主な目標の一つとなります。例えば、VM10台規模のサービスでサーバーの性能向上と効率化によって5台に削減するのと、VM100台規模のサービスで50台に削減するのとでは、その割合は同じでも、実際のコスト削減効果は10倍違います。
埋め込み(embedding)とは?
まず、この記事でよく出てくるキーワードである埋め込みとは何かを解説します。
AIと埋め込み
まず、埋め込みの例を見てみましょう。
"data": [
{
"embedding": [
1.543545822800004554,
-0.014464245600309352,
-0.021545555220005484,
...
-2.547132266452536e-05,
-1.5454545875425444544,
-1.0452722143541654544
],
}
],
ご覧のとおり、何を意味するのか人間には分かりにくい数字の配列です。埋め込みは元々人間のためのものではなく、AIのためのものです。埋め込みは、単語、画像、動画などの実際のオブジェクトをコンピュータが処理できる形で表現したものです。カンマで区切られた数字一つ一つが次元であり、その数字の 配列全体がベクトルであると考えると、各埋め込みをn次元の空間に表示できます。
これに基づいて、オブジェクトAの埋め込みとオブジェクトBの埋め込みを空間に表示し、お互いの距離を測定して、お互いの類似度を判断できます。画像を例に挙げると、黒猫の写真と白猫の写真は、シマウマの写真よりも隣接した空間に表示されます。このように、埋め込みはAIがオブジェクト間の類似度を評価する際に使用する必須要素です。
リアルタイム埋め込みとは?
AIに埋め込みが必要なタイミングは大きく2つに分けられます。1つは、モデルを学習させるときで、もう1つは、学習されたモデルを実際のサービスに適用した後、ユーザーのリクエストを受けるときです。後者の場合、ユーザーのリクエストに素早く応答しなければならないため、サーバーはモデルがリクエストした大量の埋め込みを非常に速いスピードで提供する必要があり、このとき使用する埋め込みをリアルタイム埋め込み(realtime embedding)と言います。
埋め込みのデータ特性
埋め込みがより高い次元で構成されるほど、構成している値の範囲が大きいほど、AIモデルはより正確に判断できますが、これは埋め込みがより大きなデータになるという意味でもあります。さらに、AIモデルはこのような埋め込みを1件だけリクエストすることはありません。サーバーの観点から大きな埋め込みが複数件同時に入出力されるということは、大量のI/Oが発生することを意味します。
プロジェクトに適したDB選定
プロジェクトを 開始して最初に行ったのは、DBの選定でした。プロジェクトの非常に高い性能要求レベルを達成するためには、高性能DBが必須だったからです。
必要なDBの特性を整理
まず、必要なDBの特性を把握したうえで、その基準をもとにどのDBを使うか判断できるため、具体的にどのような特性が必要かを以下のように整理しました。
RDB、NoSQLのどちらでもOK
今回のプロジェクトはリアルタイム埋め込みをキーと値で保存し、キー(サブキー(sub-key)を含む)で取得することで十分でした。保存されたデータの関係性を取得する用途ではないため、NoSQL種類のDBも使用できました。
シャーディング対応
QPS(query per second)レベルが変わっても、大量のI/Oを柔軟かつ高速にサポートし、拡張が必要なときに無停止でスケールアウトするためには、シャーディングがサポートされている必要があります。特に、ネイティブシャーディングをサポートするDBが推奨されます。MySQLのようにシャーディングを実装できる場合もありますが、効率性とメンテナンスを考慮すると、ネイティブシャーディングをサポートするDBが有利です。
ネイティブシャーディングを前提に設計されたDBは、複数のノードにデータが配置されるため、リクエストがノード全体に均等に分散され、その分ネットワーク負荷が分散されるというメリットがあります。このメリットは、特に埋め込みのようにネットワーク帯域幅を多く占有するデータを保存する場合、非常に重要な特性です。Redis ClusterやMongoDB Sharded Clusterがその代表例です。
複数のI/Oに特化しているか
複数の埋め込みを一度に大量取得する必要があるため、DBレベルで特化したコマンドをサポートし、そのコマンドが単発のコマンドより性能が良い方が有利でした。代表例としてRedisのmget
やhmget
コマンドは、個別のget
リクエストを繰り返すより数倍から数十倍も性能が優れています。
速い応答時間
どのサービスでもそうですが、リアルタイム埋め込みサーバーは特に応答時間が重要です。通常、メモリを基盤にしたDBが一番速い応答時間を示し、Redis ClusterやMemcachedがその代表例です。
QPSごとの構築費用
スケールアウトが可能なほとんどのDBは、コストを投入すればするほど性能が向上するため、どのDBでも目標QPSを達成できます。しかし、コストを考慮すると、投入費用に対してQPSが高いDBを選ぶ必要があり、特に今回のプロジェクトは要求されるQPSが非常に高かったので、QPSごとの構築費用がさらに少なくなるべきでした。QPSごとの構築費用は、Redis ClusterがMongoDBやMySQLよりはるかに少なくなります。
Reactive Driver対応
サーバーをReactorで実装し、DBがReactiveドライバーをサポートすると、Reactive Processingの優れた性能を享受できます。RedisやMongoDB、Cassandra、まだ一部機能に制限はありますが、MySQLもR2DBCというReactiveドライバーをサポートします。
DB選定 - Redis Cluster
私たちに必要なDBの特性を整理した結果、Redis ClusterをメインDBとして使うことにしました。
「Redis Clusterはキャッシュ用ではないですか?」
このような質問をされる方もいらっしゃると思います。実際、Redis ClusterはメモリベースのDBで、サーバーが再起動されるとデータが失われる可能性があります。通常、キャッシュの用途で使うことが多いです。
しかし、私たちは以下のようにもう少し深く考えてみた結果、データ保存用として使っても問題ないという結論を出しました。
- まず、Redis Clusterは複製することで、フェイルオーバー(fail over)と高可用性(high availability、HA)を実現します。Primary - Replica構造で動作し、Primaryに障害が発生した場合、フェイルオーバーが作動して自動的にReplicaがPrimaryに昇格されます。そのため、PrimaryとReplicaに同時に問題が発生しない限り、大きな問題はありません。個人的にRedis Clusterを10年前から使っていて、フェイルオーバーが動作した状況を経験したことは数えるほどで、全面障害でサービスが長時間停止したり、データが消失したりしたことはありません。フェイルオーバー動作時のサービス停止時間は10~30秒程度で、このとき、欠落した書き込み要求があれば、ログなどを使って復旧できます。
- 最悪の場合にデータが失われても、復旧できるように設計できます。サービスによって外部からデータを再注入して失われたデータを埋めることができ、普段はバックアップ用DBに二重書き込みを行い、これを活用することもできます。
- DB障害発生時、サーキットブレーカーを通じて関連モジュールを停止させ、ユーザーに代替データを提供できます。この措置により、全面障害ではなく、一部 機能の障害レベルになるので、サービスへの影響を最小限に抑えられます。
Redis Clusterのメリット(優れたQPSと応答性能)がメモリベースのDBというリスクを上回る場合、Redis Clusterをデータ保存用に使用することが有利です。このとき、リスクは上記の1を考慮した発生確率で、2と3の方法を通じてリスクを低減できる点を踏まえて評価を行う必要があります。私たちは評価結果、リアルタイム埋め込み用DBとしてRedis Clusterを採用する場合、メリットがリスクよりはるかに大きいと判断しました。
「Redis Clusterをキャッシュとして使えばいいのでは?」
以下の2つの案について、一度に複数の案件の取得と応答がすべて完了したときにデータを使用する必要がある場合、体感できる差は非常に大きいです。
- A:Redis Clusterをキャッシュとして使用し、他のDBをメインで使用
- B:Redis Clusterをメインで使用し、他のDBに二重書き込みしてバックアップとして使用
例えば、キーが違う100個の埋め込みをリクエストして応答を受けた場合、AとB、それぞれの状況に対するキーごとの応答時間が以下のようになるとします。
A: (10, 14, 15, 120, 15, 112, ... ,10)
B: (10, 12, 15, 12, 11, 15, ... , 20)
(単位: ms)
このとき、最終応答時間が、AはMAX(A) = 120ms、BはMAX(B) = 20msです。ここで、Aのみにある100msを超える応答時間は、キャッシュヒットが発生せず、他のDBで応答した時間が含まれた時間です。つまり、Aのようにキャッシュヒット率が100%でない状況で他のDBの応答速度が遅くなると、Bに比べて応答時間の面で 非常に不利になります。したがって、高速な応答速度が重要だった今回のプロジェクトには、AのようにRedis Clusterをキャッシュとして使う方法は適切ではありませんでした。
Redis Clusterをどのように使うのか?
選定されたDBとしてのRedis Clusterをシステム要件に合わせて使うためのプロセスを説明します。
データモデリング
データモデリング(以下、モデリング)はRedis Clusterに実際にどのような形でデータを保存するかを決める段階です。
モデリングの際には、以下の観点で十分に検討する必要があります。
- Redis Clusterは、キーをハッシュしてデータを分散配置します。そのため、キーが均等に分散されなければ、高い性能を得られません。キーが分散されず、特定のスロットにリクエストが集中する現象をホットスポット(hot spot)と言いますが、ホットスポットが発生すると、性能が大幅に低下してシャーディングの意味が薄れてしまいます。
- 「Big Key Issue」(参考)が発生しないようにします。Big Key Issueが発生すると、性能低下はもちろん、サービス障害まで発生する可能性があります。
- サービスのデータアクセスパターンを考慮して最適化します。
- Redisでサポートされるデータ型を考慮して設計します。
私たちは上記のような点を考慮してモデリングを行い、Redis hashesをデータ型として採用しました。