Elasticsearch導入前に気を付けておきたいこと！


# Elasticsearch導入前に気を付けておきたいこと

## はじめに

* Elasticsearchを仕事で使うことになったので導入前に考えるであろうことを調査・検証し、まとめてみました。
* 記載されている内容はほぼ公式ドキュメントから引っ張ってきています。それぞれの章にリンクを付けてありますので、記載されていること以外に気になる部分があればそちらを参照されるとよいと思います。

## インストールについて

* ここで設定している内容はすべてansibleのスクリプトに落としてあります。
* 5分あればcurl,javaのインストール、Elasticsearchのインストール・初期設定、プラグインのインストールが終わるはずです。
* https://github.com/uzresk/ansible-elasticsearch2

## 環境

* CentOS6.7
* Java1.8.0_45
* Elasticsearch 2.1.1

## [HeapSizeの設定](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html#_environment_variables)

* Elasticsearchが使うJavaのヒープサイズを設定する。目安は物理メモリの半分弱。xmsとxmxは自動的に同じ値が設定されます。嫌ならES_MIN_MEMとES_MAX_MEMを設定します。

```/etc/init.d/elasticsearch
export ES_HEAP_SIZE=512m
```

## [File Descriptors](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html#file-descriptors)

* Openできるファイルの数。「java.io.IOException: Too many open files」なんてメッセージはは大抵これが原因。
* 32K、64Kがおすすめ。Elasticsearch2.1のデフォルトでは65535になっており、起動スクリプト内で設定してあるので不足するようであれば追加する。

```/etc/init.d/elasticsearch
MAX_OPEN_FILES=65535
```

* 設定の確認(OSからみる）

```
[root@es1 vagrant]# cat /proc/[ES processID]/limits
Limit                     Soft Limit           Hard Limit           Units
Max open files            65535                65535                files
～
```

* 現在どのくらいオープンしているか？

```
[root@es1 vagrant]# ls -l /proc/12831/fd/ | wc -l
93
```

* 設定の確認(ESからみる）

```
[root@es1 tms]# curl http://localhost:9200/_nodes/stats?pretty
```

## [仮想メモリ](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html#vm-max-map-count)

* 1つのプロセスで使えるメモリマップの数の上限は「sysctl vm.max_map_count」で確認できる。デフォルトは65530
* linuxのデフォルトで足りない恐れがあるから上げたほうがよいとのこと。ちなみにrpmでインストールした場合は自動的にこの値が設定されるので設定は不要です。
* この値の妥当性がイマイチわかっていない。

```
sysctl -w vm.max_map_count=262144
```

## [メモリの設定](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html#setup-configuration-memory)

* JVMがスワップし始めるとElasticsearch（Lucene？）は重くなるようです。
* mlockallオプションを有効にすることで固定メモリで動くようにするのがよさそう。
* 起動スクリプトの中ではES_HEAP_SIZEの値をMAX_LOCKED_MEMORYに設定しているようでした。

``` /etc/elasticsearch/elasticsearch.yml
bootstrap.mlockall: true
```

* 設定を確認する(mlockall : trueになっていることを確認する）

```
[root@es1 vagrant]# curl http://localhost:9200/_nodes/process?pretty
{
  "cluster_name" : "elasticsearch",
  "nodes" : {
    "qIwzWBwQSyessRMxjKM4GQ" : {
      "name" : "Nitro",
      "transport_address" : "10.0.2.15:9300",
      "host" : "10.0.2.15",
      "ip" : "10.0.2.15",
      "version" : "2.1.1",
      "build" : "40e2c53",
      "http_address" : "10.0.2.15:9200",
      "process" : {
        "refresh_interval_in_millis" : 1000,
        "id" : 13058,
        "mlockall" : true
      }
    }
  }
}
```

* これが有効にならない場合にどうするかという記事をネット上でちらほら見る。
* 公式ドキュメントによるとulimitの設定を行うとかtemporary directoryの場所を変更しろと書いてある。

## [Elasticsearchの設定](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html#settings)

### クラスタ名

* デフォルトはelasticsearch

``` /etc/elasticsearch/elasticsearch.yml
cluster.name: test-cluster
```

### ノード名

* ノード名はデフォルトではMarvelのキャラクタ名がランダムに振られる。わかりにくいので設定しておく。
* ホスト名と一緒が良いのであれば${HOSTNAME}という感じで設定する
* ${prompt.text}を指定すると起動時にプロンプトが表示されノード名が入力できる。

``` /etc/elasticsearch/elasticsearch.yml
node.name: ${HOSTNAME}
```

### ネットワークホストの設定

* これを設定しないとlocalhostからしかhttp接続できないので必ず設定する。
* どのホストからでもつなげる場合はこのようにする

``` /etc/elasticsearch/elasticsearch.yml
network.host: 0.0.0.0
```

* vagrant上ではnetwork.hostがeth0を指してしまうのでeth1を指すように変更しておく必要がある。

``` /etc/elasticsearch/elasticsearch.yml
network.host: '_eth1:ipv4_'
```

* network.hostを設定することで、network.bind_host(httpリクエストを受け付けるhost）、network.publish_host(クラスタ時にノード接続するために利用するhost）は同じものが設定される。詳しくは[こちら](https://www.elastic.co/guide/en/elasticsearch/reference/2.x/modules-network.html)を参照
* 接続要求はどこからでも受け付けるようにし、クラスタで利用するIPはeth1を利用するには以下のように設定する。

```
network.bind_host: 0.0.0.0
network.publish_host: _eth1:ipv4_
```

### インデックスの格納場所

* デフォルトは/var/lib/elasticsearch。この下にnodes/0/_state/global-0.stという感じでインデックスが格納される。これを変更するにはこのようにする

``` /etc/elasticsearch/elasticsearch.yml
path.data: /var/lib/elasticsearch/
```

* data.pathはカンマ区切りで書くこともでき、複数のSSDに対してRAID0(ストライピング）するようなことも可能。どちらかが故障するとデータロストするので性能とのトレードオフ。

### ログの出力場所

* デフォルトは/var/log/elasticsearch。これを変更するにはこのようにする
* 起動スクリプト内でもLOG_DIRという環境変数があるがelasticsearch.ymlの設定の方が強い

``` /etc/elasticsearch/elasticsearch.yml
path.logs: /var/log/elasticsearch/
```

### クラスタ関連

* Elasticsearch2.0からはMulticastが廃止され、Unicastのみになりました。詳しくは[Elasticsearch Unplugged - 2.0におけるネットワークの変更(日本語訳)](http://blog.johtani.info/blog/2015/08/28/elasticsearch-unplugged-ja/)を読むと良い。
* unicastでクラスタメンバを見つけるための設定。デフォルトは以下のように自分しか登録されていない。

``` /etc/elasticsearch/elasticsearch.yml
discovery.zen.ping.unicast.hosts: ['127.0.0.1', '[::1]']
```

* スプリットブレイン対策。最小のマスタノード数を決める設定になり、計算式はn/2+1で設定する。1台構成の場合は1/2+1=1となります。

``` /etc/elasticsearch/elasticsearch.yml
discovery.zen.minimum_master_nodes: 1
```

### ソフト・ハードコミット頻度の変更

* デフォルトでは1sec毎にコミットされるわけですがそんなに高頻度じゃなくても要件満たせる場合や巨大なindexを登録する場合は長く設定しておくことでindex更新時の負荷を減らす。
* 単位はs or msec(書かないとmsec) -1を設定することでコミットしなくなります。
* [Dynamic Index Settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html)

```
[root@es1 tms]# curl -XPUT http://localhost:9200/[index name]/_settings -d '
index:
    refresh_interval: 30s
'
{"acknowledged":true}
```

* ハードコミットの頻度は[Translog](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html#index-modules-translog)に記載されている。
* デフォルトのflushタイミングは、サイズが512mになったタイミングと5秒おき。
* その他操作の回数でflushするタイミングを決めることもできます。

## Shard、Replicaについて

### Shard

* indexを物理的に分割したもの。ノードごとに配置すると考えたほうがシンプル。ただし、一つのノードで複数shard持つことは可能。
* デフォルトは5。index作成後には変更できないのでindex作成時に指定してあげる。
* mapping.jsonで指定する場合は以下のようにする

```
{
  "settings": {
    "index": {
      number_of_shards: 3,
      number_of_replicas: 0
    },
```



### Replica

* PrimaryShardのコピーのことをReplicaShardという。PrimaryShardがダウンしたらReplicaShardがPrimaryShardに昇格する。
* 2台構成で冗長化を担保しようとするとshardの数が2.Replicaが1となる。
* 1台構成でelasticsearchを起動するとreplica先がないのでcluster healthがunhealthyになっている。これを解消するにはreplicaの数を0にすればよい。
* replicaの数については後で変更が可能

```
[root@es1 tms]# curl -XPUT 'http://localhost:9200/tms/_settings' -d '
index:
  number_of_replicas: 1
'
```

### memo

* ハードウェア故障に対する冗長性への対応はノード(=elasticsearchのプロセス）の数を増やし、ReplicaShardを増やすことで比較的対応は簡単。
* indexのサイズが非常に大きいとか更新頻度が多いという部分に関してはShardの数がポイントになってくるが、後では変更できないので将来を見越したサイジングが必要
* 参考になりそうなのは[Capacity Planning](https://www.elastic.co/guide/en/elasticsearch/guide/current/capacity-planning.html#capacity-planning)や[Elasticsearchのインデキシングに関するパフォーマンス検討](http://blog.johtani.info/blog/2014/09/09/performance-considerations-for-elasticsearch-indexing/)

## _allフィールドの無効化

* 元ネタは[Elasticsearchのインデキシングに関するパフォーマンス検討](http://blog.johtani.info/blog/2014/09/09/performance-considerations-for-elasticsearch-indexing/)
* インデクシングされたデータはすべて_allに入るのでこれを無効化することでインデクシングのパフォーマンスが良くなる。
* マッピング定義にて以下のように記載すれば反映される

```
"_all" : {"enabled" :  false},
```

# ログ

* ログの設定は/etc/elasticsearch/logging.ymlで行う。log4jの要領で書けるので設定は難しくない。ローテートはやってくれるがファイルの削除はやってくれないのでshellなどを仕込みましょう。
* スローログの出力も可能なので必要に応じて設定する。

---

# [バックアップ＆リストア](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html#_repositories)

* 2台構成以上でレプリケーションを組んでいる状態であれば、サーバの物理障害に対する備えとしては十分だろう。2重障害を考えるのであればElasticsearchおすすめの3nodeでクラスタを組むのが良い。
* インデックスが壊れたとかおかしなデータが入ってしまったという所謂論理障害に対して備えをしておく必要がある。
* マニュアルを見る限りロールフォワードの機能はないのでバックアップ間隔分データがロストすることを許容する。RPOに厳しい場合だと別のデータソース（RDBやKVS）から差分データを戻してあげることを検討する必要があることが注意点。
* ここではelasticsearchのsnapshot機能を使ったバックアップ・リストアを考える

## Backup

* snapshotを作るには[リポジトリ](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html#_repositories)をまずは作る必要がある。複数のバックアップを取得する場合は配列で記載することも可能。またパスの書き方はUNCでも書ける。

``` /etc/elasticsearch/elasticsearch.yml
path.repo: /var/lib/elasticsearch/backup
```

* my_backupという名前で/var/lib/elasticsearch/backupを認識させる

```
[root@es1 tms]# curl -XPUT 'http://192.168.1.41:9200/_snapshot/my_backup' -d '{
  "type": "fs",
  "settings": {
    "location": "/var/lib/elasticsearch/backup",
    "compress": true
  }
}'
```

* リポジトリを登録する(/var/lib/elasticsearch/backup/my_backupディレクトリができるはず）

```
[root@es1 tms]# curl -XPUT 'http://192.168.1.41:9200/_snapshot/my_backup' -d '{
  "type": "fs",
  "settings": {
    "location": "my_backup",
    "compress": true
  }
}'
```

* [snapshotを取得する](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html#_snapshot)

```
[root@es1 my_backup]# curl -XPUT http://192.168.1.41:9200/_snapshot/my_backup/snapshot_1?WAIT_FOR_COMPLETION=true
{"accepted":true}

[root@es1 my_backup]# ls -l /var/lib/elasticsearch/backup/my_backup/
total 16
-rw-r--r--. 1 elasticsearch elasticsearch   28 Jan  7 01:09 index
drwxr-xr-x. 3 elasticsearch elasticsearch 4096 Jan  7 01:09 indices
-rw-r--r--. 1 elasticsearch elasticsearch  121 Jan  7 01:09 meta-snapshot_1.dat
-rw-r--r--. 1 elasticsearch elasticsearch  178 Jan  7 01:09 snap-snapshot_1.dat
```

* 取得したsnapshotの情報を個別、全件で取得するには以下のようにする

```
curl -XGET http://192.168.1.41:9200/_snapshot/my_backup/snapshot_1
```

```
curl -XGET http://192.168.1.41:9200/_snapshot/my_backup/_all
```

* snapshotの削除

```
curl -XDELETE http://192.168.1.41:9200/_snapshot/my_backup/snapshot_1
```


## [Restore](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html#_restore)

* snapshotからのリストアが可能です。
* ここではtmsという名前のindexをrestored_tmsという名前でリストアしている

```
[root@es1 my_backup]# curl -XPOST http://192.168.1.41:9200/_snapshot/my_backup/snapshot_1/_restore -d '
{
  "indices": "tms",
  "ignore_unavailable": "true",
  "include_global_state": false,
  "rename_pattern": "tms",
  "rename_replacement": "restored_tms"
}'
```

* 名前を変えつつreplicaの数を変更することなんかもできます。
* ここではreplicaですけどrefresh_intervalの値など変えることもできます。当たり前かもしれませんがshardの数は変えられません。

```
[root@es1 my_backup]# curl -XPOST http://192.168.1.41:9200/_snapshot/my_backup/snapshot_1/_restore -d '
{
  "indices": "tms",
  "index_settings": {
    "index.number_of_replicas": 1
  },
  "ignore_unavailable": "true",
  "include_global_state": false,
  "rename_pattern": "tms",
  "rename_replacement": "restored_tms2"
}'
```

---

# 監視

## リソース周り

* top(CPUがあふれてないか）
* iostat（IOがあふれていないか）
* ps,free,sar(メモリの使用量が一定か？swapしていないか）
* open_file_descriptors
* jvm(jstatとかで取得する。mlockall=trueなら優先度低）
* このへんも実はcurlで取れちゃうのです - [NodeStats](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html#cluster-nodes-stats)参照

## サービス監視

* クラスタの確認はstatusがgreen or yellowであることを確認するとよさそう。[springboot ActuatorのElasticsearchのhealthcheckのソース](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchHealthIndicator.java)をみても同様だった。

```
[root@es1 tms]# curl -XGET http://localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "test-cluster",
  "status" : "yellow",
  ・・・・
}
```

## LBからのヘルスチェック

* LB配下に複数のノードをぶら下げるときにLBからのヘルスチェックのURLをどうするかを考える必要がある。がこれはちょっと悩んだ。
* _nodesにはノードが良い状態なのかを判断する術は用意されていないみたいだった。http://[IP]:9200/で200が返却されることでOKとみなして良さそう。つまり上記（サービス監視）に記載されているクラスタの監視のURLと同じでよさそう。

## JMX

* 起動スクリプト内でES_JAVA_OPTSを設定する。
* mbeansの情報でelasticsearchの情報が特に何も得られるわけではないのと、そもそもelasticsearchの標準APIで色々取れるのであえてJMX経由で定期的に取るものはなさそうだ。

```/etc/init.d/elasticsearch
export ES_JAVA_OPTS="-Djava.rmi.server.hostname=192.168.1.41 -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=7085 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
```

## その他

- [CAT API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html)は便利そうなので押さえておきたい

---

# コマンド(参考)

## elasticsearchの状態を確認する

* クラスタ名、ノード名、NW周りの設定が見れる
* 
```
[root@es1 ~]# curl http://localhost:9200/_nodes/process?pretty
```

## mappingの登録

* mappingが作られた時点でシャードが作られる

```
[root@es1 tms]# curl -XPOST http://localhost:9200/[index name] -d @mapping.json
```

## indexの登録(bulk)

```
[root@es1 tms]# curl -X POST http://localhost:9200/_bulk --data-binary @data.json
```

## indexの削除

```
[root@es1 index]# curl -XDELETE 'http://localhost:9200/[index name]*'
```

## indexの設定を確認する

```
[root@es1 tms]# curl -XGET 'http://localhost:9200/tms/_settings?pretty'
```