はじめに
こんにちは。コンシューマ事業部バックエンドエンジニアの高橋です。
今回は食事メニュー検索機能にOpenSearchを導入したことについて、お話しさせて頂こうと思います。
あすけんメニュー検索画面
なぜ導入しようと思ったのか
あるデイリースクラムにて「『”水羊羹 とらや”』では検索できるのに、『”水ようかん とらや” 』では検索できない」という指摘がでてきました。確かにそれはユーザーにとって検索できない理由がわからず、ユーザビリティが低いと感じました。また、以前から「探したい食品がなかなか検索でヒットしない」という改善要望もありました。それらが今回の導入のきっかけです。
以前のメニュー検索
以前の検索ではデータベースに対しSQLの部分一致検索を行っていました。
OpenSearchで検索を行える状態にするためにはデータベースからOpenSearchへデータを同期する必要があり、また、これらのデータは弊社の栄養士によって日々改善が行われていることを考慮した同期方法を考えました。
MySQLからOpenSearchへのデータの同期方法
別システムで日々弊社の栄養士によってデータの更新は行われるものの、そこまでのリアルタイム性は求められていないことから、1日1回の同期バッチを実行するようにしました。
OpenSearch内のIndex洗い替えは下記のようなワークフローを作成し、データ同期用Aliasに設定したIndexにデータ同期し、検索用Aliasに設定済みだった古いIndexとデータ同期後のIndexを入れ替える処理を考えました。そうすることで、入れ替わるのは本体データのみとなり、検索部分の処理に影響させずに自動でデータの更新を行えるようになります。
失敗時のロールバックに関しては、最新のデータ同期成功日から数日分の過去データIndexを残しておくようにし、最新の成功日のIndexに切り替えることで、失敗の原因究明から復旧までの間も極力検索性能の劣化がないようにしました。
OpenSearchによる検索改善方法
今回はより自然に日本語を解釈させる方法として、形態素解析*1を採用しました。
形態素解析としては、kuromojiとsudachiというプラグインを比較し、適合率*2が高かったsudachiを採用しました。
以前の検索は部分一致を行っていたので、その検索結果をある程度担保する目的でN-gramも採用しました。
形態素解析は適合率が高くなるが再現率*3が低くなる、N-gramは再現率が高くなるが適合率が低くなるので、一般的にこれらはトレードオフの関係にあります。
形態素解析とN-gramの両方をIndex化し、どちらに対しても検索を行う方法で改善することで、適合率と再現率をいい按配でチューニングできたと思います。
(詳細はこちらを参照:全文検索を日本語向けにチューニングする)
その他の改善としては、ユーザーの検索キーワードからの登録率などを解析し、ユーザー辞書の内容を拡充させていくことで、あすけんユーザーに特化した辞書を作成する活動をしました。
analyzerの設定例
Index時も検索時も同じanalyzerを使用するものとします。
上記でも述べている通り今回はsudachiを使用しています。
"analysis": { "tokenizer": { "ngram_tokenizer" : { "token_chars" : [ "letter", "digit" ], "min_gram" : "1", "type" : "ngram", "max_gram" : "2" }, "sudachi_b_tokenizer": { "type": "sudachi_tokenizer", "additional_settings": "{\"systemDict\":\"system_core.dic\"}", "split_mode": "B", "discard_punctuation": true } }, "analyzer": { "b_analyzer": { "type": "custom", "filter": [ "sudachi_normalizedform", "sudachi_baseform", "sudachi_part_of_speech" ], "tokenizer": "sudachi_b_tokenizer" }, "ngram": { "type": "custom", "char_filter" : [ "icu_normalizer" ], "tokenizer": "ngram_tokenizer" } } } }
mappingの設定例
Indexさせたい1つ1つの項目(titleやdescription等)に対して個別にmappingすることもできますが、それだと定義するのが大変なので、dynamic_templatesを使ってIndexのマッピングを楽に行うことができます。
"mappings": { "dynamic_templates" : [ { "string_template" : { "match" : "*", "match_mapping_type" : "string", "mapping" : { "fields" : { "analyzed" : { "analyzer" : "b_analyzer", "type" : "text" }, "ngram" : { "analyzer" : "ngram", "type" : "text" } } } } } ] }
詳しい説明は参考をご参照いただくとより理解が深まります。
苦労したこと
多くのユーザーにご利用いただいている分、以前の検索結果で慣れているユーザーも多く、改善する部分と既存と変えない部分を考慮したチューニングをする必要がありました。
最終的にはユーザーに使ってもらいながら改善していくことが良いと判断し、段階的にリリースを行い実際にユーザーの食事メニュー登録率やレビューを日々確認していくことで、ユーザーに影響が出ていないか効率的に判断しながら作業を行うことができました。
導入した結果
改善結果の指標としてユーザーの食事メニュー登録率を改善前と改善後で比較したところ、変化なしという結果になりました。
登録率に影響するほどの結果は今のところ確認できていませんが、今後の活動次第でまだまだ向上できる可能性がありますので、これからも取り組んでいきたいと思います。
ちなみに「水ようかん とらや」は検索できるようになりました。
まとめ
今回はあすけんのメニュー検索にOpenSearchを導入してみました。
これからもより精度を上げていくためにAIの導入など常に最善の改善ができるよう取り組んで参りたいと思っております。
これが誰かの参考になれば幸いです。
askenでは創意工夫して業務を遂行できるエンジニアを幅広く募集しています。
興味を持っていただいた方はカジュアル面談からまずはお話ししませんでしょうか?!
お気軽にご連絡ください。
www.asken.inc