Elasticsearch 2.4.4 設定サンプル等

OSはCentOS 7.2

#elasticsearch.yml
```bat
cluster.name: クラスタ名
node.name: "ノード名"
node.master: true
node.data: true

network.host: '_eth0:ipv4_'
discovery.zen.minimum_master_nodes: 1
discovery.zen.ping.timeout: 3s
discovery.zen.ping.unicast.hosts: ["ホスト名orIPアドレス"]
path.data: /data/elasticsearch
index.number_of_shards: 1
index.number_of_replicas: 0
bootstrap.memory_lock: true

script.groovy.sandbox.enabled: true
script.engine.groovy.inline.aggs: true
script.engine.groovy.indexed.aggs: true
script.engine.groovy.indexed.mapping: true
script.inline: true
script:indexed: true
```
１ノードでmaster兼用設定
多ノードの場合もshardsの数は1,6,12,24,36,60等データノードを随時増やしても割り切れる数を指定するようにしている。
scriptはfunction_scoreでカスタムの_scoreを計算するためのもの。もっと権限絞りこめるが、とりあえず動かすこと優先。
bootstrap.memory_lockは以前はbootstrap.mlockだったものが名称変更。内部的にはまだmlockでも互換性のために残っているが非推奨なのかな。
Multi-AZでZone毎にデータ完結させたい（Zoneを跨がないので通信遅延の削減にもなる）場合は下記を追加

```bat
node.zone: ap-northeast-1a　＊ノードのあるzone
cluster.routing.allocation.awareness.attributes: zone
cluster.routing.allocation.awareness.force.zone.values: ap-northeast-1a,ap-northeast-1c
```


#mapping template
```json
{
    "template" : "適用インデックスの頭文字列*",
    "order" : 0,
    "settings" : {
      "refresh_interval" : "1m",
      "index.analysis.filter.romaji.type" : "kuromoji_neologd_readingform",
      "index.analysis.filter.romaji.use_romaji" : "true",
      "index.analysis.filter.synonym.type" : "synonym",
      "index.analysis.filter.synonym.synonyms_path" : "user_synonym.txt",
      "index.analysis.tokenizer.ja_tokenizer.type" : "kuromoji_neologd_tokenizer",
      "index.analysis.tokenizer.ja_tokenizer.user_dictionary" : "userdict_ja.txt",
      "index.analysis.tokenizer.ja_tokenizer.mode" : "search",
      "index.analysis.analyzer.default.tokenizer" : "ja_tokenizer",
      "index.analysis.analyzer.facet_analyzer.type" : "custom",
      "index.analysis.analyzer.facet_analyzer.char_filter.0" : "html_strip",
      "index.analysis.analyzer.facet_analyzer.tokenizer" : "keyword",
      "index.analysis.analyzer.facet_analyzer.filter.0" : "cjk_width",
      "index.analysis.analyzer.facet_analyzer.filter.1" : "lowercase",
      "index.analysis.analyzer.ws_analyzer.type" : "custom",
      "index.analysis.analyzer.ws_analyzer.char_filter.0" : "html_strip",
      "index.analysis.analyzer.ws_analyzer.tokenizer" : "whitespace",
      "index.analysis.analyzer.ws_analyzer.filter.0" : "cjk_width",
      "index.analysis.analyzer.ws_analyzer.filter.1" : "lowercase",
      "index.analysis.analyzer.ws_analyzer2.type" : "custom",
      "index.analysis.analyzer.ws_analyzer2.char_filter.0" : "html_strip",
      "index.analysis.analyzer.ws_analyzer2.tokenizer" : "whitespace",
      "index.analysis.analyzer.ja_analyzer.type" : "custom",
      "index.analysis.analyzer.ja_analyzer.char_filter.0" : "html_strip",
      "index.analysis.analyzer.ja_analyzer.char_filter.1" : "kuromoji_neologd_iteration_mark",
      "index.analysis.analyzer.ja_analyzer.tokenizer" : "ja_tokenizer",
      "index.analysis.analyzer.ja_analyzer.filter.0" : "cjk_width",
      "index.analysis.analyzer.ja_analyzer.filter.1" : "lowercase",
      "index.analysis.analyzer.ja_analyzer.filter.2" : "kuromoji_neologd_stemmer",
      "index.analysis.analyzer.ja_analyzer.filter.3" : "kuromoji_neologd_part_of_speech",
      "index.analysis.analyzer.ruigi_analyzer.type" : "custom",
      "index.analysis.analyzer.ruigi_analyzer.char_filter.0" : "html_strip",
      "index.analysis.analyzer.ruigi_analyzer.char_filter.1" : "kuromoji_neologd_iteration_mark",
      "index.analysis.analyzer.ruigi_analyzer.tokenizer" : "ja_tokenizer",
      "index.analysis.analyzer.ruigi_analyzer.filter.0" : "cjk_width",
      "index.analysis.analyzer.ruigi_analyzer.filter.1" : "lowercase",
      "index.analysis.analyzer.ruigi_analyzer.filter.2" : "kuromoji_neologd_stemmer",
      "index.analysis.analyzer.ruigi_analyzer.filter.3" : "kuromoji_neologd_part_of_speech",
      "index.analysis.analyzer.ruigi_analyzer.filter.4" : "synonym"
    },
    "mappings" : {
      "_default_" : {
        "dynamic_templates" : [ {
          "string_fields2" : {
            "match_pattern" : "regex",
            "mapping" : {
              "type" : "string",
              "fields" : {
                "raw" : {
                  "index" : "not_analyzed",
                  "type" : "string"
                },
                "ws2" : {
                  "analyzer" : "ws_analyzer2",
                  "type" : "string"
                }
              }
            },
            "match_mapping_type" : "string",
            "match" : "str_特殊なフィールド名.*"
          }
        }, {
          "string_fields" : {
            "match_pattern" : "regex",
            "mapping" : {
              "type" : "string",
              "fields" : {
                "raw" : {
                  "index" : "not_analyzed",
                  "type" : "string"
                },
                "facet" : {
                  "analyzer" : "facet_analyzer",
                  "type" : "string"
                },
                "ws" : {
                  "analyzer" : "ws_analyzer",
                  "type" : "string"
                },
                "ja" : {
                  "analyzer" : "ja_analyzer",
                  "type" : "string"
                },
                "ruigi" : {
                  "analyzer" : "ruigi_analyzer",
                  "type" : "string"
                }
              }
            },
            "match_mapping_type" : "string",
            "match" : "str_.*"
          }
        }, {
          "text_fields" : {
            "mapping" : {
              "type" : "string",
              "fields" : {
                "raw" : {
                  "index" : "not_analyzed",
                  "type" : "string"
                }
              }
            },
            "match_mapping_type" : "string",
            "match" : "txt_.*"
          }
        }, {
          "double_fields" : {
            "match_mapping_type" : "double",
            "mapping" : {
              "type" : "double"
            },
            "match" :"dbl_.*"
          }
        }, {
          "long_fields" : {
            "match_mapping_type" : "long",
            "mapping" : {
              "type" : "long"
            },
            "match" :"l_.*"
          }
        }, {
          "integer_fields" : {
            "match_mapping_type" : "integer",
            "mapping" : {
              "type" : "integer"
            },
            "match" :"i_.*"
          }
        }, {
          "boolean_fields" : {
            "match_mapping_type" : "boolean",
            "mapping" : {
              "type" : "boolean"
            },
            "match" :"b_.*"
          }
        }, {
          "date_fields" : {
            "match_mapping_type" : "date",
            "mapping" : {
              "type" : "date",
              "format": "yyyy-MM-dd hh:mm:ss"
            },
            "match" :"date_.*"
          }
        }, {
          "timezone_fields" : {
            "match_mapping_type" : "date",
            "mapping" : {
              "type" : "date",
              "format": "hh:mm:ss"
            },
            "match" :"tz_.*"
          }
        } ],
        "_all" : {"enabled" : false}
      }
    },
    "aliases" : { }
}
```
投入するデータの型をカラム名で決める方式。途中でスキーマ変わるかもとか仕様が不確定な場合とか。
str_は文字列解析有り、txt_は文字列解析無し。
string_fields2はアルファベットの大文字小文字を区別したい場合用
NGRAMは検索精度が悪くなりすぎるので使いません。
refresh_interval設定はBulk転送時の負荷を下げるため。
　elasticsearch.ymlでは設定できなくなったようなのでテンプレートに移動
　反映待ち1分だと長過ぎる場合は30sとかに、単位付けないとms扱いらしい。デフォルト値は1s
＊2.4以降から？5.0でindex系の設定がelasticsearch.ymlで設定できなくなった影響かも
　情報元：https://github.com/elastic/elasticsearch/issues/20584


#/etc/sysconfig/elasticsearch
```bat
MAX_LOCKED_MEMORY=unlimited
ES_HEAP_SIZE=8g
```
搭載メモリが16GBの例
ただし、8gだと丁度半分なので起動時にWARNINGが発生する。
＊31gまでか搭載メモリの半分まで制限がある。
＊ES_HEAP_NEWSIZEと両方指定するとCPU負荷が上がりっぱなしになる。
/etc/init.d/elasticsearch
で指定する例も見かけるが、現状それではヒープサイズはデフォルトのまま。
＊「ps -aelf |grep elas」とかして-Xms,-Xmxとかの値で確認できる。

#/etc/systemd/system/elasticsearch.service
CentOS系(6.8, 7.2で確認)ではbootstrap.memory_lock: trueのために追加でsystemdの設定が必要
cp /usr/lib/systemd/system/elasticsearch.service /etc/systemd/system/elasticsearch.service
雛形をコピーして編集（コメントアウトを外す）
```
LimitMEMLOCK=infinity
```
変更したら
systemctl daemon-reload
/etc/init.d/elasticsearch restart


#検索のクエリ例(PHP)
```php
$query = [
  'query' => [
    'function_score' => [
      'query' => [
        'filtered' => [
          'query' => [
            'bool' => [
              'must' => [
                [ 'query_string' => [
                    'query' => $検索文字列,
                    'default_field' => 'str_検索対象.ws'
                  ]
                ]
              ]
            ]
          ],
          'filter' => [
            'bool' => [
              'must' => [
                [ 'term' => [ "$フラグ" => true ] ]
              ],
              'must_not' => [
                [ 'wildcard' => [ 'str_特殊なフィールド名.ws2' => $検索文字列 ] ]
              ]
            ]
          ]
        ]
      ],
      'script_score' => [
        "script" => "(float)_score * doc['dbl_score_coff'].value + doc['dbl_score_term'].value"
      ],
      'boost_mode' => 'replace'
    ]
  ]
  ,'from' => 0
  ,'size' => 10
];
```
ポイントは
・function_scoreの中にfilteredクエリ。逆はエラー。
・script内の_scoreは前に(float)を付けないと結果が整数値になる
・wildcardは「検索対象と全く同じであること」という条件に使用している。
　→matchでいい。matchだと0/1ではなく、query_stringで完全一致したときと同じようなスコアを得られる。

#他
・/var/log/elasticsearchを追加ディスクのシンボリックリンクに変更する。
＊ルートと同じディスクに置かない。
・query_stringの特殊文字対策（とりあえず削る）
```
  $k = preg_replace("/\'/", ' ', $k); 
  $k = preg_replace("/\"/", ' ', $k); 
  $k = preg_replace("/\//", ' ', $k); 
  $k = preg_replace("/\{/", ' ', $k); 
  $k = preg_replace("/\}/", ' ', $k); 
  $k = preg_replace("/\(/", ' ', $k); 
  $k = preg_replace("/\)/", ' ', $k); 
  $k = preg_replace("/\[/", ' ', $k); 
  $k = preg_replace("/\]/", ' ', $k); 
  $k = preg_replace("/\~/", ' ', $k); 
  $k = preg_replace("/\^/", ' ', $k); 
  $k = preg_replace("/\$/", ' ', $k); 
  $k = preg_replace("/\+/", ' ', $k); 
  $k = preg_replace("/\./", ' ', $k); 
  $k = preg_replace("/\?/", ' ', $k); 
  $k = preg_replace("/\*/", ' ', $k); 
  $k = preg_replace("/:/", ' ', $k); 
  $k = preg_replace("/;/", ' ', $k); 
  $k = preg_replace("/,/", ' ', $k); 
  $k = preg_replace("/!/", ' ', $k); 
  $k = preg_replace("/  +/", ' ', $k); 
  $k = preg_replace("/\t+/", ' ', $k); 
  $k = preg_replace("/\s+/", ' ', $k); 
  $k = trim($k); 
```

#求む
・データを完全にメモリー上に置く設定
　＊path.dataをtmpfs上に置くと安定して早くなるが、データ量が少ない場合しか使えない。