DRYな備忘録

Don't Repeat Yourself.

【緩訳Elasticsearch】その3 Controlling analysis

原文: Controlling Analysis | Elasticsearch: The Definitive Guide [2.x] | Elastic

controlling analysis

検索クエリは逆引きインデックス*1に存在している単語しか見つけることができません。したがって、indexに紐づくすべてのドキュメントをインデクシングするときに施されるanalysis処理と、検索時にquery文字列に対して施されるanalysis処理が同じであることが重要です。そうでなければ、queryに含まれる単語が逆引きインデックスとマッチしなくなってしまいます。*2

さっき「ドキュメントを」って言ったが、あれは嘘だ。

アナライザーはドキュメントのフィールドごとに設定することができます。各フィールドにつき特定のアナライザを設定することもでき、あるいはそれが失敗すればtypeないしindexないしドキュメントのデフォルトに従います。ドキュメントを保存しインデクシングするタイミングにおいても、各フィールドに設定されている、あるいはデフォルトの、アナライザーに従ってanalyzeされます。*3

実際にやってみましょう。

my_indexというインデックスに新しいフィールドを追加します。*4

PUT /my_index/_mapping/my_type
{
    "my_type": {
        "properties": {
            "english_title": {
                "type":     "string",
                "analyzer": "english"
            }
        }
    }
}

ここで、analyzeAPIで"Foxex"という単語をanalyzeすることで、english_titleというフィールドとtitleというフィールドが、インデクシングのタイミングでどのようにanalyzeされるか比較してみます。*5

GET /my_index/_analyze?field=my_type.title&text=Foxes&pretty

GET /my_index/_analyze?field=my_type.english_title&text=Foxes&pretty

デフォルト標準アナライザを使って"Foxes"をアナライズすると、"foxes"というトークンを返します。*6

{
  "tokens" : [ {
    "token" : "foxes",
    "start_offset" : 0,
    "end_offset" : 5,
    "type" : "<ALPHANUM>",
    "position" : 1
  } ]
}

一方、englishアナライザを使った場合は、"fox"というトークンを返すことがわかります。*7

{
  "tokens" : [ {
    "token" : "fox",
    "start_offset" : 0,
    "end_offset" : 5,
    "type" : "<ALPHANUM>",
    "position" : 1
  } ]
}

これはつまり、low-levelなtermクエリを使った場合「fox」という単語で検索した場合にenglish_titleフィールドならマッチして、titleフィールドはマッチしないことを意味します。*8

high-levelのクエリであるmatchなどはフィールドのマッピングそのものを解釈し、検索対象のフィールドに適切なアナライザを適用します。validate-query APIを使ってこの挙動を確認してみましょう。*9

GET /my_index/my_type/_validate/query?explain
{
    "query": {
        "bool": {
            "should": [
                { "match": { "title":         "Foxes"}},
                { "match": { "english_title": "Foxes"}}
            ]
        }
    }
}

これは以下のようなレスポンスを返します。

{
  "valid" : true,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "explanations" : [ {
    "index" : "foo",
    "valid" : true,
    "explanation" : "filtered(title:foxes english_title:fox)->cache(_type:bar)"
  } ]
}

matchクエリは各フィールドに適切なアナライザを適用して適切なトークンで各フィールドを検索することを保証していることがわかります。*10

default analyzers

フィールド単位でアナライザを設定できる、という話がここまでで、一方、デフォルトで適用されるアナライザが何なのかという話が残っています。*11

どのアナライザを適用するかはいくつかのレベルで判断されます。Elasticsearchは、適用可能なアナライザが決まるまで各レベルで検証をします。インデクシングをするタイミングでは、以下のフローに従っています。*12

  1. mappingでフィールドレベルに定義されているアナライザ
  2. documentレベルに設定されているアナライザ
  3. typeレベルに定義されているアナライザ
  4. indexレベルでdefaultと命名されているアナライザ)
  5. nodeレベルでdefaultと命名されているアナライザ
  6. 上記がどれも定義されていない場合はstandard analyzer *13

以上が、インデクシングをするタイミングでのデフォルトアナライザ決定フローです。一方で、検索するタイミングでのデフォルトアナライザの決定は以下のフローに従います。*14

  1. クエリで明示されているアナライザ
  2. mappingでフィールドレベルに定義されているアナライザ
  3. typeレベルに定義されているアナライザ
  4. indexレベルのdefault
  5. nodeレベルのdefault
  6. 上記がどれも定義されていない場合はstandard analyzer *15

NOTE >>
上記でイタリック文字で書かれている文字は、インデクシングと検索とで異なる部分です。ドキュメント内の_analyzerフィールドは、検索時にドキュメントのバリューに適用するアナライザ(english, french, spanishなど)を指定でき、一方でクエリ内のanalyzerパラメータはそのquery文字列に対するアナライザを指定します。しかしながら、これは複数言語を扱う方法としてあまり良いものではありません。詳しくはDealing with Human Languageを参照してください。*16

場合によっては、インデクシングと検索で別のアナライザを使うほうが良いこともあります。たとえば、類義語(quickに対してfast,rapid,speedy)もインデクシングしておきたくなるかもしれません。そうしておけば、検索時に毎回類義語を評価しなくて済み、ユーザの"quick"という検索語に対して"fast",“rapid”,“speedy"の結果も返せることになるでしょう。*17

このような区別をするために、Elasticsearchではindex_analyzersearch_analyzerというパラメータがあり、それぞれdefault_indexdefault_searchと命名されたアナライザがあります。*18

このようなパラメータを考慮すると、マジな話ですべてのアナライザ決定フローは以下のようになります。インデクシングでは…*19

  1. mappingに定義されたindex_analyzer
  2. mappingに定義されたanalyzer
  3. documentに定義された_analyzer
  4. typeに定義されたindex_analyzer
  5. typeに定義されたanalyzer
  6. indexに定義されたdefault_index
  7. indexに定義されたdefault
  8. nodeに定義されたdefault_index
  9. nodeに定義されたdefault
  10. standard_analyzer *20

検索時では

  1. クエリに与えられたanalyzer
  2. mappingに定義されたsearch_analyzer
  3. mappingに定義されたanalyzer
  4. typeに定義されたsearch_analyzer
  5. typeに定義されたanalyzer
  6. indexに定義されたdefault_search
  7. indexに定義されたdefault
  8. nodeに定義されたdefault_search
  9. nodeに定義されたdefault
  10. standard_analyzer*21

configuring analyzers in practice

アナライザを指定できる箇所はクソみたいに多くてぶっちゃけやってられません。しかし、実践的な設定はめっちゃシンプルなルールに従います。*22

use index settings not config files

まず忘れてはいけないのは、たとえばログとか、簡単で単純な目的なアプリケーションでElasticsearchを使う場合であっても、今後このクラスタで別のアプリケーションを動かすことになるかもしれないということです。したがって、各indexは独立して設定できる状態である必要があります。なので、クラスタの設定ファイルにデフォルトを書いて、新たな要件でそれを編集するなどということは、ゆめゆめなされぬよう。*23

ひらたく言えばnodeレベルでアナライザdefaultを設定すんなっていうことです。nodeレベルでアナライザを設定しちゃうと、すべてのnodeの再起動にともなって設定ファイルを書き換えなければいけません。こんな運用はクソなので、設定ファイルじゃなくて、APIレベルでdefaultアナライザを設定しましょう。マジで。*24

keep it simple

だいたいの場合、ドキュメントのどのフィールドにどんなバリューが入るかあらかじめ分かっていると思います。なので、一番シンプルなのは、index,type,mappingsを新規作成するときに、全文検索されうるフィールドすべてにアナライザを設定しておくことです。いささか冗長ではありますが、どのフィールドにどんなアナライザが適用されているか確認するのは簡単で済みます。

もう少し実践的でよくある方法としては、完全一致に使うたとえばtagsとかenumsとかいう使い方のフィールドにはnot_analyzedを設定しておき、全文検索するものに対して適用したいデフォルトアナライザを設定しておく方法です。で、カスタムしたいいくつかのフィールドにのみ、別途アナライザを設定すればいいでしょう。たぶんtitleフィールドはfind-as-you-typeをサポートするようにインデクシングされるべきでしょう。

だいたいの全文検索フィールドに対して適用したいアナライザをindexレベルのデフォルトとすれば、カスタムの必要があるフィールドだけを設定すればよくなります。設計においてtypeレベルごとにデフォルトアナライザを設定する必要がある場合だけ、代わりにtypeレベルのデフォルトを設定しましょう。*25

高速スケーラブル検索エンジン ElasticSearch Server (アスキー書籍)

高速スケーラブル検索エンジン ElasticSearch Server (アスキー書籍)

*1:転置インデックス 転置索引 http://ja.wikipedia.org/wiki/%E8%BB%A2%E7%BD%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9

*2:Queries can find only terms that actually exist in the inverted index, so it is important to ensure that the same analysis process is applied both to the document at index time, and to the query string at search time so that the terms in the query match the terms in the inverted index.

*3:Although we say document, analyzers are determined per field. Each field can have a different analyzer, either by configuring a specific analyzer for that field or by falling back on the type, index, or node defaults. At index time, a field’s value is analyzed by using the configured or default analyzer for that field.

*4:For instance, let’s add a new field to my_index:

*5:Now we can compare how values in the english_title field and the title field are analyzed at index time by using the analyze API to analyze the word Foxes:

*6:Field title, which uses the default standard analyzer, will return the term foxes.

*7:Field english_title, which uses the english analyzer, will return the term fox.

*8:This means that, were we to run a low-level term query for the exact term fox, the english_title field would match but the title field would not.

*9:High-level queries like the match query understand field mappings and can apply the correct analyzer for each field being queried. We can see this in action with the validate-query API:

*10:The match query uses the appropriate analyzer for each field to ensure that it looks for each term in the correct format for that field.

*11:While we can specify an analyzer at the field level, how do we determine which analyzer is used for a field if none is specified at the field level?

*12:Analyzers can be specified at several levels. Elasticsearch works through each level until it finds an analyzer that it can use. At index time, the order is as follows:

*13:The analyzer defined in the field mapping, else The analyzer defined in the _analyzer field of the document, else The default analyzer for the type, which defaults to The analyzer named default in the index settings, which defaults to The analyzer named default at node level, which defaults to The standard analyzer

*14:At search time, the sequence is slightly different:

*15:The analyzer defined in the query itself, else The analyzer defined in the field mapping, else The default analyzer for the type, which defaults to The analyzer named default in the index settings, which defaults to The analyzer named default at node level, which defaults to The standard analyzer

*16:The two lines in italics in the preceding lists highlight differences in the index time sequence and the search time sequence. The _analyzer field allows you to specify a default analyzer for each document (for example, english, french, spanish) while the analyzer parameter in the query specifies which analyzer to use on the query string. However, this is not the best way to handle multiple languages in a single index because of the pitfalls highlighted in Dealing with Human Language .

*17:Occasionally, it makes sense to use a different analyzer at index and search time. For instance, at index time we may want to index synonyms (for example, for every occurrence of quick, we also index fast, rapid, and speedy). But at search time, we don’t need to search for all of these synonyms. Instead we can just look up the single word that the user has entered, be it quick, fast, rapid, or speedy.

*18:To enable this distinction, Elasticsearch also supports the index_analyzer and search_analyzer parameters, and analyzers named default_index and default_search.

*19:Taking these extra parameters into account, the full sequence at index time really looks like this:

*20:The index_analyzer defined in the field mapping, else The analyzer defined in the field mapping, else The analyzer defined in the _analyzer field of the document, else The default index_analyzer for the type, which defaults to The default analyzer for the type, which defaults to The analyzer named default_index in the index settings, which defaults to The analyzer named default in the index settings, which defaults to The analyzer named default_index at node level, which defaults to The analyzer named default at node level, which defaults to The standard analyzer

*21:The analyzer defined in the query itself, else The search_analyzer defined in the field mapping, else The analyzer defined in the field mapping, else The default search_analyzer for the type, which defaults to The default analyzer for the type, which defaults to The analyzer named default_search in the index settings, which defaults to The analyzer named default in the index settings, which defaults to The analyzer named default_search at node level, which defaults to The analyzer named default at node level, which defaults to The standard analyzer

*22:The sheer number of places where you can specify an analyzer is quite overwhelming. In practice, though, it is pretty simple.

*23:The first thing to remember is that, even though you may start out using Elasticsearch for a single purpose or a single application such as logging, chances are that you will find more use cases and end up running several distinct applications on the same cluster. Each index needs to be independent and independently configurable. You don’t want to set defaults for one use case, only to have to override them for another use case later.

*24:This rules out configuring analyzers at the node level. Additionally, configuring analyzers at the node level requires changing the config file on every node and restarting every node, which becomes a maintenance nightmare. It’s a much better idea to keep Elasticsearch running and to manage settings only via the API.

*25:Most of the time, you will know what fields your documents will contain ahead of time. The simplest approach is to set the analyzer for each full-text field when you create your index or add type mappings. While this approach is slightly more verbose, it enables you to easily see which analyzer is being applied to each field.

Typically, most of your string fields will be exact-value not_analyzed fields such as tags or enums, plus a handful of full-text fields that will use some default analyzer like standard or english or some other language. Then you may have one or two fields that need custom analysis: perhaps the title field needs to be indexed in a way that supports find-as-you-type.

You can set the default analyzer in the index to the analyzer you want to use for almost all full-text fields, and just configure the specialized analyzer on the one or two fields that need it. If, in your model, you need a different default analyzer per type, then use the type level analyzer setting instead.