こんにちは。LINE VOOMサービスのレコメンドシステムを開発しているMLエンジニアのChang Hyun Lee、Jin Woo Baekです。私たちは、LINE VOOMのリアルタイムレコメンドシステムのための大規模なベクトルデータベース(以下、ベクトルDB)を導入するプロジェクトを行いました。この記事では、その導入過程について詳しく紹介したいと思います。
LINE VOOMとは
まず、私たちが開発しているサービス、LINE VOOMを紹介します。LINE VOOMは、LINEアプリ内で提供している動画コンテンツを中心としたソーシャルネットワークサービスです。現在、日本と台湾、タイでサービスを展開しています。
LINE VOOMでは、誰でもコンテンツクリエイターになってコンテンツをアップロードできます。ユーザーはさまざまなコンテンツを閲覧、視聴できます。For youタブでは、ユーザーが興味を持ちそうな動画を連続して視聴可能で、Followingタブでは、フォローしたクリエイターのコンテンツをまとめて見ることが可能です。

この記事では、主にFor youタブで提供されるレコメンドプロセスについて説明します。For youタブは、ユーザーにパーソナライズされたコンテンツの推薦結果を提供するタブです。ユーザーのコンテンツに関するフィードバック、例えば、以前に視聴またはクリックしたこと、「いいね!」を押したことなどのフィードバックを基に、ユーザーが好みそうなコンテンツを抽出して提供します。
従来のレコメンドシステムの課題
以前使用していたレコメンドシステムの全体的な動作フローは、以下のとおりです。

- 投稿の作成:ユーザーが動画の投稿を作成します。
- 投稿埋め込みの生成:作成された投稿に対する埋め込みを生成します。
- 投稿埋め込みの保存:生成された埋め込みをベクトルデータベースに保存します。
- 類似投稿の検索(ANN(approximate nearest neighbor)):類似性検索を通じて類似投稿を検索します。
- レコメンド投稿のリクエストおよび取得:ユーザーにレコメンドされた投稿を提供します。
このプロセスを通じて、ユーザーは自分の興味に合ったさまざまなコンテンツを容易に閲覧できるようになります。しかし、一つ課題がありました。
課題:即時性の欠如
従来のレコメンドシステムは、投稿の埋め込みを生成・保存した後、推薦候補群を抽出し、インメモリデータベースに保存する一連のプロセスを、すべて日単位のオフラインバッチ処理で行っていました。そのため、推薦候補群の更新に最大で1日かかるという限界があり、この限界はユーザー体験に以下の問題を引き起こしていました。
- クリエイターAが新年を迎え、「Happy New Year」という内容の動 画を投稿しても、この動画がすぐにユーザーにレコメンドされません。
- クリエイターBがサッカーワールドカップの試合中にゴールを決めたシーンを収めた動画を投稿しましたが、この動画もすぐにユーザーにレコメンドされません。
このように、新しいコンテンツをすぐに閲覧できないという「即時性の欠如」の問題は、ユーザー体験を低下させました。この課題を解決するために、システムを改善する必要があり、リアルタイムレコメンドシステムを実装するプロジェクトを開始しました。
このプロジェクトの目標は、ユーザーが投稿したコンテンツを即座に推薦候補群に反映し、より新鮮な投稿をユーザーにレコメンドすることでした。そのために、従来のシステムの多くの部分を変更しました。日単位で候補群プール(ポストプール)を生成していた方式をモデルベースのリアルタイム候補群生成方式に変更し、ベクトル検索の方式をオフライン演算からオンライン演算に切り替えました。
この記事では、その中でもベクトル検索構造の変更に焦点を当てて説明します。
新しいリアルタイムレコメンドシステムの構造と、ベクトルDBが必要だった理由
新しく開発したリアルタイムレコメンドシステムの全体的な動作フローは、以下のとおりです。

上記のようにリアルタイムレコメンドを実装するため、以前のレコメンドシステムではオフラインで行っていた作業をオンラインに切り替える必要がありました。そのためには、2つの作業が必要でした。まず、投稿の埋め込みを保存する場所をオフラインストレージからオンラインストレージに切り替える必要がありました。次に、以前は類似性検索を通じてレコメンド可能なすべての投稿をオフラインで事前に見つけ、その結果をオンラインストレージに保存していましたが、それを中間保存のプロセスなしで即座に類似性検索を行う必要がありました。
私たちは、オンラインストレージとリアルタイムの類似性検索という、この2つの機能を実装するには、ベクトルDBを導入する必要があると判断し、複数のベクトルDBプラットフォームを検討し始めました。
ここまでのプロセスをまとめます。
- 従来のレコメンドシステムは、オフラインストレージとバッチ処理を通じて類似性検索を行っていたため、即時性が低いという課題がありました。
- 従来のレコメンドシステムの主な課題は、投稿が作成されてから推薦候補群に含まれるまでに、最大で1日かかるという点でした。そのため、ユーザーにすぐに投稿を提供できていませんでした。
- 従来のシステムの課題を解決するために、オフライン作業をすべてオンラインに切り替えることにしました。そのためには、投稿埋め込みをオンラインストレージに保存し、中間プロセスなしでリアルタイムに類似性検索を行うシステムに改善する必要がありました。
- オンラインストレージと類似性検索をリアルタイムに処理するには、ベクトルDBを導入する必要がありました。
ベクトルDBの選定基準とMilvusを選択した理由
最近、ベクトルDBのエコシステムが発展し、さまざまな選択肢が生まれました。私たちは、その中から適切なプラットフォームを選定するために、以下のような基準を設定しました。
- ベクトル専用DBであること
- オープンソースであること
- オンプレミス環境に直接構築できること
- リアルタイムレコメンドのため、高負荷でも安定して動作し、レイテンシが低いこと
このような基準で調査した結果、適切なフレームワークはMilvusとQdrantでした。これから、その2つのフレームワークを比較してみます。
| 基準 | Milvus | Qdrant |
|---|---|---|
| パフォーマンス(QPS(query per second)、レイテンシ)(参考) | 2406 req/s, 1ms | 326 req/s, 4ms |
| ストレージ/コンピューティングの分離 | O | X |
| ストリームとバッチのサポート | O | X |
| GitHubスター数(コミュニティ規模と活性度の間接的測定) | 35.9K stars | 24.6K stars |
| サポートするインメモリインデックスタ イプの数 | 10 (参考) | 1 (HNSW のみサポート) |
Milvusは、パフォーマンス面でQdrantより優れており、ストレージとコンピューティングが分離されているため安定性もさらに高かったです。また、多様なインデックスアルゴリズムをサポートしているため、さまざまな実験を通じて私たちのシナリオに合わせてパフォーマンスを最適化できる可能性が高いと判断しました。ストリームとバッチ作業が分離されており、オンラインINSERTとオフラインバルクINSERT機能をサポートしているため、今後、埋め込みのアップデートに対応する機能も実装できると考えました。さらに、活発なコミュニティのおかげで問題発生時に情報を簡単に入手できるという利点もありました。
以上の理由から、私たちはMilvusを採用することにしました。
Milvusについて
Milvusは、数十億以上の大規模なベクトルデータに対する類似性検索を前提に設計されているため、以下のように複雑な構造になっています。大規模なベクトルデータに対する類似性検索が目的であったため、クラスタ版では公開された使用事例が少なく、そのため、パフォーマンスや安定性がどうなるかは不確実な要素として残っていました。

私たちはこの不確実性を解消し、Milvusの導入を成功させるために、パフォーマンスと安定性を徹底的に検証する必要がありました。そこで、カオステストとパフォーマンステストを実施しました。
まず、カオステストでは、システムがどれだけ安定して動作するかを確認し、もしシステムが不安定に動作する状況があれば、それをどのような方法で安定化できるかに重点を置いてテストを行いました。
次に、パフォーマンステストでは、ANNベンチマークで確認できるベクトル検索のパフォーマンスが私たちの環境でも再現できるか、限られたリソースでスループットを最大化し、応答時間を最小化できるかなどに重点を置いてテストを行いました。
では、各テスト方法と結果を一つずつ見ていきましょう。
Milvusの検証1:カオステスト
カオステストは、システムの安定性と回復力を評価するために、意図的にシステムに混乱を引き起こすテスト方法です。私たちは、クエリ、検索(search)、挿入、削除、インデックス作成、ロード、リリースなどのAPIセットを用意し、特定のAPIリクエストを継続的または繰り返しMilvusクラスタに送信しながら、Podの強制終了(pod kill)、スケールイン/アウト/アップなどの状況を注入し、モニタリングを行いました。

モニタリング時には、対象コンポーネントが復旧可 能かどうか、復旧にかかる時間、復旧される前に発生する問題などを綿密に観察・検討しました。このようにモニタリングしたおかげで、今後障害につながる可能性のある問題を発見し、予防措置を講じることができました。では、テスト結果とともに、どのような問題があり、それを解決するためにどのような措置を講じたのかについて説明します。
テスト結果
まず、以下はテスト結果から主なものをまとめた表です。
| API | カオスシナリオ | 結果 |
|---|---|---|
| Search | Scale in Querynode | 正常動作 |
| Search | Scale out Querynode | 正常動作 |
| Search | Kill Querynode | 正常動作 |
| Search ※ | Kill Querycoord ※ | コレクションがリリースされ、類似性検索が利用できなくなった ※ |
| Search ※ | Kill Etcd(過半数以上) ※ | クラスタがダウンし、コレクションのメタデータが失われたため検索不可能になった ※ |
| ... | ... | ... |
| Insert | Kill Datanode | 正常動作 |
| Insert | Kill Querynode | 正常動作 |
| Insert | Kill Proxy | 正常動作 |
| Create Index ※ | Kill Indexcoord ※ | インデックスの作成に失敗し、復旧に102秒かかった ※ |
赤字(表内の※箇所)で表示したシナリオは、実際のサービスで障害につながる可能性がある深刻な問題だったため、これを予防するために安定性を高める措置を講じる必要がありました。Milvusのシステム安定性を高めるために、私たちが適用したいくつかの解決策を紹介します。
コレクションの高可用性構成
まず、「Kill Querycoord」シナリオのようにコレクション(collection)が突然リリースされ、類似性検索が利用できなくなることを防ぐため、以下のようにコレクションのコピーを一つ用意して、埋め込みを二重に記録する方法(dualwriting)でコレクション単位の高可用性(high availability、HA)構成を実装しました。コレクションからベクトルを検索するクライアント側では、エイリアス(alias)を使用して、一つのコレクションが利用できなくなった場合でもすぐに別のコレクションを参照するように構成しました。
