Elasticsearchのサイジングの考え方

## はじめに
Elasticsearchでクラスタ設計する際にData NodeにどのくらいMemoryとSSDを搭載するのが正しいのか
調べてみましたので、備忘録として本記事を書いてみました。

**【参考】**
・[Elastic Stack実践ガイド[Logstash/Beats編]][08]

## 利用バージョン
Elasticsearch 6.6.1

## 概要図
こんなイメージになるのかなと思っています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211371/7b0570ac-965a-1200-10c2-23f997b3ccfd.png)

## 各コンポーネントの説明
### Memoryについて
- Memoryは、**OS File Cache**と**JVM Heap**で構成されます。
- **JVM Heap**は、**Node Query Cache**、**Shard Request Cache**、**Indexing Buffer**、**Field Data Cache**で構成されます。
- **OS File Cache**は、クエリ結果の対象となったStorage上にある**Segment**をキャッシュする領域になります。
- **Node Query Cache**は、Node内の全Shardで共有されるクエリ結果をキャッシュする領域になります。
- **Shard Request Cache**は、Node内の各Shardのローカル検索結果をキャッシュする領域になります。
- **Indexing Buffer**は、IndexingされるDocumentを保持するバッファ領域で一杯になるとStorage上の**Segment**に書き込まれます。
- **Field Data Cache**は、Fieldデータの集計をソートしたり計算する時に使用するキャッシュ領域になります。

**【参考】**
・[Node Query Cache][02]
・[Indexing Buffer][03]
・[Shard request cache][04]
・[Field data cache][05]

### Storageについて
- **Index**は、RDBで言うところのTableです。スキーマを定義(Index Setting)してDocumentを格納していきます。
- **Shard**は、Index内のDocumentを複数Nodeに分散させてスケールアウトさせるための論理的な単位です。
- **Segment**は、Index内のDocumentを保持する物理的な単位です。

## サイジングの考え方
- **JVM Heap**は、Memoryの`半分(50%)`もしくは`32GB`未満にします。※32GB以上割り当てると性能が劣化します。
- **JVM Heap**の1GBに対して、Storage内のShardを`20個`以下にします。※31GB×20個=620個なので、`600個/ノード`に抑えます。
- **Shard**の大きさは格納するDocument次第ですが、`20～40GB`に収めます。
- **Shard**で悩ましいのは、Primary Shard数を2個以上に増やすとAggregationでの集計結果が正しく無くなる可能性があるということです。
- MemoryサイズはIndexを格納するStorage容量の`3%`以上を確保します。※Storageが2TBの場合、2,048GB×0.03=`61.44GB`になります。
- Data NodeのStorageはIO重視なので、推奨はDAS(Direct Attached Storage)で`NVMe形式のSSD`になります。※AWS EC2だとi3インスタンスです。

以下はElastic CloudのHighIOのData Nodeのデフォルト値です。Storage240GBに対して、Memory8GBなので、3%ルールに乗っ取っています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211371/a7d491b3-e3be-a819-5d68-126a4ed52a5a.png)

また1台のData Nodeの最大Memory容量は58GB(Storageが1.7TB)となっていて、さらにスケールさせる場合はNode数を増やすしかないようです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/211371/ca94501a-0d71-60d9-8132-d1ea182765b4.png)

- 上記よりMemoryの推奨サイズは`2TB`のStorageに対して`64GB`、その半分以下の`31GB`を**JVM Heap**に割り当てるのが良さそうです。

**【参考】**
・[Why 35GB Heap is Less Than 32GB – Java JVM Memory Oddities][06]
・[How many shards should I have in my Elasticsearch cluster?][01]

### 2020/06/10追記
Elasticsearch 7.7からJVMヒープの使われ方が変わったようです。
より効率的にヒープ領域が利用でき、多くのデータを抱えられるようになりました。

**【参考】**
・[Elasticsearchヒープのメモリ使用量を大幅に削減する][07]

## まとめ
色々と考えてみたんですが、**JVM Heap**は最大31GBですが、**OS File Cache**を32GB以上にして
**Segment**のキャッシュを増やすことでStorageも大きく出来ると考えていたんですが、やめた方が良いのでしょうか。

書き込みが終了したIndexに対して、Force mergeを定期的に実行することで**Segment**数が減り
**JVM Heap**のMemory節約になって、より多くのMemoryとStorageが詰めるのではないかと思った次第でした。

また、1個の**Shard**を20GBで計算すると、1台のData Nodeは最大で600個×20GBで12TBのSSDを積んでも良いことになります。
ですが、恐らくそれだとMemoryの**OS File Cache**を増やさないとMemory溢れを起こすと思いますが
64GBを超えて大量にMemoryを積んでもちゃんと性能を落とさずにキャッシュでSearch出来るのか？、、、というのが分かりませんでした。
特に**Field Data Cache**が足りなくなるのではないかという気がしているのですが、認識は合っているのか。

NodeあたりのCPU性能やIO分散を考えるとNodeをクラスタに追加してクラスタを大きくするのが良いと思いますが
Nodeが増えると購入するElasticサブスクリプションが増えるので、なかなか悩ましい問題ですね。

[01]: https://www.elastic.co/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster "How many shards should I have in my Elasticsearch cluster?"
[02]: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-cache.html "Node Query Cache"
[03]: https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html "Indexing Buffer"
[04]: https://www.elastic.co/guide/en/elasticsearch/reference/current/shard-request-cache.html "Shard request cache"
[05]: https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-fielddata.html "Field data cache"
[06]: https://blog.codecentric.de/en/2014/02/35gb-heap-less-32gb-java-jvm-memory-oddities/ "Why 35GB Heap is Less Than 32GB – Java JVM Memory Oddities"
[07]:https://www.elastic.co/jp/blog/significantly-decrease-your-elasticsearch-heap-memory-usage "Elasticsearchヒープのメモリ使用量を大幅に削減する"
[08]:https://www.amazon.co.jp/dp/4295009989/ "Elastic Stack実践ガイド[Logstash/Beats編]"