Contract One の開発チームで EM をやっている石畑です。Contract One は「契約データベースから、収益を最大化する」をタグラインに、契約業務や管理の効率化だけでなく、契約データをビジネスに活かすためにさまざまな機能開発を行っています。
その中で最近より検索機能の向上が重要になって来ているので、全文検索エンジンである Elasticsearch について調べてまとめました。Elasticsearch をざっくり把握したい人に役立つブログになれば嬉しいです。
なお、本記事は Sansan Advent Calendar 2023 25日目の記事です。
Elasticsearch の概要
全文検索と Elasticsearch
複数の文書の中から特定の文字列を探す全文検索には、検索方法が大きく分けて 2 種類あります。
- 逐次検索 : ファイルの内容を逐次チェックして特定の文字列を見つける(例 : grep)
- 索引型検索 : 事前に単語の索引(転置インデックス)を作成し、それを使って検索する
Elasticsearch は後者の方法で検索を行う全文検索エンジンです。内部には Apache Lucene というオープンソースの検索ライブラリを使っており、これがインデックス(の作成)や検索などの機能を提供しています。Elasticsearch はそれをラッパーし、インタフェースやさまざまな管理機能を提供します。Apache Lucene ベースの全文検索エンジンの代表例としては Apache Solr もあります。
索引型検索は、大量の文書を高速に検索する事に適している一方で、インデックスをどのように構築するか、それをどう検索するか、によってノイズや検索漏れが発生する可能性があり、これらのチューニングが重要になります。
特に日本語は英語と違い、単語の区切りがわからないので、インデックスの際の形態素解析の精度が重要になってきます。ちなみに、一定の文字数単位でインデックスする N-gram 方式もありますが、漏れが発生しにくい一方で、ノイズが大きくなるなどの欠点があります。
基本的な概念
Elasticsearch はざっくりノード、クラスタ、シャードという物理レイヤがあり、その中でデータを分散保持します。データはインデックス、ドキュメント、フィールドという論理概念を持ちます。以下、簡単な説明です。
物理概念
- ノード(Node)
- Elasticsearch が動作するサーバー
- ノードを増やしていくことで、スケールアウトさせることができる
- 各ノードはクラスタ内の他のノードを知っており、リクエストを転送できる
- クラスタ(Cluster)
- 接続されたノードの集まり
- Elasticsearch は複数のノードでクラスタを構成することを想定して作られている
- シャード(Shard)
- データを分割して分散保持することができ、その一つ一つ
- 複数のシャードでデータを保持することで、より大容量のデータをより早く処理する事ができる
- 一方でオーバーヘッドも増えるため、容量に合わせてシャード数を決定する必要がある
- シャードには、プライマリとレプリカが存在するが、プライマリはインデックス作成後にシャード数を変更できないため、最初に設計する必要がある
- レプリカは後で変更可能
- レプリカ(Replica)
- 各シャードを複製する仕組みがあり、この複製されたシャードをレプリカシャードと呼ぶ(オリジナルはプライマリ)
- プライマリとレプリカは同じノードに配置することはできず、自動的に別のノードに配置される
- プライマリに問題が発生した場合は、自動的にレプリカがプライマリに変更される
- 検索はレプリカにも並列で行われるため、レプリカを増やすことで可用性だけでなく、検索性能を向上させることができる
論理概念
- インデックス(Index)
- RDB のテーブルに当たるデータを格納する場所
- ドキュメント(Document)
- RDB のレコードに当たる一つ一つの文書
- 各ドキュメントには ID が付与され、指定しない場合は自動採番される
- フィールド(Field)
- RDB のカラムに当たるドキュメント内部の項目名・値(key / value)の組
- 検索はフィールド単位で行われる
- マッピング(Mapping)
- RDB のスキーマに当たるドキュメントがどのような型・データ構造(フィールド)を持つかを定義したもの
- マッピングを明示的に設定せずにデータを取り込むと、 Elasticsearch 側で動的にマッピングを定義する
フィールドのデータ型
フィールドは次のようなデータ型を持ちます。
- Text
- 文字列を格納するデータ型
- Analyzer によって単語分割され、転置インデックスが作成される
- Keyword
- text とは違い単語分割がされないため、完全一致で検索する用途で使われる
- 最大文字列長は 32,766 byte
- 数値型(long, integer, float など)
- 大小比較や範囲検索などができる
- Date
- 日時を表すデータ型。ミリ秒まで表現できる
- ナノ秒まで扱いたい場合は date_nanos を使う
- Object
- JSON オブジェクトを扱える
- Array
- 配列型
- 文字列、数値、配列(配列の入れ子)、オブジェクト(JSON)の配列を扱える
- マルチフィールド型
- 例えば、形態素解析された text での検索、keyword として完全一致での検索、両方を行いたい場合などに使う
- 型をマッピングで明示せずに文字列を入れると自動的にマルチフィールド型として扱われる
その他にも Boolean や緯度経度などを扱う型もあります。全データ型はこちら。
ノードのロール
ノードにはいくつかのロールがあり、システム要件に応じてロールを割り当てることができます。 特に何も指定しなければ、これから紹介するロールは全てのノードで持っているのですが、明示的に他のロールを削除して、特定のロールのノードを作ることも可能です。
Master-eligible ノード
Master 適格ノード。Mater ノードとして機能するノードで、この Master-eligible ノード群の中から一つ Master ノードが選出されます(Master ノードはクラスタに必ず一つ持ちます、Master ノードが停止するとクラスタ全体が停止します)。 Master ノードが行う役割は主に以下です。
- 各ノードの Healthcheck
- クラスタメタデータの管理
- クラスタのノード構成、インデックスの設定情報、シャード割当情報などを管理
- シャードの割り当てと再配置
Master ノードに何かしらの障害が発生した場合は、他の Master-eligible ノードから選出されます。
選出は以下のようなイメージで、各 Master-eligible ノードの接続数が過半数を超えていると、そのノードグループから Master ノードが選出されます。
過半数は ノード数 / 2 (切り捨て) + 1
です。
逆に過半数を満たさないグループは Master ノードに選出されないようになっています。そのため、ノードは
- 半分以上のノードを一度に停止しない
- 3 台以上で構成する(可用性が全く不要なら 1 台でもいい)
などの注意が必要です。
Data ノード
データを保持し、CRUD、検索、集約などのデータ関連の操作を実行するノードです。シャードは Data ノードによって保持されます。
Data ノードの中で多層アーキテクチャにしたい場合、特性に合わせて細かくロールを設定できます(特に明示しない場合は data
というロールを持つ)
- data_content : 時間の経過でデータの価値が変わらず、古さに関係なく迅速に検索できる必要がある
- data_hot, data_warm, data_cold, data_frozen : 時系列データなど、時間の経過とともにアクセス頻度が変わる際にフェーズで振り分ける
Ingest ノード
Data ノードにデータを送る前の処置フロー(pipeline)を定義できます。Ingest の負荷が高い場合は専用のノードを構築させると良いです。
Coordinating only ノード
Coordinating と呼んでいる、クライアントからのリクエストのハンドリング処理を行うノード(リクエストの各シャードへの分配と、結果の集約)。他の役割を off にして Coordinating 専用ノードにすることで、ロードバランサーとして機能させることができます。
Machine learning ノード
API を受け付けて、指定された機械学習ジョブを実行するノード。機械学習の設定については以下を参照してください。
シャーディングについて
ノードを増やしていくと、処理が分散されパフォーマンス向上が見込めるため、ノードに対して一つのプライマリシャードが配置されている状態が望ましいです。しかし、プライマリシャード数はインデックス作成時にしか決められないので、将来の拡張を見越して大きめに設定しておく必要があります。一方でシャードは多すぎてもオーバーヘッドが増えて非効率になってしまいます。目安としては 1 シャード 20-40GB 程度にすると良いようです。
また、レプリカシャードを増やすことで可用性や検索処理を向上させることができます。こちらも検索負荷が高い場合は増やすことを検討した方がいいでしょう。レプリカ数は後から増減可能です。そのため、逆に大量のデータをバルクでインデックスする際は、レプリカ数を一時的に 0 にするなども検討すると効率的にデータを取り込めます(スループットと可用性のトレードオフ)。
REST API
Elasticsearch の管理・操作は全て REST API 経由で実行する事ができ、JSON でデータのやり取りをします。クラスタの操作は https://ドメイン/_cluster
、ドキュメントの操作は https://ドメイン/インデックス名/_doc
など、操作対象に対して、GET, POST で操作できます。
Analyzer によるインデクシング
ここまでが Elasticsearch の超ざっくりな基本的な概念です。ここから検索のコアとなる Analyzer によるインデクシング部分と検索部分(の上澄み)について見ていきます。
Analyzer とは、text 型のフィールドで転置インデックスの作成や、検索の際にテキスト分析を行う機能です。
Analyzer の構造
Analyzer は 3 つのステップで構成されており、入力となるテキストは Character filter → Tokenizer → Token filter のフローを通り、トークン(単語)に分割されてインデックスされます。 各 Filter, Tokenizer は Elasticsearch でビルドインされており、指定するだけで簡単に利用することができます。また、よく使われる Filter, Tokenizer の組み合わせが Analyzer としてパッケージされているので、Analyzer を指定するだけでも利用できます。これらは辞書追加やオプションで細かくチューニングできますし、自前で作ることもできます。
- Character filter
- 入力のテキストを受け取り、Tokenizer で単語分割を行う前に文字変換処理を行う
- 例えば、全角・半角の文字フォーマットの統一や省略文字の展開など
- 0 件以上の filter を設定することができる
- Tokenizer
- テキストの分割処理を行いトークンを生成する
- 単語分割、N-gram 分割、構造化テキスト(Email, URL のような)の分割用にさまざまな Tokenizer が用意されている
- 必ず 1 件指定する必要がある
- Token filter
- トークンに対して文字変換処理を行う
- 例えば、日本語の「は」「です」のようなストップワードの削除など
- 0 件以上の filter を設定することができる
Kuromoji Analyzer
Kuromoji Analyzer は Elasticsearch で日本語を扱う際によく使われている Analyzer です。プラグインとして提供されており、簡単に導入できます。
Kuromoji Analyzer は以下の分析チェーンを使用します。
- Character filter
- CJKWidthCharFilter
- Tokenizer
- kuromoji_tokenizer
- Token filter
- kuromoji_baseform
- kuromoji_part_of_speech
- ja_stop
- kuromoji_stemmer
- lowercase
CJKWidthCharFilter だけキャメルケースになっていますが、こちらは Lucene の提供する Character filter で、文字の全角半角の正規化を行います。その他の Tokenizer, Filter がどのように動作するのか簡単に見ていきます。
kuromoji_tokenizer
kuromoji の日本語形態素解析を行う Tokenizer です。 tokenization mode が以下の 3 種類から選ぶことができ、デフォルトは search モードです。
- normal
- search
- extended
ドキュメントに例がありますが、次のような分割結果になります。
normal : 単語は分割しない
関西国際空港 アブラカダブラ
search : 最小単位に単語を分割する
関西, 関西国際空港, 国際, 空港 アブラカダブラ
extended : 未知の単語は 1-gram として一文字単位に分割する
関西, 関西国際空港, 国際, 空港 ア, ブ, ラ, カ, ダ, ブ, ラ
ちなみに個人情報保護法の第一章第一条では、モードごとに差はありませんでした。
text 第一条 この法律は、デジタル社会の進展に伴い個人情報の利用が著しく拡大していることに鑑み、個人情報の適正な取扱いに関し、基本理念及び政府による基本方針の作成その他の個人情報の保護に関する施策の基本となる事項を定め、国及び地方公共団体の責務等を明らかにし、個人情報を取り扱う事業者及び行政機関等についてこれらの特性に応じて遵守すべき義務等を定めるとともに、個人情報保護委員会を設置することにより、行政機関等の事務及び事業の適正かつ円滑な運営を図り、並びに個人情報の適正かつ効果的な活用が新たな産業の創出並びに活力ある経済社会及び豊かな国民生活の実現に資するものであることその他の個人情報の有用性に配慮しつつ、個人の権利利益を保護することを目的とする。 output 第 一 条 この 法律 は デジタル 社会 の 進展 に 伴い 個人 情報 の 利用 が 著しく 拡大 し て いる こと に 鑑み 個人 情報 の 適正 な 取扱い に関し 基本 理念 及び 政府 による 基本 方針 の 作成 その他 の 個人 情報 の 保護 に関する 施策 の 基本 と なる 事項 を 定め 国 及び 地方 公共 団体 の 責務 等 を 明らか に し 個人 情報 を 取り扱う 事業 者 及び 行政 機関 等 について これら の 特性 に 応じ て 遵守 す べき 義務 等 を 定める とともに 個人 情報 保護 委員 会 を 設置 する こと により 行政 機関 等 の 事務 及び 事業 の 適正 かつ 円滑 な 運営 を 図り 並びに 個人 情報 の 適正 かつ 効果 的 な 活用 が 新た な 産業 の 創出 並びに 活力 ある 経済 社会 及び 豊か な 国民 生活 の 実現 に 資する もの で ある こと その他 の 個人 情報 の 有用 性 に 配慮 し つつ 個人 の 権利 利益 を 保護 する こと を 目的 と する
その他のオプション
- discard_punctuation
- 句読点を削除するか決める、デフォルトは true
- user_dictionary
以下の CSV 形式で辞書を追加
<text>,<token 1> ... <token n>,<reading 1> ... <reading n>,<part-of-speech tag>
例
東京スカイツリー,東京 スカイツリー,トウキョウ スカイツリー,カスタム名詞
- discard_compound_token
- 元の複合トークンを捨てるか決める。デフォルトは false
- search, extended モードで true にすると「関西国際空港」は
関西 , 国際, 空港
という結果になる
kuromoji_baseform
lemmatizer として動作し、単語を基本形、辞書系に正規化します。以下例です。
入力テキスト
第一条 この法律は、デジタル社会の進展に伴い個人情報の利用が著しく拡大していることに鑑み、個人情報の適正な取扱いに関し、基本理念及び政府による基本方針の作成その他の個人情報の保護に関する施策の基本となる事項を定め、国及び地方公共団体の責務等を明らかにし、個人情報を取り扱う事業者及び行政機関等についてこれらの特性に応じて遵守すべき義務等を定めるとともに、個人情報保護委員会を設置することにより、行政機関等の事務及び事業の適正かつ円滑な運営を図り、並びに個人情報の適正かつ効果的な活用が新たな産業の創出並びに活力ある経済社会及び豊かな国民生活の実現に資するものであることその他の個人情報の有用性に配慮しつつ、個人の権利利益を保護することを目的とする。
トークン化
第_一_条_この_法律_は_デジタル_社会_の_進展_に_伴い_個人_情報_の_利用_が_著しく_拡大_し_て_いる_こと_に_鑑み_個人_情報_の_適正_な_取扱い_に関し_基本_理念_及び_政府_による_基本_方針_の_作成_その他_の_個人_情報_の_保護_に関する_施策_の_基本_と_なる_事項_を_定め_国_及び_地方_公共_団体_の_責務_等_を_明らか_に_し_個人_情報_を_取り扱う_事業_者_及び_行政_機関_等_について_これら_の_特性_に_応じ_て_遵守_す_べき_義務_等_を_定める_とともに_個人_情報_保護_委員_会_を_設置_する_こと_により_行政_機関_等_の_事務_及び_事業_の_適正_かつ_円滑_な_運営_を_図り_並びに_個人_情報_の_適正_かつ_効果_的_な_活用_が_新た_な_産業_の_創出_並びに_活力_ある_経済_社会_及び_豊か_な_国民_生活_の_実現_に_資する_もの_で_ある_こと_その他_の_個人_情報_の_有用_性_に_配慮_し_つつ_個人_の_権利_利益_を_保護_する_こと_を_目的_と_する
kuromoji_baseform による変換
第_一_条_この_法律_は_デジタル_社会_の_進展_に_伴う_個人_情報_の_利用_が_著しい_拡大_する_て_いる_こと_に_鑑みる_個人_情報_の_適正_だ_取扱い_に関し_基本_理念_及び_政府_による_基本_方針_の_作成_その他_の_個人_情報_の_保護_に関する_施策_の_基本_と_なる_事項_を_定める_国_及び_地方_公共_団体_の_責務_等_を_明らか_に_する_個人_情報_を_取り扱う_事業_者_及び_行政_機関_等_について_これら_の_特性_に_応じる_て_遵守_する_べし_義務_等_を_定める_とともに_個人_情報_保護_委員_会_を_設置_する_こと_により_行政_機関_等_の_事務_及び_事業_の_適正_かつ_円滑_だ_運営_を_図る_並びに_個人_情報_の_適正_かつ_効果_的_だ_活用_が_新た_だ_産業_の_創出_並びに_活力_ある_経済_社会_及び_豊か_だ_国民_生活_の_実現_に_資する_もの_だ_ある_こと_その他_の_個人_情報_の_有用_性_に_配慮_する_つつ_個人_の_権利_利益_を_保護_する_こと_を_目的_と_する
差分(元文章 : 変化した単語)
進展_に_伴い_個人_情報 : 伴い -> 伴う 利用_が_著しく_拡大_し : 著しく -> 著しい 著しく_拡大_し_て_いる : し -> する こと_に_鑑み_個人_情報 : 鑑み -> 鑑みる の_適正_な_取扱い_に関し : な -> だ 事項_を_定め_国_及び : 定め -> 定める 明らか_に_し_個人_情報 : し -> する 特性_に_応じ_て_遵守 : 応じ -> 応じる て_遵守_す_べき_義務 : す -> する 遵守_す_べき_義務_等 : べき -> べし かつ_円滑_な_運営_を : な -> だ 運営_を_図り_並びに_個人 : 図り -> 図る 効果_的_な_活用_が : な -> だ が_新た_な_産業_の : な -> だ 及び_豊か_な_国民_生活 : な -> だ 資する_もの_で_ある_こと : で -> だ に_配慮_し_つつ_個人 : し -> する
kuromoji_part_of_speech
stoptags に定義されている品詞タグにマッチする単語が削除されます。削除するタグの種類はオプションで指定することもできます。
個人情報保護法第一条の例では 175 単語から 113 単語になり、62 単語削除されました。具体的に削除された単語は以下になります(**
で囲われた単語が削除単語)。
第_一_条_この_法律_*は*_デジタル_社会_*の*_進展_*に*_伴う_個人_情報_*の*_利用_*が*_著しい_拡大_する_*て*_いる_こと_*に*_鑑みる_個人_情報_*の*_適正_*だ*_取扱い_*に関し*_基本_理念_*及び*_政府_*による*_基本_方針_*の*_作成_その他_*の*_個人_情報_*の*_保護_*に関する*_施策_*の*_基本_*と*_なる_事項_*を*_定める_国_*及び*_地方_公共_団体_*の*_責務_等_*を*_明らか_*に*_する_個人_情報_*を*_取り扱う_事業_者_*及び*_行政_機関_等_*について*_これら_*の*_特性_*に*_応じる_*て*_遵守_する_*べし*_義務_等_*を*_定める_*とともに*_個人_情報_保護_委員_会_*を*_設置_する_こと_*により*_行政_機関_等_*の*_事務_*及び*_事業_*の*_適正_*かつ*_円滑_*だ*_運営_*を*_図る_*並びに*_個人_情報_*の*_適正_*かつ*_効果_的_*だ*_活用_*が*_新た_*だ*_産業_*の*_創出_*並びに*_活力_ある_経済_社会_*及び*_豊か_*だ*_国民_生活_*の*_実現_*に*_資する_もの_*だ*_*ある*_こと_その他_*の*_個人_情報_*の*_有用_性_*に*_配慮_する_*つつ*_個人_*の*_権利_利益_*を*_保護_する_こと_*を*_目的_*と*_する
ja_stop
stopwords で定義されたストップワードが削除されます(「は」「の」「です」のような単語)。
例えばこんな感じで削除されます(**
で囲われた単語が削除単語)。
第_一_条_*この*_法律_デジタル_社会_進展_伴う_個人_情報_利用_著しい_拡大_*する*_*いる*_*こと*_鑑みる_個人_情報_適正_取扱い_基本_理念_政府_基本_方針_作成_*その他*_個人_情報_保護_施策_基本_*なる*_事項_定める_国_地方_公共_団体_責務_等_明らか_*する*_個人_情報_取り扱う_事業_者_行政_機関_等_*これら*_特性_応じる_遵守_*する*_義務_等_定める_個人_情報_保護_委員_会_設置_*する*_*こと*_行政_機関_等_事務_事業_適正_円滑_運営_図る_個人_情報_適正_効果_的_活用_新た_産業_創出_活力_*ある*_経済_社会_豊か_国民_生活_実現_資する_*もの*_*こと*_*その他*_個人_情報_有用_性_配慮_*する*_個人_権利_利益_保護_*する*_*こと*_目的_*する*
kuromoji_stemmer
stemmer として単語の正規化を行います。具体的には全角カタカナの最後の伸ばし棒を削除します。 オプションとして minimum_length で正規化を行う最小文字数を設定でき、デフォルトは 4 で設定されいます。
コピー
→コピー
サーバー
→サーバ
lowercase
kuromoji の Filter ではなく、日本語は影響しないですが、大文字を小文字に正規化します。Greek, Irish, Turkish の場合は language を指定することで、専用の filter が適用されます。
追加 Filter
ここまでが kuromoji analyzer が実施しているテキスト解析になります。その他にも以下のような Filter が用意されています。
- kuromoji_iteration_mark : 踊り字の展開(
久々
→久久
、学問のすゝめ
→学問のすすめ
) - kuromoji_readingform : カタカナ or ローマ字の読みに変換(
第一条
→ダイイチジョウ
) - kuromoji_number : 漢数字をアラビア数字に変換(
九千八百七十六兆六億五万
→9876000600050000
)
Sudachi Analyzer
このように日本語の解析にさまざまな辞書、機能を提供する kuromoji ですが、最近は頻繁に辞書が更新されている Sudachi も日本語 Analyzer として人気が高いようです。以下のブログでわかりやすく紹介されていました。
検索
ここまででサーバーの構築 → テキストの解析までできるようになりましたが、検索まで行ってようやく目的の文書が見つけられるようになるので、最後に検索もまとめていきます。
検索クエリには大きく、単独で実行する「リーフクエリ」とクエリを複数組み合わせる「複合クエリ」の 2 種類があります。その中で代表的なものとして以下があります。
- リーフクエリ
- 全文検索クエリ
- Term レベルクエリ
- 複合クエリ
- Bool クエリ
簡単にそれぞれ
- 全文検索クエリ
- 解析された text フィールドに対して検索することができる
- 入力も analyzer が適用される
- Term レベルクエリ
- keyword フィールドなど完全一致での検索に使う
- Bool クエリ
- must(AND 条件), should(OR 条件), must_not(NOT 条件), filter(AND 条件)オプションを持つ
- Elasticsearch は検索結果の関連度に応じてスコアを返すが、must_not, filter はスコアは返さない(0 になる)
- これらを組み合わせて、複数の複雑な条件で検索することができる
これらのクエリを機能に合わせて使い分ける必要がありますが、ここでは全文検索クエリについて見ていきます。
全文検索クエリ
全文検索クエリに含まれるクエリは以下です。
- match クエリ
- match_phrase クエリ
- match_phrase_prefix クエリ
- match_bool_prefix クエリ
- multi_match クエリ
- combined_fields クエリ
- intervals クエリ
- query_string クエリ
- simple_query_string クエリ
この中からいくつかのクエリを見ていきます。
match クエリ
全文検索で標準的に使われるクエリです。検索対象の text フィールドと同様の analyzer によって入力がパースされ、単語単位で検索が行われるため(analyzer は差し替え可能)、入力に対して正確な検索にはならない一方で、柔軟な検索が可能になります。
例えば以下のような動作があり得ます。
入力(content はフィールド名)
POST test_index/_search { "query": { "match": { "content": { "query": "新年の挨拶について" } } } }
結果
{ "took": 0, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 0.62364864, "hits": [ { "_index": "test_index", "_id": "1", "_score": 0.62364864, "_source": { "content": "昨年は何かとお世話になりありがとうございました。お陰様で無事に新年を迎えることができました。今年もどうかよろしくお願いします" } } ] } }
「昨年は何かとお世話になりありがとうございました。お陰様で無事に新年を迎えることができました。今年もどうかよろしくお願いします」という文章が「新年の挨拶について」という検索キーワードでマッチしています。内部的には「新年」がマッチして返ってきています(クエリに "explain": true
をつけるとマッチしたものが見れる)。
例のように match 句の中で top-level に検索したいフィールド名を指定し、検索キーワードを query 句に指定します。
主なパラメーター
フィールド名、query 以外はオプションになります。
- analyzer(string)
- 指定するとフィールドとは別の analyzer に差し替えることができる
- boost(float)
- マッチした際の関連スコアを増減させる(デフォルトは 1.0)
- 複合クエリの際に重み付けとして使える
- fuzziness(string)
- マッチの際に許容する編集距離 0-2 で指定できる
AUTO:[low],[high]
で文字列の長さに応じた指定も可能。参照 : Fuzziness の定義
- max_expansions(integer)
- fuzzy マッチは柔軟な分、計算負荷も高いため、マッチさせる用語の最大数を指定できる
- デフォルトは 50
- prefix_length(integer)
- fuzzy マッチで変更させない文字列の長さ
- デフォルトは 0
- fuzzy_transpositions(Boolean)
- fuzzy マッチで ab → ba のような隣接する文字の入れ替えを許容するかどうか
- デフォルトは true
- operator(string)
- 指定した検索キーワード(query)で分割された単語は、デフォルトでは OR 検索になるのだが、OR か AND か指定できる
- minimum_should_match(string)
OR 検索で最低限マッチしなければいけない単語数を指定できる
検索対象 : "昨年は何かとお世話になりありがとうございました。お陰様で無事に新年を迎えることができました。今年もどうかよろしくお願いします" "content": { "query": "今年 去年 来年", "minimum_should_match": 1 } => hit "content": { "query": "今年 去年 来年", "minimum_should_match": 2 } => 2 つマッチしないといけないので、hit せず
- zero_terms_query(string)
analyzer の処理などで検索キーワードが空文字になったときに、空を返すか(デフォルト)、全ドキュメントを返すか指定できる
"content": { "query": "、", "zero_terms_query": "all" } => 全ドキュメントが返される
match_phrase クエリ
入力は AND 検索になり、さらに単語の順番、距離も考慮したマッチングが行われます。例えば先程の match 例だと
検索対象 : "昨年は何かとお世話になりありがとうございました。お陰様で無事に新年を迎えることができました。今年もどうかよろしくお願いします" POST test_index/_search { "query": { "match_phrase": { "content": { "query": "新年の挨拶について" } } } } => []
match_phrase の場合、結果は空になります。また、match の場合(operator: and で) 新年 無事
とかでもマッチしますが、match_phrase の場合は語順が違うためマッチしません。単語の距離も考慮されます。
"query": "無事 に 新年" => マッチする "query": "無事 新年" => マッチしない
距離は slop パラメーターで(デフォルトは 0)許容範囲を変えることができます。
"content": { "query": "無事 新年", "slop": 1 } => マッチする
ちなみに analyzer, zero_terms_query もパラメーターとして指定できます。
multi_match クエリ
複数フィールドに対して match クエリを実行することができます。 例えば以下のような使い方ができます。例では first_name, last_name のフィールドを組み合わせて full_name のように扱っています。
{ "query": { "multi_match" : { "query": "Will Smith", "type": "cross_fields", "fields": ["first_name", "last_name"], "operator": "and" } } } => first_name: Will, last_name: Smith or first_name: Smith, last_name: Will がマッチする
上の例だと type: cross_fields
を指定していますが、その他にも例えば
- best_fields
- 指定したいずれかのフィールドにマッチするドキュメントを探す
- Will Smith の例だと
first_name: Will Smith or last_name: Will Smith
がマッチする
- most_fields
- マッチングは best_fields と同様だが、スコアの算出が異なる
- best_fields は最も高いフィールドを使い、most_fields は各フィールドを組み合わせる
- 「同じテキストを異なる解析方法でインデックスした複数のフィールドを用意し、検索することで、より類似した文書を見つける」というような使い方ができる
- phrase
- best_fields の挙動で match_phrase を実行する
などがあります。
query_string クエリ
Query string シンタックスを使って検索することができます。
これまで見てきた AND, OR 検索、phrase 検索、複数フィールドに対する検索、fuzzy 検索などもできますし、正規表現も使用できます。
複雑性や汎用性が高い反面、クエリは厳密で、クエリ文字列に無効な構文が含まれる場合はエラーになります。そのため、検索ボックスのクエリとして query_string クエリを使用することは推奨されておらず、simple_query_string クエリを使うことが推奨されています。こちらは無効な場合もエラーを無視したり、Operator flags で使える機能を制限できたりします。
全文検索クエリの説明は以上になります。
まとめ
ここまで Elasticsearch の基本的な概念から、Analyzer(特に kuromoji)の挙動、検索クエリについてまとめてみました。監視や運用周りの話はありますが、おそらくこれでひとまず使ってみることはできるかなーと思っています。ただ、挙動はデータ次第なので、やってみないとわからないところが多く、大変なのはここからですね。そんなことより、ブログ長すぎますね。