Elasticsearch 日本語の為のスキーマレス環境構築

Elasticsearch Schema-less for Japanese — 動的マッピングを使用して日本語でもスキーマレスな環境を構築する

Kunihiko Kido
Hello! Elasticsearch.

--

Elasticsearch の特徴の一つスキーマレス(事前のスキーマ定義なしにデータをインデックスできる機能)ですが、日本語ではなかなかこの恩恵を受けることが出来ません。アナライザーを日本語向けにカスタマイズしたり、一つのフィールドでも日本語、ファセット、などコンテンツの内容と、いろいろな用途で使用することを考慮して、マッピング定義を設計する必要があるからです。

せっかくスキーマレスな検索エンジンなのに毎回マッピング定義をいちいちするのもめんどいと思うのは私だけでしょうか?と言うことで、動的マッピングを使って日本語でもスキーマレス環境の構築を考えたいと思います。

目指すは、検索の高度な知識を習得しなくても簡単に使える環境!

使用する主な機能

日本語環境でもスキーマレスな環境を手に入れる為に以下の機能を使用しました。

  • インデックステンプレート(Index Templates)
    インデックステンプレートは、インデックスの設定内容をテンプレート化する為の機能です。
    詳細:reference [1.x] » indices apis » index templates
  • ダイナミックテンプレート(Dynamic Templates)
    ダイナミックテンプレートは、フィールド名などのルールにマッチするマッピングを定義し、新規のインデックス作成時に動的なマッピング定義を実現する為の機能です。
    詳細:reference [1.x] » mapping » types » root object type » dynamic_templates
  • マルチフィールド(Multi Fields)
    マルチフィールドは、一つのフィールドに対し用途の異なる複数のフィールドを定義する為の機能です。
    詳細:reference [1.x] » mapping » types » core types » multi fields

日本語でもスキーマレスな設定

上記の機能を組み合わせて考えた、日本語でもスキーマレスな環境を実現する為のインデックステンプレートの設定内容がこれです。

{
"japanese_template": {
"template": "*ja",
"settings": {
"analysis": {
"filter": {
"romaji": {
"type": "kuromoji_readingform",
"use_romaji": true
}
},
"tokenizer": {
"ja_tokenizer": {
"type": "kuromoji_tokenizer",
"mode": "search"
},
"ngram_tokenizer": {
"type": "nGram",
"min_gram": 2,
"max_gram": 3,
"token_chars": ["letter", "digit"]
}
},
"analyzer": {
"facet_analyzer": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "keyword",
"filter": ["cjk_width", "lowercase"]
},
"ja_analyzer": {
"type": "custom",
"char_filter": [
"html_strip",
"kuromoji_iteration_mark"
],
"tokenizer": "ja_tokenizer",
"filter": [
"cjk_width",
"lowercase",
"kuromoji_stemmer",
"kuromoji_part_of_speech"
]
},
"ngram_analyzer": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "ngram_tokenizer",
"filter": ["cjk_width", "lowercase"]
},
"yomi_analyzer": {
"type": "custom",
"char_filter": [
"html_strip",
"kuromoji_iteration_mark"
],
"tokenizer": "ja_tokenizer",
"filter": [
"cjk_width",
"lowercase",
"kuromoji_stemmer",
"kuromoji_part_of_speech",
"romaji"
]
}
}
}
},
"mappings": {
"_default_": {
"_all": {
"analyzer": "ja_analyzer"
},
"dynamic_templates": [
{
"special_string_fields": {
"match": ".*title.*|.*name.*",
"match_pattern": "regex",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
},
"facet": {
"type": "string",
"analyzer": "facet_analyzer"
},
"ja": {
"type": "string",
"analyzer": "ja_analyzer"
},
"ngram": {
"type": "string",
"analyzer": "ngram_analyzer"
},
"yomi": {
"type": "string",
"analyzer": "yomi_analyzer"
}
}
}
}
},
{
"long_string_fields": {
"match": ".*message.*|.*content.*|.*description.*|.*text.*",
"match_pattern": "regex",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"fields": {
"ja": {
"type": "string",
"analyzer": "ja_analyzer"
}
}
}
}
},
{
"short_string_fields": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
},
"facet": {
"type": "string",
"analyzer": "facet_analyzer"
},
"ja": {
"type": "string",
"analyzer": "ja_analyzer"
}
}
}
}
}
]
}
}
}
}

https://gist.github.com/KunihikoKido/141b3cb72043babcff60

使い方は、この設定内容を config/templates 配下に japanese_template.json など任意の名前を付けて保存した後、新しいインデックスにデータを登録するだけです。適用される対象のインデックスは以下に説明します。

NOTE:日本語解析用に Japanese (kuromoji) Analysis plugin. を使用していますのでインストールしてください。

適用される対象のインデックス
例えば、tiwtterja や、product_ja などインデックス名のサフィックスが ja のインデックスにこのテンプレートが適用される設定です。

{
"japanese_template": {
"template": "*ja",
...
}

_all (デフォルト検索フィールド)
analyzer に、カスタムアナライザーの ja_analyzer を設定して、日本語形態素解析を使って検索可能にしています。トークナイザーはプラグインで提供されている kuromoji_tokenizer を使用しています。

{
...
"_all": {
"analyzer": "ja_analyzer"
},
...
}

short_string_fields(短い文字列用)
ダイナミックテンプレートを使って、各フィールド毎にオリジナルのフィールドに加え、raw、facet、ja の3つのフィールドを定義しています。この設定で、{field name}、{field name}.raw、{field name}.keyword、{field name}.ja 4つの異なる検索用途で使い分けることが可能です。

{
...
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
},
"facet": {
"type": "string",
"analyzer": "facet_analyzer"
},
"ja": {
"type": "string",
"analyzer": "ja_analyzer"
}
}
...
}
  • {field name} フィールド
    コンテンツはデフォルトのアナライザーでインデックスかつ検索可能なフィールドです。
  • {field name}.raw フィールド
    コンテンツはオリジナルデータのまま1つの単語としてインデックスされます。完全一致での検索が可能です。また、オリジナルデータを使った統計情報(ファセット)の取得や絞り込みが可能です。
  • {field name}.facet フィールド
    コンテンツは大文字小文字など正規化され1つの単語としてインデックスされます。主に正規化されたデータを使った統計情報(ファセット)の取得や絞り込みに使用します。
  • {field name}.ja フィールド
    コンテンツは日本語として形態素解析されインデックスされます。主にフィールド検索で日本語検索用途で使用します。

long_string_fields(長い文字列用)
文章などの長い文字列が入ってくるであろうフィールド(.*message.*|.*content.*|.*description.*|.*text.*)は、ファセットなどでは使わないので、各フィールド毎にオリジナルのフィールドに加え、日本語検索用の ja フィールドのみ定義しています。

special_string_fields(スペシャルフィールド用)
タイトルやユーザー名などスペシャルなフィールド(.*title.*|.*name.*)は、raw、facet、ja に加え、検索漏れを少なくする為に ngram フィールドと、よみでも検索できるように、yomi フィールドを定義しています。

各フィールドの日本語検索はオリジナルのフィールドを日本語対応してしまっても良いかなと思いますが、実際には String 型でも、日本語を含まないフィールドも多くあるだろうし、{field name}.ja で明示的に日本語検索の指定を出来るのであえて、ja フィールドを作成しています。この辺りは、おこのみで。

確認してみる

まずは、次のコマンドでマッピング定義なしにいきなりデータをインデックスしてみよう。

# 念のため、インデックスを削除
$ curl -XDELETE 'localhost:9200/testja'
# マッピング定義なしにデータをインデクスしてみる。
$ curl -XPUT 'localhost:9200/testja/type/1' -d '
{
"user_name": "山田 太郎",
"message": "この設定で日本語でもスキーマレスな環境ができたかな?",
"location": "東京都",
"post_date": "2014-03-11T12:28:00"
}'

次のコマンドでマッピング定義を確認すると、設定された動的マッピングが適用されているのが確認できると思います。

$ curl -XGET 'localhost:9200/testja/_mapping/type/?pretty'
{
"testja" : {
...
"user_name" : {
"type" : "string",
"fields" : {
"yomi" : {
"type" : "string",
"analyzer" : "yomi_analyzer"
},
"facet" : {
"type" : "string",
"analyzer" : "facet_analyzer"
},
"ngram" : {
"type" : "string",
"analyzer" : "ngram_analyzer"
},
"raw" : {
"type" : "string",
"index" : "not_analyzed"
},
"ja" : {
"type" : "string",
"analyzer" : "ja_analyzer"
}
}
...
}
}

このインデックスに対して、日本語で検索したい場合は、user_name.ja フィールドを使用し、ファセットの情報を取得したい場合は、user_name.facet フィールドを使用、オリジナルのデータで完全一致検索したい場合は、user_name.raw フィールドを使用するなど、様々な検索要件に対応することが可能です。

前回まとめた「Elasticsearch Kibana でデータの可視化」は、いろいろ考えて、マッピング定義作ってましたけど、この設定しておけば簡単ですね。

まとめ

この方法だと、単純に元のデータよりもインデックスするフィールド数が増えるので、データ数の多いインデックスでの使用などは、インデックス時のパフォーマンスやサイズなど気になるところです。まだ完璧ではありませんが、これで日本語でもスキーレスな環境を手に入れられそうなので、面倒なマッピング定義の設計からは解放されそうな気がします。

--

--