BASE株式会社Data Strategyチーム兼 Data Platformチームの楊(@wyang)です。
ショッピングアプリ「BASE」では、前回公開した記事の通り、商品検索基盤をCloudSearchからAWS Elasticsearch Serviceへ移行しました。
この記事では、レスポンス速度改善と検索精度改善をメインにご紹介します。
1. レスポンス速度改善について
1-1. ElasticsearchのProfile API
このProfile APIを利用すると検索クエリを分析し、どんなクエリが発行されたのか、どのくらいの処理時間がかかっているのかなどを簡単に知ることが出来ます。
GET items/_search { "query": { "bool": { "filter": [ { "range": { "date_field": { "gte": "now-180d" } } } ] } }, "profile": "true", // profile: trueで有効になる "size": 0 }
BASEではAWS Elasticsearch Serviceを使っている都合上、X-PackのSearch Profilerを利用することはできませんが、こちらではより手軽にパフォーマンス分析をすることができます。
1-2. 改善された変更点
1-2-1. 時間Rangeクエリの丸め込み
BASEでは長期間でログインされていないショップの制御など、検索の要所要所でdate型データに対する絞り込みをしています。
GET items/_search { "query": { "bool": { "filter": [ { "range": { "date_field": { "gte": "now-180d" } } } ] } } }
しかし、Elasticsearch公式のドキュメント)にもある通り、時刻によるフィルタリングはfilter cacheに載らないため、毎回絞り込みが行われレスポンス速度が低速になる傾向がありました。
ドキュメントのガイドのように時刻を丸め込むことにより、filterクエリがfilter cacheの対象となり、レスポンス速度が大幅に改善されました。
GET items/_search { "query": { "bool": { "filter": { "range": { "date_field": { "gte": "1600819200000" } } } } } }
1-2-2. 必要最低限のフィルターを利用、不要なドキュメントの削除
Profile APIで時間がかかるフィルターにあたりをつけた後、検索インデックスで管理する必要がないフィールドとフィルターを削除しました。この部分は、インデックス更新バッチ側で制御しており、不要なドキュメントをそもそも登録しないなどの対応で、インデックスサイズを軽量化させました。結果として、クエリの軽量化にも繋がり、レスポンス速度が改善されました。
1-2-3. search_analyzerの使用
シノニム辞書を利用した検索を実現するために、インデックス時と検索時でanalyzerを分けて管理しました。 詳しい内容はElastic社公式の記事にもありますが、
- インデックスサイズに影響が出ない。
- 用語の統計全体は同じに保たれる。
- 同義語ルールを変更するにあたり、ドキュメントの再インデックスは必要ない。
など、大きなデメリットはなく、こちらの設計を採用しています。
2. 精度改善について
2.1. A/Bテスト運用
Firebase Remote Config による検索の A/Bテストを実施しています。アプリのアップデートを配布せずとも、Remote Configのコンソール上で設定を修正するだけで、指定の比率でテストサイズを設定できるようになりました。
2-2. A/Bテストの評価指標
実際に改善に繋がったかどうかを以下の指標で判断しています。
- 商品閲覧につながった検索率
- 商品閲覧につながった検索のうち、商品閲覧位置(表示順位)の最小値の平均値
- 検索結果を1件も返せなかった率
- 検索結果のうち上位k件の商品閲覧率(SERP@k)
- 商品お気に入りにつながった率
- 検索実行からレスポンスを返すまでの時間
閲覧率だけでなく他の評価項目(商品の多様性など)も含めました。
2-3. これまでに効果があった変更点
2-3-1. 複数語検索時の絞り込み条件の緩和
ユーザーが複数語で検索した場合、語順を考慮する検索(type: phrase)と、語順を考慮しない検索(type: best_fields)では、語順を考慮した検索のほうが成績が良いことがわかりました。ただし、一部のキーワードの結果に対してはヒット件数が0件になってしまうため、ヒット件数が一定件数以下になった場合に、条件を緩和して語順を考えないクエリで再検索をするようにしています。結果、CTR改善に繋げることができました。
2-3-2. function_score
BASEのおすすめ順では、ユーザが検索したキーワードに該当するドキュメントをスコアリングする際にfunction_scoreを利用しています。function_scoreにより、ショップ情報や、商品情報の複数の要素に対して、それぞれ重みづけをしたオリジナルのスコアを設定することができます。
GET items/_search { ... "function_score": { "functions": [ { "field_value_factor": { "field": "score_field_A" // スコアに関するfield } }, { "gauss": { "date_field_B": { // 日付に関するfield "origin": "now", "scale": "180d", "decay": "0.5" } } } ], "score_mode": "multiply", "boost_mode": "multiply" } }
上記のクエリでは、score_field_Aとdate_field_Bのスコアを乗算してスコアリングをしています。 gaussを利用する場合では、origin(now:現在時刻)を基準として、scale(180d: 180日)の日付差の地点に対して、decay(0.5)の減衰をする正規分布のスコアを設定することができます。
その他
日々登録される商品データのサイズや、更新反映までに求められる時間を考慮し、
- Bulkで投入するリクエストデータのサイズ
- refresh_interval
に対する調整をそれぞれ行い、効率的にドキュメントを更新できる設定にしています。
おわり
今回は移行後におけるレスポンス速度改善と検索精度改善についてご紹介しました。AWS Elasticsearch Serviceへの移行を行うことで継続的な検索性能の改修、改善をしやすい環境を作ることができました。これからさらなる改善を行なっていきたいと思います。