動かざることバグの如し

近づきたいよ 君の理想に

何故Elasticsearchに32GB以上メモリ割り振るのはNGなのか

その理由を探るべく、我々はアマゾンの奥地へと向かった。

環境

  • 少なくともElasticsearch 2以上はこの記事該当

概要

古事記にも書かれていたんじゃないかってレベルで、「Elasticsearchには32GB以上のメモリを割り当てるべきではない」とよく言われる。ESのオプション設定記事とか見てるとよく書かれている。

が、なぜ32GBなのか、Elasticsearchのヒープサイズに32GB以上を設定するとどうなってしまうのか。

そもそも本当なのか

2020年11月15日現在の最新は7系だが、公式ドキュメントを確認してみる。

Important Elasticsearch configuration | Elasticsearch Reference [7.x] | Elastic

Set Xmx and Xms to no more than the threshold that the JVM uses for compressed object pointers (compressed oops).
The exact threshold varies but is near 32 GB.
You can verify that you are under the threshold by looking for a line in the logs like the following:

確かに32GBより下にしろ的なことが書かれている

Javaのメモリについて

そもそもElasticsearchはJavaアプリケーションであり、JVM上で動作する。 よって、メモリの設定も jvm.options とかで設定できる

いったんElasticsearchから離れて、ピュアなjavaで検証する。

すごい人がめっちゃいいサンプルコードを用意してくれたのでありがたく使う。なんと内容はLinkedListをひたすら増やし続けて割り当てられたメモリすべてを使う潰すコード。

// https://gist.github.com/CodingFabian/8708393
public class Memory {

    // Dummy Entity representing usual data objects
    private static class Entity {
    public String name;
    public String detail;
    public Double amount;
    public Integer age;
    }

    // Linked list offers table inserts and helps illustrating the issue by using multiple
    // references per entry
    public static java.util.LinkedList<Entity> entities = new java.util.LinkedList<>();

    // This threshold should be 2 times Xmn. It ensures the loop stops before a full GC happens.
    private static final int MB = 1024 * 1024;
    private static final int THRESHOLD = 100 * MB;

    public static void main(String[] args) {
    System.out.println("Total Memory (in bytes): " + Runtime.getRuntime().totalMemory());
    System.out.println("Free Memory (in bytes): " + Runtime.getRuntime().freeMemory());
    System.out.println("Max Memory (in bytes): " + Runtime.getRuntime().maxMemory());

    while (true) {
        appendEntitiesToDataStructure();
        terminateBeforeFullGCorOOMEcanHappen();
    }
    }

    private static void appendEntitiesToDataStructure() {
    entities.add(new Entity());
    }

    private static void terminateBeforeFullGCorOOMEcanHappen() {
    if (Runtime.getRuntime().freeMemory() < THRESHOLD) {
        System.out.println("Elements created and added to LinkedList: " + entities.size());
        System.exit(0);
    }
    }

}

これをヒープサイズを31GBに指定して実行してみる。

root@memory01:~# java -Xms31g -Xmx31g -Xmn50m Memory.java
Total Memory (in bytes): 33285996544
Free Memory (in bytes): 33251882600
Max Memory (in bytes): 33285996544
Elements created and added to LinkedList: 538353486

これを見ると 538,353,486個のLinkedListを作成できた、と言える。

ではもう1GB増やして、32GBで再度実行してみる。

root@memory01:~# java -Xms32g -Xmx32g -Xmn50m Memory.java
Total Memory (in bytes): 34359738368
Free Memory (in bytes): 34323161688
Max Memory (in bytes): 34359738368
Elements created and added to LinkedList: 353838793

結果353,838,793個。明らかにおかしい。Memory欄を見るとさっきより使用できるメモリ量は増えているのに、作成できたリストの数はさっきより全然少ない。本来なら1GB分増えているはず。。。

これはJavaの仕様で、Javaには 圧縮オブジェクトポインター機能がある。これはJavaオブジェクトを圧縮することで実メモリ使用量を削減できるありがたい機能なのだが、ヒープサイズが32GB未満が条件となる。

だからさっきの例ではヒープサイズ32GBの割当てより、31GBのほうがより多くのLinkedListを作成できたというわけ

当然この制約はElasticsearchも例外ではなく、メモリ設定が8GBとか32GB未満なら圧縮機能が有効化されていい感じになるが、32GBにしてしまうと一気にメモリ効率が悪くなってしまう。だから32GBを指定するぐらいなら

-Xms32766m
-Xmx32766m

で32GB未満(32*1024= 32768MBより少ない値)を設定しろってこと。

では64GBとかは

やろうと思えばできる。効率のことは一旦忘れ、31GBの 538353486 を超える以上のヒープサイズを割り当ててしまえば結果的にはESがより多くのメモリを使えることになる。

例えば以下は64GBで実行した場合 702338196 > 538353486なので、効率は悪いが64GBのほうがより多くのLinkedListを生成できている。

root@memory01:~# java -Xms64g -Xmx64g -Xmn50m Memory.java
Total Memory (in bytes): 68719476736
Free Memory (in bytes): 68708200928
Max Memory (in bytes): 68719476736
Elements created and added to LinkedList: 702338196

蛇足

今回は実際にサンプルコードで試したので実感できたが、冒頭の公式ドキュメントをもう一回読み直してみる

Set Xmx and Xms to no more than the threshold that the JVM uses for compressed object pointers (compressed oops).
The exact threshold varies but is near 32 GB.
You can verify that you are under the threshold by looking for a line in the logs like the following:

公式ドキュメントはちゃんと読もうね(ニッコリ

参考リンク