Elasticsearchのmapping

(2017-02-09)

Dynamic mappingがあるので、自分で設定しなくてもデータは入るけど、 自分でやるとindexやanalyzerなどの設定が詳細にできるし、意図しないmappingを避けることもできる。 バージョンは5.2。

$ curl -XPOST 'localhost:9200/test_hoge/fuga?pretty' -d'
{
    "name": 0 
}
'

$ curl -XPOST 'localhost:9200/test_hoge/fuga?pretty' -d'
{
    "name": "sambaiz"
}
'

{
  "error" : {
    "root_cause" : [
      {
        "type" : "mapper_parsing_exception",
        "reason" : "failed to parse [name]"
      }
    ],
    "type" : "mapper_parsing_exception",
    "reason" : "failed to parse [name]",
    "caused_by" : {
      "type" : "number_format_exception",
      "reason" : "For input string: \"sambaiz\""
    }
  },
  "status" : 400
}

Mapping parameters

index

falseにするとindexしない。クエリで必要ないものはfalseにする。

"memo": { "type": "text", "index": false }

store

デフォルトでフィールドはindexされるがstoreはされず、metaの_sourceとしてオリジナルのJSONがstoreされている。 サイズの大きなフィールドがあるなど、選んでstoreする場合はtrueにする。stored_fieldsで必要なものだけとってくることができる。

"memo": { "type": "text", "store": true }

Meta fields

_all

全てのフィールドをスペースでつなげた一つの文字列にしてanalyzeし、indexする。storeはされない。

フィールドの区別なく検索できたりするけどindexするのにコストがかかるので必要ないならfalseにする。

"_all": { "enabled": false }

_source

オリジナルのJSONを含み、indexはされずstoreされる。

無効にするとストレージを節約できるが、 まずはcompression levelを上げてみる。 無効にするとupdateやreindexができなくなったりするので有効のままにしている。

"_source": { "enabled": false }

analysis

textをどのようにanalyzeするか。

Analyzerは Character filtersで文字列を加工してTokenizerでトークンに分割してからToken filtersでトークンを取り除いたり変更したりするもの。 自分でこれらを組み合わせて定義することもできる。

日本語のAnalyzerとしてkuromojiがある。

$ bin/elasticsearch-plugin install analysis-kuromoji
"name": { "type": "keyword", "analyzer": "kuromoji"}
$ curl -XGET 'localhost:9200/_analyze?pretty' -d '
{
  "analyzer" : "kuromoji",
  "text" : "Character filtersで文字列を加工します"
}'

{
  "tokens" : [
    {
      "token" : "character",
      "start_offset" : 0,
      "end_offset" : 9,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "filters",
      "start_offset" : 10,
      "end_offset" : 17,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "文字",
      "start_offset" : 18,
      "end_offset" : 20,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "列",
      "start_offset" : 20,
      "end_offset" : 21,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "加工",
      "start_offset" : 22,
      "end_offset" : 24,
      "type" : "word",
      "position" : 6
    }
  ]
}

datatype

文字列

5.Xからstringは廃止され textkeywordになった。

textはメールの文章のようなfull-textの値で、ある単語がそれぞれの文章に含まれるかということを調べることができる。 メールアドレスのようなデータの場合はkeywordを使う。

"email": { "type": "keyword" }

数値

long(64bit), integer(32bit), short(16bit, ~32767), byte(8bit, ~127), double, floatとか。

"age": { "type": "short" }

日付

こんな感じ。

"date": {
  "type":   "date",
  "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}

Boolean

"success": { "type": "boolean" }

Object

propertiesの中に書く。

"hoge": {
  "properties": {
    "fuga": { "type": "boolean" }
  }
}

nested

objectの配列。

"hoge": {
  "type": "nested"
  "properties": {
    "fuga": { "type": "boolean" }
  }
}
"hoge": [{"fuga": true}]

登録

$ curl -XPUT 'localhost:9200/test_index?pretty' -d'
{
  "mappings": {
    "test_type": { 
      "_all":       { "enabled": false  }, 
      "properties": { 
        "name": { "type": "keyword", "store": true },
        "description": { "type": "text", "analyzer": "kuromoji" },
        "memo": { "type": "text", "index": false }
      }
    }
  }
}
'

$ curl -XPOST 'localhost:9200/test_index/test_type?pretty' -d'
{
    "name": "sambaiz",
    "description": "青い海",
    "memo": "白い空"
}
'

logstashのようにindex名に日付が付いているような場合は indices-templateで設定する。

$ curl -XPUT localhost:9200/_template/hogefuga-template -d '
{
  "template" : "hogefuga-*",
  "mappings" : {
    ...
  }
}
'

取得

まずはデータが入っていることを確認。

$ curl 'localhost:9200/test_index/test_type/_search?pretty'
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "test_index",
        "_type" : "test_type",
        "_id" : "AVodMAubr8EtIroFs0eP",
        "_score" : 1.0,
        "_source" : {
          "name" : "sambaiz",
          "description" : "青い海",
          "memo" : "白い空"
        }
      }
    ]
  }
}

stored_fieldsを付けてリクエスト。_sourceが含まれず、storeがtrueなnameだけが返ってくる。

$ curl 'localhost:9200/test_index/_search?pretty&stored_fields=name,description,memo'
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "test_index",
        "_type" : "test_type",
        "_id" : "AVodMAubr8EtIroFs0eP",
        "_score" : 1.0,
        "fields" : {
          "name" : [
            "sambaiz"
          ]
        }
      }
    ]
  }
}

クエリを付けてリクエスト。indexされてないmemoではひっかからない。

$ curl -XPOST 'localhost:9200/test_index/test_type/_search?pretty' -d '
{
  "query":{
    "query_string":{
      "default_field" : "description",
      "query": "青い海"
    }
  }
}'

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    ...
  }
}
$ curl -XPOST 'localhost:9200/test_index/test_type/_search?pretty' -d '
{
  "query":{
    "query_string":{
      "default_field" : "memo",
      "query": "白い空"
    }
  }
}'

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

参考

Elasticsearchのインデキシングに関するパフォーマンス検討 - @johtaniの日記 2nd