🛒
はじめての OpenSearch
検索機能の実装の際に触る機会があったので、はじめての記録を残しておきます。
やりたかったこと
アニメ作品の名前からアニメ作品を検索したい。ごく一般的なWebサイト内での検索機能ですね。
OpenSearchってなに?
名前の通り、検索機能を提供するサービス。
以下のような用途で使われることが多いようです。
- ログ分析
- AWS CloudWatchLog Insightのもっとすごい版的な使い方らしい。噂。
- エンタープライズ検索
- 社内の資料を一元管理
- アプリ内での検索
- いわゆる検索。 -> 今回はこの用途で使いました。全文検索。
今回は単純な全文検索で利用しましたが、他にも、いろんな機能があるみたいです。意味検索/AI検索など、使ってみたいです。
ながれ
-
- インデックス作成
- 転置インデックスらしい
- 引用: AWSの資料
-
- 検索
- ↑で作成したインデックスに対して、検索ワードを検索する
じっそう
- AWS OpenSearch Searviceを利用
-
Ruby client - OpenSearch Documentation
- Ruby用のgemが用意されていました
-
Ruby client - OpenSearch Documentation
- AWS 使うやり方はこちらでした
- アクセスキー発行が必要
- 内部でAPIいい感じに呼んでくれます
ドキュメント追加・更新
-
bulk
- 100以上ある作品情報を更新する必要があったので、一括で処理できる方法にしました
- 作品情報は逐一更新されていくので、日時のバッチでドキュメントの追加・更新をします
- ポイント
- updateアクションで
doc_as_upsert: true
として、更新 or 存在しない場合追加とする - refreshタイミング
- インデックスのドキュメント追加/更新後に、refreshすると検索できる様になるのですが、しんどい処理のようでパフォーマンスを下げるようです。 -> 詳細
- bulkでまとめて更新後に、refreshを1回だけ実行するようにします
- bulkのパラメータに
refresh: true
とした場合に、全アクションに対して1回refreshが動くのか、各アクションごとにrefreshが動くのか仕様がはっきりしなかったので、一応明示的に、bulkのあとに、refreshを呼ぶ形とした(調べ力が足りない。。)
- bulkのパラメータに
こんな感じbulk_body = [ { update: { _id: "1", data: { doc: { "id" => 1, "name" => "進撃の巨人", "romaji_name" => "Shingeki no Kyojin", "summary" => "人類は巨人の餌だった―――古の歴史を紐解くかの如く、人類の前に立ち塞がる脅威。巨人の存在によって人類は絶滅の危機に瀕し、生き残るために高い壁を築いていた。" -> summaryは将来的に使うかも〜くらいの気持ちで入れておいた }, doc_as_upsert: true <- 更新 or 存在しない場合追加とする }, retry_on_conflict: 3 } }, { update: { _id: "2", data: { doc: { "id" => 2, "name" => "鬼滅の刃", "romaji_name" => "Kimetsu no Yaiba", "summary" => "時は大正、日本。炭を売る心優しい少年・炭治郎の日常は、家族を鬼に皆殺しにされたことで一変する。唯一生き残ったものの鬼に変貌した妹・禰豆子を元に戻すため、また家族を殺した鬼を討つため、炭治郎と禰豆子は旅立つ。" }, doc_as_upsert: true }, retry_on_conflict: 3 } } ] client.bulk(body: bulk_body) <- refresh: true としない(一応)
- updateアクションで
検索
- search
- ポイント
-
multi_match
- 複数フィールドを検索できる
- 検索タイプ: best_fields
- 一番関連性が高かったフィールドのスコアを結果のスコアとする
- 一瞬
phrase
にしたが、作品名を正確に覚える人はいないため、デフォルトのbest_fields
へ-
phrase
の場合、複数単語あった場合、1フレーズとして扱ってくれる-
phrase
:kimetu no yaiba
->kimetu no yaiba
-
not phrase
:kimetu no yaiba
->kimetu
no
yaiba
-
-
- 検索対象フィールドの指定
- 名前とローマ字名のみを対象とするため
-
filter
- 関連性とか関係なく、絶対的にその条件で絞り込みができる
- 成人向けかどうかの条件での絞り込みがあったので、利用
こんなんquery = { size: 30, query: { bool: { must: { multi_match: { query: @word, fields: [ "name", "romaji_name" ] } }, filter: [ { term: { is_mature: @is_mature } } ] } } } response = client.search( body: query, index: 'anime_titles' )
-
multi_match
OpenSearch Dashbord
- これ
- 管理画面的な感じで、GUIでインデックス内のドキュメント検索や分析ができる
- 検索ワードの集計とかできるのかと勝手に思っていたが、できなかったので、結局利用せず
- CloudWatch Logsにログ出すようにすれば、集計できる様子- 今回は手軽にGA4に検索イベントを送るようにした
Discussion