🛒

はじめての OpenSearch

に公開

検索機能の実装の際に触る機会があったので、はじめての記録を残しておきます。

やりたかったこと

アニメ作品の名前からアニメ作品を検索したい。ごく一般的なWebサイト内での検索機能ですね。

OpenSearchってなに?

名前の通り、検索機能を提供するサービス。
以下のような用途で使われることが多いようです。

  • ログ分析
    • AWS CloudWatchLog Insightのもっとすごい版的な使い方らしい。噂。
  • エンタープライズ検索
    • 社内の資料を一元管理
  • アプリ内での検索
    • いわゆる検索。 -> 今回はこの用途で使いました。全文検索。

今回は単純な全文検索で利用しましたが、他にも、いろんな機能があるみたいです。意味検索/AI検索など、使ってみたいです。

ながれ

    1. インデックス作成
    1. 検索
    • ↑で作成したインデックスに対して、検索ワードを検索する

じっそう

ドキュメント追加・更新

  • bulk
    • 100以上ある作品情報を更新する必要があったので、一括で処理できる方法にしました
    • 作品情報は逐一更新されていくので、日時のバッチでドキュメントの追加・更新をします
    • ポイント
      • updateアクションでdoc_as_upsert: trueとして、更新 or 存在しない場合追加とする
      • refreshタイミング
        • インデックスのドキュメント追加/更新後に、refreshすると検索できる様になるのですが、しんどい処理のようでパフォーマンスを下げるようです。 -> 詳細
        • bulkでまとめて更新後に、refreshを1回だけ実行するようにします
          • bulkのパラメータにrefresh: trueとした場合に、全アクションに対して1回refreshが動くのか、各アクションごとにrefreshが動くのか仕様がはっきりしなかったので、一応明示的に、bulkのあとに、refreshを呼ぶ形とした(調べ力が足りない。。)
      こんな感じ
      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 としない(一応)
      

検索

  • 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'
      )
      
      

OpenSearch Dashbord

  • これ
  • 管理画面的な感じで、GUIでインデックス内のドキュメント検索や分析ができる
  • 検索ワードの集計とかできるのかと勝手に思っていたが、できなかったので、結局利用せず
       - CloudWatch Logsにログ出すようにすれば、集計できる様子
    • 今回は手軽にGA4に検索イベントを送るようにした

まとめ

Discussion