検索エンジンをVespaへ移行しています
こんにちは、スタンバイで検索周りの開発を担当している鷹取です。 今回はスタンバイで利用している検索エンジンをVespaへ移行している話を紹介します。
検索エンジン移行の背景
Stanby Tech Blogのスタンバイ2+1年の軌跡の記事で説明されている通り、 スタンバイでは、主に求人検索機能を提供していますが、その中でもオーガニック(無料掲載)と広告(有料掲載)という2種類の検索が存在します。
この2種類の検索ではそれぞれで異なる検索エンジンを使用しています。
- オーガニック検索: Yahoo! ABYSSという検索プラットフォーム
- 広告検索: Elasticsearch
このようになっている背景については、前述の記事に詳細が記載されていますので、興味がある方はそちらをご参照ください。これまで、この2種類の検索エンジンを運用してきましたが、それぞれに課題を抱えていました。
オーガニック検索における課題
オーガニック検索における課題としては、 まず、検索エンジン自体を利用できなくなるリスクです。 ABYSSの提供が終了した場合、スタンバイのサービス全体が停止してしまう可能性があります。
次に、開発上の制約があります。 スタンバイはABYSSという検索プラットフォームの利用者であるため、エンジン自体の開発ができません。その結果、検索エンジンの開発や精度の向上において、自社で完結できず他社に依存しています。
また、スケーリングの点でも問題が生じています。 契約の都合上、クラウドサービスのように即座にサーバ台数を増減できず、 トラフィックに応じた柔軟な変更ができません。 このため、常に最大のトラフィックに耐えられるサーバ台数を確保しておく必要があります。
最後に、経験と知識の面での課題も挙げられます。 他社の検索エンジンを利用していると、 スタンバイ内で検索エンジン開発の知識や運用経験が蓄積されず、 高度な検索エンジンの開発や運用が難しい状態にあります。
広告検索の課題
広告検索の課題としては、独自に開発しているプラグインの開発・メンテナンスの負担が挙げられます。 スタンバイでは、Elasticsearchの独自プラグインを開発し、ランキング機能を実現しています。 このプラグイン導入当時、既存のOSSプラグインでは、実現したい仕様や性能を満たせなかったため、独自で実装することにしました。 しかしながら、ランキング変更のたびにプラグインの改修をしなければならず、改善のボトルネックとなってしまっています。 また、Elasticsearchのバージョンを上げるたびに対応が必要です。加えて、インフラコストの問題もあります。 上記のプラグインの影響により、性能を満たすために必要なサーバの台数が多くインフラコストの増加につながっています。
共通の課題
さらに、オーガニックと広告で異なる検索エンジンを運用していることによる課題も存在しています。 異なるエンジンを利用していることから、精度改善のための実装がそれぞれのエンジンで共有できず、 同じ施策でもオーガニックと広告で別々に実装しなければなりません。これにより、実施できる全体の精度改善施策の数が減少しています。 また、学習コストや運用にかかるコストも2倍になり、本来の検索改善を行うための時間が減少してしまっています。
検索エンジンの統一の決定
これらの課題を解消するため、以下のような目的で検索エンジンを統一し、自社で運用する方針が決定されました。
- 社内の検索エンジンを統一することで、エンジニアリングリソースを集約し、検索エンジン運用と検索サービス開発の効率を上げる
- 自社で検索エンジンを運用することで、情報検索技術や検索エンジンに関する深い知識を社内に蓄積する
- システム提供、ソフトウェアライセンスに関して自社でコントロールできない制約事項を可能な限り排除し、自由度高く検索サービスの開発を行えるようにする
検索エンジンの選定
検索エンジンを統一するにあたって、スタンバイでは最終的にVespaを採用しました。 ここからは、なぜVespaを選んだかについて説明します。
スタンバイの検索の特徴
検索エンジンの選定に当たって、まずは現状の検索の特徴を把握する必要があります。 スタンバイの検索の特徴をまとめると以下のようになります。
検索
- 高トラフィック
- 国内有数の求人検索エンジン
- サービスの成長に伴い今後も更に増加見込み
- 低レイテンシー
- スタンバイは検索が主体なので、検索結果画面の表示に時間がかかると、ユーザが離脱してしまいます。広告も検索なので、売上の低下に直接つながってしまします。
- 高トラフィック
更新
- 検索対象のドキュメント量が多い
- 1000万件以上の求人データを扱っています。また、求人検索エンジンの特性上、特定の求人だけ検索できるのではなく、すべての求人が等しく常に検索できる状態になっている必要があります。
- ドキュメントの登録と削除が頻発する
- 求人票は各社の採用状況に応じて、頻繁に公開、非公開が行われます。
- 新規公開求人の反映速度も重要ですが、特には応募時のトラブル防止のために求人票の取り下げや更新については求人データがスタンバイに連携されたら即時反映させる必要があります。
- 後述する機械学習に使うための、特徴量データの反映のための部分更新も必要となります。
- 検索対象のドキュメント量が多い
機械学習
- スタンバイでは、検索結果のランキングに機械学習モデルを活用しています。
- GBDTモデルを用いたランキング
- two-phase ranking
- 機械学習モデルを作成している機械学習チームと検索基盤チームが別
- 独自のプラグインだと、機械学習チームが検索基盤チームに依存してしまいます
- それぞれで改善を行えるような体制が望ましいです。
- スタンバイでは、検索結果のランキングに機械学習モデルを活用しています。
次に検索エンジンVespaの概要と特徴について説明します。
Vespaとは
Vespaとは、オープンソースのbig data searving engineです。 オンラインでビッグデータにAIを適用できることが特徴です。
Vespaは検索に限らず、レコメンドや会話AIなど、様々な用途に利用できます。 もともとは、Yahoo(米)の社内で開発されていましたが、 2017年にオープンソース化1され、2023年10月にはYahoo(米)からスピンアウトし独立した企業となりました。2
Yahooの検索での長い実績があり、大規模な量のドキュメントとトラフィックに対応できる能力が証明されています。 1日に25Bのリアルタイムクエリと75Bのライティング(更新)を処理可能です。 また、Spotifyなどのグローバルに大規模なサービスを展開する企業でも採用され始めています。3
Vespaの特徴
高速な検索と高いスケーラビリティ
Vespaはリアルタイムで低レイテンシかつ高スループットが求められるユースケースに最適化されています。 数十ミリ秒以下のレイテンシでレスポンスを返すことが可能です。 また、並列にクエリを実行することで、どのようなクエリ量、データ量でも一定の応答時間を維持できるように設計されています。 さらに、クエリごとに複数のサーチャースレッドを活用し、スループットに対してレイテンシを柔軟にスケールできます。
また、Vespaは高いスケーラビリティも備えています。 Vespaのドキュメントはバケットと呼ばれる単位で管理されます。 バケットのサイズと数はVespaによって完全に管理され、 手動でシャーディングを制御する必要はありません。 そのため、ノードをクラスタに追加するだけで簡単にスケールできます。 将来的にアクセスが増加してもスケールアウトが容易です。
効率的なインデックス作成
Vespaのインデックス作成メカニズムは低レイテンシでの更新に最適化されており、常にデータが変化するシナリオに適しています。
通常の検索エンジンのインデックス作成は、リアルタイムの書き込みを実現するために、 書き込みに対してイミュータブルな転置インデックスのセグメントを構築し、 バックグラウンドでそれらをマージすることで行われます。 大きなセグメントとのマージには非常に長い時間がかかるため、 多くの場合、徐々に大きくなる複数のセグメントを使用し、複数回マージする必要があります。 この方式の場合、高い書き込みレートを維持すると、 クリーンアップしなければならない多くのゴミを作成することになります。 これにより、安定した書き込みレートとクエリレートの維持に問題が生じます。
Vespaも以前はこの方式でしたが、2010年以降は異なる方式を採用しています。4 Vespaはイミュータブルなインデックスセグメントの前にミュータブルなインメモリインデックスを持ちます。 変更はインデックスセグメントの代わりにメモリ内のB-treeに書き込まれ、 バックグラウンドで不変なインデックスとマージされる設計に変更されました。 この設計ではインデックスセグメントを徐々に大きくしていく必要がなく、 ガベージコレクションや大きなメモリ操作の発生が非常に少ないというメリットがあります。 また、更新リクエストが完了した時点で、そのドキュメントは検索可能になります。
豊富な機械学習関連の機能
VespaはそもそもビッグデータセットにAIを適用するためのプラットフォームとして作られているため、 機械学習関連の機能が豊富にあります。
例えば、以下のような機能を持ちます。
- ONNX,XGBoost,LightGBMといった複数のモデルサポート
- weightedsetやtensorなどのデータ型が使用可能
- multi phase rankingのサポート
Vespaではrank-profileとよばれる形式でランキングを設定します。 rank-profileでは、様々なランク式や特徴量を組み合わせてランキングのアルゴリズムを定義できます。 また、rank-profileは設定ファイルとしてVespaクラスタに直接デプロイします。 これにより、ランキングアルゴリズムを検索クエリとは分けて管理できます。
運用面でも、Vespaは本体に組み込みで機械学習の機能を持っているため、 プラグイン等を管理する手間がありません。
スタンバイの検索とVespaの相性
スタンバイの検索の特徴とVespaの特徴を以下にまとめました。
スタンバイの検索 | Vespa | |
---|---|---|
検索 | 低レイテンシ 高トラフィック |
高速な検索 高いスケーラビリティ |
更新 | 検索対象のドキュメントが多い ドキュメントの更新量が多く高頻度 |
効率的なインデックスの作成 |
機械学習 | 機械学習を活用 機械学習チームと検索基盤チームが別 |
豊富な機械学習関連の機能 rank-profileによるランキングアルゴリズムの指定 |
このように、上記で述べたスタンバイの検索の特徴とVespaの特徴がマッチしていたため、Vespaを採用しました。
次章から、具体的にVespaに移行した方法を紹介します。
Vespaへの移行
Vespaへの移行は、以下のようなステップで進めていきました。
- Vespaの調査・機能検証
- Vespaクラスタの構築
- 機能開発
- テスト
Vespaの調査・機能検証
現在提供している検索仕様をVespaで全て満たせるかどうかの調査を実施しました。 実際の移行可能性を確認するために、公式ドキュメントを読みこみ、その後、ローカル環境でクエリを作成して検証しました。
Vespaは公式ドキュメントが充実しているため、 ドキュメントを読めば、どのような機能があるか、どのように使えばいいかがわかります。ただし、ドキュメント量は多いので、全ては読み切れておらず、少しづつ読み進めています。また、VespaのSlackでは開発チームに直接質問を投げることができます。ここで、過去の質問を検索することでも、参考になります。 VespaはDockerイメージも公式で提供されており、ローカルでの検証も簡単にできました。
ここからは、調査したVespaの機能をかいつまんで紹介します。 詳細は、Vespaの公式ドキュメントを参照してください。
Vespaの紹介 - Vespaのアーキテクチャ
最初に、Vespaのアーキテクチャを説明します。 Vespaのアーキテクチャは以下の図のようになっています。
図のように、Vespaは複数のコンポーネントから構成されています。
コンポーネント | 説明 |
---|---|
Admin/Config | 設定の管理、クラスタの制御など |
Stateless Java Conatiner | 入力データやクエリ・レスポンスを加工するステートレスなコンポーネント。クエリとデータの操作をコンテンツクラスタの適切なノードに渡す。Javaで実装されており、プラグインで容易に拡張できる 。 |
Content | インデックスの管理を担当する。検索・ランキングはここで実行される。C++で実装されている |
Vespaクラスタに属する各サーバは、これらのいずれかの役割を担い協調して動作します。
Vespaの紹介 - クラスタの設定
先ほど、アーキテクチャについて紹介しました。 Vespaでは設定ファイルで、クラスタの構成を管理します。 ここでは、Vespaのクラスタ設定について説明します。 クラスタ設定は、以下の2種類のファイルで行います。
- hosts.xml
- services.xml
hosts.xml
hosts.xmlはクラスタに参加するサーバを定義します。 ホスト名に対して、設定の中で使うエイリアスを指定します。 以下が、hosts.xmlの例です。
<?xml version="1.0" encoding="utf-8" ?> <hosts> <host name="node0.vespanet"> <alias>node0</alias> </host> <host name="node1.vespanet"> <alias>node1</alias> </host> ... </hosts>
services.xml
services.xmlはVespaクラスタの構成を定義します。 hosts.xmlで定義された各サーバに役割を与えます。 また、redundancyといった冗長化の設定やプラグインの設定およびスレッド数・メモリ等のリソース設定もこちらで行えます。 以下がservices.xmlの例です。
<?xml version="1.0" encoding="utf-8" ?> <services version="1.0" xmlns:deploy="vespa" xmlns:preprocess="properties"> <admin version="2.0"> <configservers> <configserver hostalias="node0" /> <configserver hostalias="node1" /> <configserver hostalias="node2" /> </configservers> <cluster-controllers> <cluster-controller hostalias="node0" jvm-options="-Xms32M -Xmx64M" /> <cluster-controller hostalias="node1" jvm-options="-Xms32M -Xmx64M" /> <cluster-controller hostalias="node2" jvm-options="-Xms32M -Xmx64M" /> </cluster-controllers> <slobroks> <slobrok hostalias="node0" /> <slobrok hostalias="node1" /> <slobrok hostalias="node2" /> </slobroks> <adminserver hostalias="node3" /> </admin> <container id="feed" version="1.0"> <document-api/> <document-processing/> <nodes> <node hostalias="node4" /> <node hostalias="node5" /> </nodes> </container> <container id="query" version="1.0"> <search/> <nodes> <node hostalias="node6" /> <node hostalias="node7" /> </nodes> </container> <content id="news" version="1.0"> <min-redundancy>2</min-redundancy> <documents> <document type="news" mode="index" /> <document-processing cluster="feed" /> </documents> <nodes> <node hostalias="node8" distribution-key="0" /> <node hostalias="node9" distribution-key="1" /> </nodes> </content> </services>
Vespaの紹介 - ドキュメントの管理
つぎに、Vespaのドキュメント管理について紹介します。
Vespaでは、ドキュメントのスキーマを、.sd
という拡張子のスキーマ定義ファイルで管理します。
スキーマ定義ファイルの例を以下に示します。
schema news { document news { field news_id type string { indexing: summary | attribute attribute: fast-search } field title type string { indexing: index | summary index: enable-bm25 } field abstract type string { indexing: index | summary index: enable-bm25 } field url type string { indexing: index | summary } field date type int { indexing: summary | attribute attribute: fast-search } field clicks type int { indexing: summary | attribute } field tensorfield type tensor<float>(x{},y{}) { indexing: attribute | summary } } fieldset default { fields: title, abstract } }
field
キーワードに続けてフィールド名と型を指定します。
intやstringといった基本的な型のほか、tensorやweightedsetといった複雑な型や構造体の定義も可能です。
また、フィールドごとにインデクシングの方法や検索方法を指定できます。
例えば、indexing
パラメータを指定することで、インデックス作成時にフィールドのデータをどのように処理するかを設定します。
indexing
には以下の3つの値を指定できます。また、複数組み合わせての指定も可能です。
index
- テキストマッチ用のインデックスを作成します。形態素解析が行われます。
attribute
- メモリに保持します。ソートやグルーピングに使用可能です。また、完全一致、プレフィックス一致、大文字小文字を区別する一致などが可能です。
summary
- 検索結果のレスポンスに含まれるドキュメントの情報(summary)に指定されたフィールドを含めます。
また、index
パラメータやattribute
パラメータを指定することで、検索の高速化なども可能です。他にも、fieldset
を使うことで、検索用にフィールドをグループ化できます。
注意点として、Vespaでは動的なフィールドは作成できません。 フィールドを追加する場合は明示的に指定する必要があります。
Vespaへのドキュメントの登録方法ですが、/document/v1/
APIにHTTPリクエストを送る、
もしくは、Java製のfeed-clientでフィードを行うことができます。
/document/v1/
APIは検索用のAPIと別のAPIで、
IDを指定したドキュメントの取得や、登録・更新・削除が可能です。
Vespaの紹介 - 検索の方法
続いて、Vespaの検索方法について紹介します。
Vespaの検索クエリはYQLというSQLに似たDSLで記述します。
select * from news where title contains "vespa"
select
でレスポンスに含めるフィールドを指定します。スキーマ定義でsummary
を指定したフィールドを選択できます。from
では検索対象のドキュメントタイプを指定します。
そしてwhere
でさまざまな検索条件を指定します。上記の例では、titleフィールドに"vespa"を含むドキュメントを検索しています。他にも、フレーズ検索や緯度経度による検索など、様々検索が可能であり、
Apache SolrやElasticsearchで提供されている基本的な検索機能は一通り揃っています。
また、ランキング後に検索結果をグルーピングする機能も提供されており、集計やdedupe5なども可能です。
検索リクエストをVespaに送る際には、yql
だけでなく、タイムアウト時間やヒット件数など検索に関する他のパラメータも同時に指定可能です。
特に使用するrank-profileの名前を指定することで、検索結果のランキングをクエリごとに変更できる機能は、オンラインABテスト時などに便利です。
{ "hits": 200, "model": { "locale": "ja" }, "timeout": "1s", "offset": 0, "ranking": { "profile": "vespa-test" }, "yql": "select * from news where default contains \"vespa\"" }
また、rank-profileとは別のquery-profileとよばれる機能を使うことで、検索パラメータのセットを名前をつけて管理できます。 これにより、検索時にquery-profile名だけ指定すれば良く、 毎回多数のパラメータを付けてリクエストせずにすみます。 query-profileは以下のように設定します。
<query-profile id="MyProfile"> <field name="hits">20</field> <field name="maxHits">2000</field> </query-profile>
Vespaの紹介 - ランキングの指定
最後にVespaのランキングの指定方法について紹介します。
本記事で何度も出てきていますが、Vespaのランキングはrank-profileで設定します。 rank-profileは以下のように設定します。
rank-profile my-rank-profile inherits base { function myfeature() { expression: fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness, 2) } first-phase { expression { attribute(quality) * freshness(timestamp) } } second-phase { expression: lightgbm("test_model.json") rerank-count: 50 } }
rank-profile
キーワードのあとに、rank-profileの名前を指定します。
inherits
を使うことで、他のrank-profileを継承できます。
これは、ABテストなどで、ランキングの一部を変更したい場合(second-phaseのみ変更するなど)に非常に便利です。
function
でランキング式の一部として、また、特徴量として使用可能な独自の関数を定義できます。
さらに、Vespaでは多段階ランキングでランキングの負荷と精度のバランスをとることが最初から可能です。
first-phase
で負荷が軽いランキング式を指定し、
second-phase
はfirst-phaseでランキングされた上位の結果に対して、
負荷が重いがより精度の高いランキング式を指定します。
Vespaクラスタの構築
ここまで、Vespaの機能の一部を紹介しました。 ここからは、実際にスタンバイでVespaクラスタを構築した方法を紹介します。
クラスタの構成
以下が現在のVespaクラスタの構成です。
スタンバイではAWSを使用しています。 そのため、各サーバはEC2を使用してVespaクラスタを構築しています。 high-availability(HA)のため、multi-node構成をとっており、 admin/configクラスタは3台のノードから構成されています。 containerクラスタは、フィードを処理するためのクラスタと検索クエリを処理するためクラスタを分けています。 これにより、フィード、クエリそれぞれで、負荷に応じてノードの性能・台数を 変更できるようにするとともに、フィードの負荷が検索へ影響しないようにしています。 containerクラスタはステートレスであるため、簡単にスケールアウトが可能です。 各クラスタごとのインスタンスタイプは負荷に応じて異なるものを使用しています。 contentクラスタのノードはデータを保持し検索を処理するため、他のクラスタと比べて大きなインスタンスタイプを使用しています。 また、ディスクへのアクセスを多く行うため、インスタンスストアを持つインスタンスタイプを選択しています。
クラスタの構築方法
つぎに、スタンバイでのVespaのクラスタ構築方法を説明します。
検証中何度もVespaのクラスタを構築し直す必要があったため、インフラをコード化しています。 まず、Vespaがインストールされたマシンイメージ(AMI)の作成します。 AWSのEC2 Image Builderを使用して、ゴールデンイメージを作成します。 この際、VespaだけでなくDatadogAgentなど監視や運用に必要なツールもインストールしています。このように、Vespaをインストール済みのAMIを作成しておくことで、 インスタンス起動時間を短縮できます。また、 検証環境で確認したものと全く同じ状態のノードを本番に構築でき、 以前のバージョンの環境にもどすことも容易になります。
つぎに、Vespaクラスタ用のインスタンスを起動します。
台数やインスタンスタイプ、ネットワーク構成などの設定は、すべてTerraformでコード管理しています。このため、新しいVespaクラスタを構築する際は、既存の設定をコピーし、パラメータを変更してterraform apply
コマンドを実行するだけで簡単に構築できるようになっています。この時点では、Vespaクラスタの設定はまだ反映されていなため、各ノード上でVespaのプロセスは起動していますが、クラスタとして協調して動作できません。どのノードがどの役割を持つかも決まっておりません。それらの設定は前述したhosts.xmlやservices.xmlで行います。
ただし、config serverだけは、VESPA_CONFIGSERVERS
という環境変数をすべてのノードに設定して置く必要があります。これは、Vespaの設定ファイルを管理するconfig serverのホスト名を指定するための環境変数です。この環境変数を設定することで、各ノードはconfig serverに接続し、設定ファイルを取得します。
最後に、Vespaの設定ファイルを反映させます。 Vespaの設定ファイルは、アプリケーションパッケージという単位にまとめられデプロイされます。アプリケーションパッケージにはデプロイと実行に必要なすべての設定、コンポーネント、機械学習モデルが含まれています。 スタンバイでは、Jenkinsを使用してGithub上で管理しているリソースからアプリケーションパッケージを作成し、Vespaにデプロイしています。
アプリケーションパッケージのデプロイが完了すると、各ノードがVespaクラスタとして協調して動作を開始します。
機能開発
次に、検索エンジン移行のために必要だった機能開発について説明します。 以下が、今回実施した開発の一覧です。
- クエリ処理を一箇所に集約
- オーガニックAPI・広告API・バックエンドAPIで、クエリを変換するロジックが別々に実装されていたため、エンジンを統合する前にクエリ処理を行うAPIを作成し統一しました。
- 検索APIの実装
- ユーザから受け取ったクエリをYQLに変換
- rustでAPIを実装
- Feederの実装
- ドキュメント1件毎にVespaへの更新リクエストを発行
- JavaでStream処理を実装
- Linguisticモジュールの実装
- 形態素解析などの言語処理機能を実装
- 機械学習モデルの実装
- 既存のモデルで使用している特徴量を、Vespaで使用できる特徴量にマッピング
- rank-profileの作成
ここでは、特にLinguisticsモジュールの開発について詳しく説明します。
Linguistics
VespaはLinguistics(言語処理)モジュールを使用して、 インデックス作成および検索時にドキュメントやクエリのテキストを処理します。
Linguisticsモジュールは、以下の処理を実装しています。
- 言語特定
- tokenizing
- normalizing(アクセント記号の除去)
- stemming
これらの処理がLignusiticsモジュールで行われた結果のtermがインデックスに追加されます。 また、検索時にも同様に、クエリのテキストに対して処理が行われます。
Lignusiticsモジュールは、containerクラスタで動作するため、Javaで実装されています。 Vespa公式でもいくつかの実装が同梱されていますが、カスタマイズしたい場合には、com.yahoo.language.Linguisticsインタフェースを実装します。
今回は、自分たちでカスタマイズできるように、Linguisticsモジュールも自前実装したものを使用しています。 実装にあたっては、以下のような実装を参考にしました。
- SimpleLinguistics
- 英語のステミングのみ提供
- LuceneLinguistics
- LuceneのAnalyzerを使用した実装
- OpenNlpLinguistics
- OpenNLPを使用した実装
- KuromojiLinguistics
- LINEヤフー株式会社が公開しているKuromojiを使用した実装
特にKuromojiLinguisticsは日本語の形態素解析用の実装のため、非常に参考になりました。
Linguisticsの注意点として、テキストの処理時に言語を特定しますが、 その特定が間違っているとマッチしなくなってしまいう点があります。 特に、クエリに含まれるワードなど、短い単語は言語の特定が困難なため、 検索パラメータ等で言語を指定したほうが良いです。 また、当然ですが検索とフィードで同じLingusitcsモジュールの実装をすることも重要です。
テスト
最後に、テストについても触れておきます。 テストは、一般的な検索改善と同じように、以下の種類のテストを実施しました。
- QA
- 既存の検索機能が実現できているかの確認
- 負荷試験
- 負荷に耐えられるかの確認
- 選定時にある程度の性能評価は行っていたが、モデル等を本番で使用するものを用意し改めて確認
- オフラインテスト
- 定量・定性の両方で評価
- SQ(サーチクオリティ)グループという検索精度を定性的に評価するチームが存在します。
- オンラインテスト
- ABテストを実施
- 検索精度を定量的に評価
特別なことはしていませんが、検索エンジンの移行ということで、入念にテストを行いました。
移行結果
上記のステップで移行を進めた結果、無事に移行できました。 現状はオーガニックのみ移行完了しており、広告は移行中です。
移行中ではあるものの、検索エンジンを統一し、自社運用するメリットが徐々に出てきています。 まず、オーガニックと広告で同じ検索エンジンを使用することで、検索に関するリソースを集約できそうです。 現段階でも、広告の検索エンジンをVespaへ移行するために、オーガニックの知見を活用できています。 また、自社運用することで、Vespaに関することが中心にはなりますが、情報検索技術や知識を蓄積し始めることができています。
難しかったポイント
Vespaの移行に際し、難しかったポイントを紹介します。
まず、覚えなければならないことが多いという点です。 いままで使用していたABYSSやElasticserachとは、 大きく異なる検索エンジンであるため、仕組みを理解するのに時間がかかりました。 単に検索を行うだけであれば、クエリの書き方を覚えるだけで済みますが、 実際に運用をしていくためにはVespaの様々な概念を覚えなければなりませんでした。 Vespaの各コンポーネント上では、proton, config-proxy, config-sentinel, config server, slobrok(Service Location Broker), cluster controllerといった、複数のサービスが動作しています。 運用時、特に何かしらのトラブルが発生した場合の原因を特定する場合、 それぞれのサービスの機能を理解しておく必要があります。
また、日本語の情報が少ないという点もあります。 日本のユーザが少ないため、日本語の知見があまりネット上にありません。 特に言語処理周りは、言語に依存する部分が多く、 自前で実装するにあたっては、コードを読み込んで試行錯誤する必要がありました。
今後について
Vespaへの移行は一部完了しましたが、移行しただけで活用できているとはいえません。 今後は、さらにVespaを活用していく予定です。
まず、検索精度改善についてですが、形態素解析の改善、ランキングモデルのさらなる改善、 ベクトル検索(ANN)の導入などが考えられます。 とくに、Vespaは通常の検索とベクトル検索を組み合わせたハイブリッド検索が可能であるため、 現在の検索仕様を維持しつつ、ベクトル検索を導入することができると考えています。
また、オートスケーリングの実装も検討しています。 スタンバイでは時間帯によって検索ボリュームが大きく変わるため、 コストを減らすためにインスタンス台数を最適化したいです。 VespaCloudでは提供されていますが、self-hostingの場合は機能が提供されていないので、 オートスケーリング機能を自前で実装する必要があります。
まとめ
スタンバイでは検索エンジンをVespaに移行しています。 複数の検索エンジンを使用していましたが、検索エンジンを統一し、自社で運用する方針を決定しました。 統一後の検索エンジンとして、スタンバイの検索の特徴と合致したVespaを採用しました。 今後は、ベクトル検索の導入など、さらなる活用を進めていく予定です。
スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com