Elasticsearchを使う前に先に読んでおくべきドキュメント

# Elasticsearchを使う前に先に読んでおくべきドキュメント

## はじめに

Elasticsearchの運用は難しい。最初から知っておけばよかった・・・というプラクティスをまとめておく。
原則的に6.8系の公式ドキュメントに記載されていることを書いていく。
そして、やっぱりドキュメントを呼んで理解して使うことが大事。

## プラクティス

### 一般的に推奨されること

#### ドキュメント

* [6.8](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/general-recommendations.html)
* [7.12](https://www.elastic.co/guide/en/elasticsearch/reference/7.12/general-recommendations.html)


#### 大きな結果セットを返さない

Elasticsearchはデータベースではなく検索エンジンなので、上位ドキュメントを取得するのに向いている。全件取得するものではない。
取得する場合は[Scroll API](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-scroll.html)を使用すること。

#### 大きなドキュメントを避ける

[http.max_content_length](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/modules-http.html)(HTTPリクエストの最大コンテンツ。デフォルトは100mbで Integer.MAX_VALUEより大きい値に設定すると、100mbにリセットされる。)より大きなドキュメントをインデックス出来ない。そしてLuceneに2GBの制限がある。

Elasticsearchは`_id`を取得する必要があるため、`_source`を使わないリクエストでも、大きなドキュメントだとネットワークやメモリ使用量やディスクに大きな負荷がかかる。このドキュメントにインデックスをつけるとドキュメントの乗数のメモリが使われることもある。


### 重要なElasticsearchの構成

#### ドキュメント

[6.8](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/important-settings.html)
[7.12](https://www.elastic.co/guide/en/elasticsearch/reference/7.12/important-settings.html)

#### network.host

デフォルトでは`127.0.0`のループバックに設定されているが、クラスタを構成するにはルークバック以外を設定する必要がある。
また、`network.host`を設定したらElasticsearchは開発モードから本番モードに移行していると想定してシステム起動チェックがWarningからExceptionにアップグレードされる。
開発環境ではWarningで良かったものがExceptionになるため、起動できなくなる。これは構成が正しくない状態で起動しないようにする安全措置である。


例:

```
network.host: 192.168.1.10
```

AWSの場合:

```
network.host: _ec2_
```

GCPの場合:

```
network.host: _gce_
```


#### Discovery設定

##### discovery.zen.minimum_master_nodes

データの損失を防ぐためには、discovery.zen.minimum_master_nodesを設定して最小となるmaster候補ノード(master-eligible nodes)数を知ることが重要である。
この設定が無いと、ネットワーク障害が発生したクラスタは、2つの独立したクラスタ(split brain)に分割されるリスクがあり、データの損失につながる。
詳細は[Avoiding split brain with minimum_master_nodesedit](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/modules-node.html#split-brain)に記載されている。

split brainを回避するにはクォーラムに設定する必要がある。

```
(master_eligible_nodes / 2) + 1
```

マスター候補ノードが3つある場合は、最小マスターノードを（3/2）+1または2に設定する必要がある。

```
discovery.zen.minimum_master_nodes: 2
```

しかし、[デフォルト](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/modules-node.html)ではすべてのノードがmster-eligible,data,ingestのnodeのroleになっているため、3台以上に構成すれば問題はない。
しかし、大規模なクラスタとなると[masterは安定して負荷がないように、専用にするべき](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/modules-node.html#master-node)なので、分けた場合は上記を考慮する必要がある。X

#### ヒープサイズの設定

デフォルトではJVMに最小最大サイズが1GBのヒープを使用するようにしている。そのため、本番環境を構築する際は[jvm.option](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/jvm-options.html)で`Xms`,`Xmx`を正しく設定する必要がある。また、同じ値に設定する。

経験則的には以下。

* 使用できるヒープが多いほどキャッシュに使用できるメモリが多くなるが、多すぎるとGC時の停止時間が長くなる可能性がある
* `Xms`と`Xmx`は物理RAMの50%以下。カーネル、ファイルシステムキャッシュに十分な物理RAMがあるようにする
* `Xms`と`Xmx`をJVMが圧縮オブジェクトポインタ(compressed oops)に使用するカットオフ値以上に設定しない。正確なカットオフは様々だが、32GB近く。  
  以下のようなログがあると制限を下回っていることを確認出来る。  

  ```
heap size [1.9gb], compressed ordinary object pointers [true]
  ```
* ゼロベース圧縮oopsを下回ること。正確なカットオフは様々だが、26GBが安全で、一部のシステムでは30GBになる可能性もある。 JVM options `-XX:+UnlockDiagnosticVMOptions`を指定して起動し、次の行を探すことで制限を下回っていることを確認できる。  

  ```
heap address: 0x000000011be00000, size: 27648 MB, zero based Compressed Oops
  ```
ゼロベース圧縮oopsが有効になっていることを示す。  

```
heap address: 0x0000000118400000, size: 28672 MB, Compressed Oops with base: 0x00000001183ff000
```

#### Tempディレクトリ

Elasticsearchはスタットアップスクリプトが`/tmp`以下を使用する。Linuxのディストリビューションによっては、`/tmp`領域をクリーンアップする。
スタートアップ以外で使用する機能が追加された場合、問題が生じる可能性がある。
`.deb`や`.rpm`を`systemd`以下で実行するとクリーンアップから除外される。
しかし、`.tar.gz`形式の使うのであれば、 `$ES_TMPDIR`環境変数にて書き込み可能な場所を指定して起動する必要がある。

### 重要なシステム設定

#### ドキュメント

[6.8](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/system-config.html)
[7.12](https://www.elastic.co/guide/en/elasticsearch/reference/7.12/system-config.html)

#### OS設定系

`.zip`か`.tar.zip`パッケージを使用する場合、以下のシステム構成が出来る

* 一時的に[`ulimit`](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/setting-system-settings.html#ulimit)を使う、もしくは
* [`etc/security/limits.conf`](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/setting-system-settings.html#limits.conf)

RPM か Debian パッケージを使用した場合、ほとんどの設定は[システム構成ファイル](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/setting-system-settings.html#sysconfig)で設定される。 `systemd`を使用する場合は[systemdの設定ファイル](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/setting-system-settings.html#systemd)で設定する必要がある。

`ulimit`

```
sudo su  
ulimit -n 65535 
su elasticsearch 
```

`/etc/security/limits.conf`

```
elasticsearch  -  nofile  65535
```

`sysconfig file`

* RPM: /etc/sysconfig/elasticsearch
* Debian: /etc/default/elasticsearch

`Systemd configuration`

```
[Service]
LimitMEMLOCK=infinity

保存後に　sudo systemctl daemon-reload しておく
```

#### swappingを無効化する

swappingが発生するとパフォーマンスとノードの安定性が著しく低下するため、発生しないようにする。
GCがミリ秒ではなく、数分間続く可能性があるため、応答が遅延したりクラスタから切断さられる可能性がある。

swappingを無効化する方法は以下の3つ。

1. `sudo swapoff -a`で一時的に無効化する。永続的には`/etc/fstab`の`swap`をコメントアウトする。
1. sysctlの`vm.swappiness`を1にする。
1. elasticsearch.ymlに`bootstrap.memory_lock: true`を追加する

以下で設定を確認できる。`"mlockall" : true`になっておけばいい。

```
curl -X GET "localhost:9200/_nodes?filter_path=**.mlockall&pretty"
```

#### ファイルディスクリプタ

ファイルディスクリプタ不足するとデータが損失する恐れがある。
そのため、65536以上に増やしておく必要がある。

`.zip`や`.tar.gz`パッケージの場合は起動前に`ulimit -n 65535`を実行しておくか、`/etc/security/limits.conf`にてnofileを65536にしておく必要がある。
RPMやDebianのパッケージの場合はデフォルトで65536に設定しているから必要はない。

以下で確認できる。` "max_file_descriptors" : 65535`になっておけばいい。

```
curl -X GET "localhost:9200/_nodes/stats/process?filter_path=**.max_file_descriptors&pretty"
```

#### 仮想メモリ

Elasticsearchはデフォルトで[mmapfs](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/index-modules-store.html#mmapfs)(メモリにマッピングすることでファイルシステム上のインデックスを格納する)を使用してインデックスを保存する。
OSのデフォルトでは低すぎてメモリ不足の例外が発生する可能性がある。そのため、以下でlimitを増やすことが出来る。永続的には `/etc/sysctl.conf`の`vm.max_map_count`を設定する。
なお、RPMおよびDebianパッケージの場合は自動で構成されるため、何もしなくていい。

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

#### スレッド数

Elasticsearchは多数のスレッドプールを作成する。そのため、最低でも4096は無いといけない。 `ulimit -u 4096`で設定できるが、永続的には `/etc/security/limits.conf`の `nproc`を`4096`にする。

systemdで動作させるパッケージディストリビューションの場合は自動で設定するので、追加の必要はない。

### indexing速度の調整

#### ドキュメント

[6.8](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/tune-for-indexing-speed.html)
[7.12](https://www.elastic.co/guide/en/elasticsearch/reference/7.12/tune-for-indexing-speed.html)

#### bulk requestを使用する

大量の単一ドキュメントのインデックスリクエストより、bulk requestsははるかにパフォーマンスがいい。

最適なサイズ見積もりは以下で行う。
1. 単一ノード単一シャードでベンチマークを行う
2. 100個 -> 200個 -> 400個といったように2倍にしていく
3. indexの作成速度が一定になる部分を見つける
同じスコアなら、ドキュメントが多すぎるより、少なすぎるほうが安全なので、少ない方にする。
しかし、いくらパフォーマンスが向上しているといってもリクエストが数十メガバイトオーダーになるとクラスタ全体がメモリ不足になる可能性がある。

#### 複数のworker/threadを使用してElasticsearchにデータを送信する

bulk requestが単一スレッドで最大限に活用できる可能性はほぼ無い。複数スレッドを使うようにする。クラスタリソースを有効に使えてfsyncのコストも削減できる。
`TOO_MANY_REQUESTS (429)`(javaクライアントだと`EsRejectedExecutionException`)が発生しないことを確認する。これはElasticsearchのインデックスが間に合ってないことを示す。
発生時はエクスポネンシャルバックオフでリトライするようにする。

サイズと同様に、ワーカー数も徐々に増やしてディスクI/OやCPUがクラスタで飽和になるまで試すしかない。

#### 更新間隔を長くする

[`index.refresh_interval`](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/index-modules.html#dynamic-index-settings)(更新操作を実行する頻度。これの実施タイミングで最近の変更が検索に表示される)のデフォルトは1秒である。これによりElasticsearchは毎秒新しいセグメントを作成する。例えば30sとかに、この値を長くすると大きなセグメントがフラッシュされ、将来のマージのプレッシャーを削減することが出来る。

#### 初回ロードのrefreshとreplicaを無効にする

一度に大量のデータをロードする必要がある場合、`index.refresh_interval`を`-1`にして、`index.number_of_repicas`を0にする。
データの損失のリスクはあるが、インデックス作成が高速になる。作業完了後にもとの値に戻すようにする。

※筆者の見解だが、これはreindexなどの際に用いるのが有用だと思われる。

#### ファイルシステムキャッシュにメモリを与える

ファイルシステムキャッシュはI/O操作をバッファリングするために使われる。Elasticsearchを実行しているマシンの少なくとも半分をファイルシステムキャッシュに割り当てる必要がある。

#### 自動生成されたIDを使用する

明示的なIDをドキュメントにつけることが出来るが、これは同じIDがシャード内にあるかどうかをチェックする処理が必要になるため負荷が高い。インデックスが大きくなるほど負荷がかかる。
自動生成されたIDだとこの操作がスキップされるため、自動生成を使うべきである。

#### 高速なハードウェアを使うこと

多くのメモリをシステムファイルキャッシュに使ったり、SSDを使うようにする。また、NFSやSMBのようなリモートファイルシステムは使用してはならない。
AWSであるとPIOPSのEBSを使用するようにする。

#### インデックスバッファサイズ

ノードが重いインデックス作成のみを行っている場合、indices.memory.index_buffer_sizeが、重いインデックス作成を行っているシャードごとに最大で512MBのインデックス作成バッファを提供するのに十分な大きさであることを確認する（これを超えると、通常、インデックス作成のパフォーマンスは向上しない）。Elasticsearchはこの設定（Javaヒープに対するパーセンテージまたは絶対的なバイトサイズ）を、すべてのアクティブなシャードの共有バッファとして使用する。非常にアクティブなシャードは、軽量インデックスを実行しているシャードよりも当然このバッファを使用する。

例えば、JVMに10GBのメモリを与えた場合、インデックス・バッファには1GBが割り当てられるが、これはインデックスを多用する2つのシャードをホストするのに十分な量。

インデックスバッファ・・・インデックスバッファは新しくインデックスが付けられたドキュメントを格納するのに使われる。これがいっぱいになるとバッファ内のドキュメントがディスク上のセグメントに書き込まれる。


#### `_field_names`を無効にする

この`_field_names`ではインデックスタイムにオーバーヘッドが発生するため、existsを実行する必要がない場合は無効にする。

### 検索速度を調整する

#### ドキュメント

[6.8](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/tune-for-search-speed.html)
[7.12](https://www.elastic.co/guide/en/elasticsearch/reference/7.12/tune-for-search-speed.html)

ここでも、ファイルシステムキャッシュにメモリを与えるように言及されている

#### ドキュメントモデリング

検索時のコストが下がるようにドキュメントをモデル化する。
結合(join)は避ける。`nested`クエリが数倍遅くなる。親子(`parent-child`)関係だと数百倍遅くなる可能性がある。
非正規化で大幅なスピードアップが期待できる。

#### できるだけ少ないフィールドを検索する

`query_string`や`multi_match`クエリのターゲットが多いほど検索は遅くなる。複数のフィールドの検索速度を向上させるにはインデックス時に値を1つのフィールドにコピーし検索時のこのフィールドを使用する。これは`copy-to`で自動化出来る。

#### 日付は四捨五入する

`now`を使うとキャッシュされない。クエリキャッシュを有効活用できるように四捨五入する。
秒は丸めて分にするなど。丸める範囲を大きくすれば大きくするほどキャッシュ効率はよくなるが、ユーザーエクスペリエンスが低下するトレード・オフとなる。

#### 読み取り専用インデックスをForce-mergeする

特に時系列でインデックスを作るような用途の場合、[`Force Merge`](https://www.elastic.co/guide/en/elasticsearch/reference/6.8/indices-forcemerge.html)するとメリットが得られる。
※必ず読み取り専用となるインデックスでなければならない。非常に大きなセグメントが生成される可能性があり、それがシャードに残る可能性がある。また、マージ中は書き込みがブロックされる。
forcemergeを使用すると保持するセグメント数を減らすことが可能。(パフォーマンスが向上する)

#### レプリカはスループットに役立つ可能性があるが、常にそうとは限らない

レプリカは復元力の向上に加えて、スループットの向上にも役に立つ。
しかし、可用性とスループットにはトレードオフがある。replica数が増えると、ノードあたりのシャード数が増えるため、パフォーマンスが低下する。これはファイルシステムキャッシュがElasticsearchの最大のパフォーマンス要因であるため。

レプリカの適切な数はどうすればいいか。最大ノード障害数`max_failures`に対応したい場合は以下となる。
`max(max_failures, ceil(num_nodes / num_primaries) - 1)`

### shardのサイズ

#### ドキュメント

6.8についてのドキュメントはなし
[7.2](https://www.elastic.co/guide/en/elasticsearch/reference/master/size-your-shards.html)

#### sharding戦略について

前提として、万能なシャーディング戦略はない。最良の方法は本番で動いているようなクエリで負荷試験をしてパフォーマンスのベンチマークを行うしか無い。
その中でもオーバーシャーディングというものがある。オーバーシャーディングはシャードの数が多くてクラスターが不安定になる現象のことである。
オーバーシャーディングについて、どう対応すればいいか。

##### より長い期間をカバーする time-baseのインデックスを作成する

時系列データでは、より長い期間をカバーするインデックスを作成することができる。例えば、日足ではなく、月足や年足のインデックスを作成することができる。
ILM(インデックスライフサイクル管理)を使用している場合は、ロールオーバーアクションの`max_age`閾値を増やすことで実現出来る。
リテンションポリシーで許可されている場合は、max_age のしきい値を省略し、代わりに `max_docs` や `max_size` のしきい値を使用することで、より大きなインデックスを作成することもできる。

##### 空または不要なインデックスは削除する

ILMを使用している場合、`max_age`のしきい値に基づいてインデックスをロールオーバーすると、ドキュメントのないインデックスを誤って作成してしまうことがある。このような空のインデックスは何のメリットもないが、リソースを消費する。

##### オフピーク時にマージする。

`force merge`を使用して、小さなセグメントを大きなセグメントにマージする。こうすることによってシャードのオーバーヘッドを減らして、検索速度を向上させられる。
ただ、force mergeはリソースを大量に消費するため、オフピーク時に実行すること。

##### 既存のインデックスをより少ないshardに減らす

書き込みの必要が無くなったら `shrink API`を使用してshard数を減らすことが出来る。

##### 小さなインデックスを組み合わせる

reindex APIを使用して類似したmappingを持つインデックスを1つの大きなインデックスに結合することが出来る。
例えば `my-index-2099.10.11` というインデックスがあった場合、 `my-index-2099.10` にreindexして小さなインデックスを削除する

#### ベストプラクティス

##### ドキュメントではなインデックスを削除する

削除されたドキュメントはElasticsearchのファイルシステムからすぐに削除されない。関連する各シャードでドキュメントを削除済みとしてマークする。マークされたドキュメントは定期的な [セグメントマージ](https://www.elastic.co/guide/en/elasticsearch/reference/master/index-modules-merge.html)で削除されるまでリソースを使用し続ける。
削除する場合は、インデックス自体を削除することによってリソースを開放できる。

##### 時系列データにはデータストリームとILM(インデックスライフルサイクル管理)を使用する

データストリームを使用すると、時系列データを複数のtime-baseバッキングインデックスにストアできる。ILMを使用すると、このバッキングインデックスを自動的に管理出来る。

この設定のメリットは、自動ロールオーバーがあって `max_age` `max_docs` `max_size`の敷地に従って新しいインデックスが作成される。これらのしきい値を使用して保持するインデックスを作成出来る。
インデックスが不要になった場合は、ILMを使用してインデックスを自動的に削除してリソースを開放できる。

* 新しいインデックスのシャード数をへらしたい？
  * matching index template の index.number_of_shards を変更する
* 大きなシャードを作りたい？
  * ILMポリシーの rollover threshold を増やす
* 短いインターバルのインデックスが必要？
  * 古いインデックスを早く削除することによってシャード数の増加を相殺することが出来る。そのためにポリシー削除フェーズの `min_age`しきい値を下げる必要がある。


##### 10GB 〜 65GB のシャードサイズを目安にする

65GB以上のシャードはクラスタの障害回復能力を低下する可能性がある。ノードに障害が発生するとそのノードのシャードを他のノードにリバランシングする。
大きなシャードはネットワーク上での移動が難しく、ノードのリソースを圧迫する可能性がある。

ILMを使用している場合は、ロールオーバーアクションの `max_primary_shard_size`の敷地を65GBに設定することで大きなシャードにならないようにすることができる。

##### ヒープメモリ1GBあたり20個以下のシャードを目指す

ノードが保持できるシャードの数はノードのヒープメモリに比例する。例えば、30GBのヒープメモリを持つノードでは最大で600個のシャードを持つことが出来る。1GBあたり20シャードを超える場合は別のノードを追加する必要がある。


##### ノードのホットスポットを避ける

特定のノードにあまりにも多くのシャードが割り当てられると、そのノードがホットスポットになることがある。例えば、大量にインデキシングするインデックスに対して1つのノードにシャードが多すぎる場合、ノードに問題が発生する可能性がある。

ホットスポットを防ぐには [update index settings API](https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-update-settings.html)を使用して [index.routing.allocation.total_shards_per_node](https://www.elastic.co/guide/en/elasticsearch/reference/master/allocation-total-shards.html#total-shards-per-node)のインデックス設定を使用して1つのノード上のシャード数を明示的に制限する。