Elasticsearch マッピング

Elasticsearch Mapping — ドキュメントスキーマと検索精度を最適化するためのマッピング定義


Elasticsearch におけるマッピングとは、リレーショナルDBでいうところのテーブル定義に相当します。しかし、単にデータを格納する為のフィールドを用意して型を設定するだけではありません。Elasticsearch では、フィールドの型の他に言語解析処理などのドキュメントを検索可能にする為の各種設定が可能です。

スキーマーレスが一つの特徴の Elasticsearch では、ドキュメントをインデックスすると自動的に各フィールド毎にフィールドタイプなどのマッピングが自動で設定されインデックスが作成されます。また、事前にマッピングを設定可能な仕組みとなっています。


自動マッピング

まずは以下の内容で、インデックスを作成し自動マッピングについて見て行きます。

  • ブログ記事毎に1つのドキュメントをインデックス
  • 各ドキュメントはドキュメントタイプ story にインデックス
  • ドキュメントタイプ story は、blog インデックスに含まれる
  • インデックスは ローカルのElasticsearch クラスター内に存在

まずは、ドキュメントを登録します。次のコマンドでは、blog インデックス配下に story ドキュメントタイプを作成し、そのドキュメントタイプ内に1つのドキュメントを作成しています。

$ curl -XPUT 'localhost:9200/blog/story/1' -d '{
"title" : "Elasticsearch 特徴まとめ",
"subtitle" : "Elasticsearch Features — 主にシステムを中心とした特徴まとめ","contents": "Elasticsearch とは?簡単に説明すると、クラウド向けに構築された、RESTful な APIを提供する分散型のサーチエンジンアプリケーション。オープンソースで提供されています。",
"tags" : ["Elasticsearch", "Search-Engine"],
"pub_date" : "2014-04-10T01:40:00",
"author" : "Kunihiko Kido",
"views" : 82,
"reads": 60,
"read_ratio": 0.73,
"enabled": true
}'

マッピング定義を確認すると次のようになりました。

$ curl -XGET 'localhost:9200/blog/_mapping/story?pretty=true'
{
"blog" : {
"mappings" : {
"story" : {
"properties" : {
"author" : {
"type" : "string"
},
"contents" : {
"type" : "string"
},
"enabled" : {
"type" : "boolean"
},
"pub_date" : {
"type" : "date",
"format" : "dateOptionalTime"
},
"read_ratio" : {
"type" : "double"
},
"reads" : {
"type" : "long"
},
"subtitle" : {
"type" : "string"
},
"tags" : {
"type" : "string"
},
"title" : {
"type" : "string"
},
"views" : {
"type" : "long"
}
}
}
}
}
}

以下の基本的な型、ルールに従って、自動マッピングされたことがわかるかと思います。

1) 基本的なフィールド型
String: string
Whole number: byte, short, integer, long
Floating point: float, double
Boolean: boolean
Date: date
2) 基本的な型マッピングルール
JSON type: | Field type:
-------------------------------------|---------------
Boolean: true or false | “boolean”
Whole number: 123 | “long”
Floating point: 123.45 | “double”
String, valid date: “2014-09-15" | “date”
String: “foo bar” | “string”

ほとんどの型で、各フィールドの属性オプションは、type しか指定されていませんが、指定されていない属性オプションは、デフォルトが使用されます。例えば、タイプが “string” のフィールドでは、フィールド属性のindexオプションは”analyzed” が適用されています。これは、フィールドオリジナルの値を解析処理し、その結果の値を使って、インデックスを作成、または検索クエリを発行することを意味します。

NOTE:
フィールドタイプによって指定できる属性オプションが異なります。興味のある方は、Core Types のドキュメントを参照してみてください。

手動マッピング定義が必要なケース

データの内容によって、自動でマッピングされる型や解析処理仕様などで検索要件を満たせていればこの自動マッピングの恩恵を受けることができますが、経験上ほとんどの場合マニュアルでのマッピング定義が必要になるのではないかと思います。

例えば、tags フィールドのようにオリジナルデータの内容で正確に検索できるようにする為には、index オプションに”not_analyzed”を設定する必要があります。

{
"tags": {
"type": "string",
"index": "not_analyzed"
}
}

また、title や subtitle 、contents フィールドなどは日本語での全文検索を必要としますので、analyzer オプションに日本語解析用の analyzer を設定する必要があります。

{
"title": {
"type": "string",
"analyzer": "japanese‎"
},
"subtile": {
"type": "string",
"analyzer": "japanese‎"
},
"contents": {
"type": "string",
"analyzer": "japanese‎"
}
}
NOTE:
“Japanese” というアナライザーはデフォルトでは存在しませんので、バンドルされている NGram Tokenizer や Japanese (kuromoji) Analysis for Elasticsearch プラグインなどをベースに日本語用のアナライザーを定義して設定します。

ブログの記事をインデックスする例をあげましたが、このようにシンプルな例でもちゃんと検索できるようにする為には、マッピング定義が重要であるということが理解できたかと思います。

手動マッピング

マッピングを手動で設定する方法は、インデックスを新規で作成するときに一緒に設定する方法と、作成済みのインデックスに /_mapping エンドポイントを使って、新しいタイプの追加・更新する方法があります。

IMPORTANT!:
既存のインデックスにデータが既にインデックスされている場合、/_mapping エンドポイントを使って、新しいフィールドタイプを追加することはできますが、更新はできません。

まずは、新規作成から

自動マッピングで作成した blog インデックスを次のコマンドで削除してください。

$ curl -XDELETE 'localhost:9200/blog'

次に、blog インデックスの作成とマッピング定義を行います。title、subtitle、contents の analyzer オプションにカスタムで用意した japanese アナライザーを設定しています。(※ あえて、tags フィールドは定義してません)

$ curl -XPUT 'localhost:9200/blog' -d '
{
"mappings" : {
"story" : {
"properties" : {
"author" : {
"type" : "string"
},
"contents" : {
"type" : "string",
"analyzer": "japanese‎"
},
"enabled" : {
"type" : "boolean"
},
"pub_date" : {
"type" : "date",
"format" : "dateOptionalTime"
},
"read_ratio" : {
"type" : "double"
},
"reads" : {
"type" : "long"
},
"subtitle" : {
"type" : "string",
"analyzer": "japanese‎"
},
"title" : {
"type" : "string",
"analyzer": "japanese‎"
},
"views" : {
"type" : "long"
}
}
}
}
}
}'

マッピングの定義を見てみます。

$ curl -XGET 'localhost:9200/blog/_mapping/story?pretty=true'
{
"blog" : {
"mappings" : {
"story" : {
"properties" : {
"author" : {
"type" : "string"
},
"contents" : {
"type" : "string",
"analyzer" : "japanese‎"
},
"enabled" : {
"type" : "boolean"
},
"pub_date" : {
"type" : "date",
"format" : "dateOptionalTime"
},
"read_ratio" : {
"type" : "double"
},
"reads" : {
"type" : "long"
},
"subtitle" : {
"type" : "string",
"analyzer" : "japanese‎"
},
"title" : {
"type" : "string",
"analyzer" : "japanese‎"
},
"views" : {
"type" : "long"
}
}
}
}
}
}

ちゃんと反映されているのが確認できます。

次に、今作成したインデックスのマッピング定義に新しい tags フィールド用の定義を /_mapping エンドポイントを使って追加します。正確な値で検索できるように “index” 属性に “not_analyzed” を設定しています。

$ curl -XPUT 'localhost:9200/blog/_mapping/story' -d '
{
"properties": {
"tags": {
"type": "string",
"index": "not_analyzed"
}
}
}'

マッピングの定義を見てみます。

$ curl -XGET 'localhost:9200/blog/_mapping/story?pretty=true'
{
"blog" : {
"mappings" : {
"story" : {
"properties" : {
"author" : {
"type" : "string"
},
"contents" : {
"type" : "string",
"analyzer" : "japanese‎"
},
"enabled" : {
"type" : "boolean"
},
"pub_date" : {
"type" : "date",
"format" : "dateOptionalTime"
},
"read_ratio" : {
"type" : "double"
},
"reads" : {
"type" : "long"
},
"subtitle" : {
"type" : "string",
"analyzer" : "japanese‎"
},
"tags" : {
"type" : "string",
"index" : "not_analyzed"
},

"title" : {
"type" : "string",
"analyzer" : "japanese‎"
},
"views" : {
"type" : "long"
}
}
}
}
}
}

ちゃんと反映されているのが確認できます。

マッピングのテスト

analyze API を使って、設定したマッピングの動作を確認することができます。同じ “string” 型 の author フィールド と tags フィールドを比較するのが分かりやすいです。次の2つのコマンドの出力結果を確認してみてください。

① author フィールド:analyzed
$ curl -XGET 'localhost:9200/blog/_analyze?field=author&pretty=true' -d "Search-Engine"
② tags フィールド:not_analyzed
$ curl -XGET 'localhost:9200/blog/_analyze?field=tags&pretty=true' -d "Search-Engine"

コマンドの実行結果は、次のようになるはずです。

  • ① author フィールド:analyzed
    “search” と “engine” の2つの単語として解析結果表示
  • ② tags フィールド:not_analyzed
    “Serach-Engine” の1つの単語として解析結果表示

この結果は、author フィールドに仮に “Search-Engine” という文字列をインデックスした場合には、”search”、”Search Engine”、”Engine”、などいろいろなパッターンで検索にマッチすることを表し、tags フィールドに対する検索は、”Search-Engine” という正確な単語でしかマッチしないことを表しています。


今回は、Elasticsearch のマッピングについてまとめてみました。これまで説明してきたように、マッピングは検索精度最適化する為に重要な役目を持っていると理解していただけたかと思います。検索エンジンの設計の難しいところは、データの内容と、そのデータをどのように検索するのかの両方を考えて設計する必要があるところだと思います。説明しきれていませんが、Elasticsearch のフィールドは、フラットなフィールド定義の他にマルチレベルのオブジェクトを定義したり、かなり複雑な定義も可能ですので、その辺りは別途まとめたいと思います。