ElasticSearch Heap 메모리 설정

# 권장 메모리 할당량

ES의 힙 사이즈는 전체 서버가 가진 메모리의 반을 설정하는 것을 권장하고, 64GB 이상의 메모리를 가지고 있는 서버라고 할지라도 30.5GB 이상을 ES에 할당하는것은 권장하지 않습니다.

예를들어 32GB의 메모리를 가진 서버라면 ES에 16GB 할당. 64GB의 메모리를 가진 서버라면 ES에 30.5GB 할당.
128GB의 메모리를 가진 서버라고 할지라도 ES에 30.5 GB 할당.
왜 30.5GB로 한정 짓는지에 대해서는 밑에서 좀 더 자세히 다루도록 하겠습니다.

# Memory swapping 기능은 비활성화

ES는 메모리에 많은양의 데이터를 올려놓고 searching, indexing 등의 작업을 하기 때문에 swap을 켜 놓으면 disk로 내려서 작업이 되어 버리는 순간 성능에 치명적인 악영향을 끼칠수 있습니다.
엘라스틱 에서는 swap을 사용하지 않게 하기 위해서 세가지 옵션을 제시 합니다.

  • swap off
    1
    sudo swapoff -a

위 설정은 reboot 하고 나면 원상복구 되는 설정 입니다.
그러므로 fstab에서도 swap 을 comment out 하는것이 좋습니다.

1
2
sudo vi /etc/fstab
`

swap 이란 단어를 포함하고 있는 라인 #으로 주석처리

  • sysctl 의 vm.swappiness=1 로 설정
    1
    sysctl -a |grep swappiness

을 하여 vm.swappiness 설정이 되어 있는지 확인하고, grep되는 결과가 없거나 1이 아닌 값으로 설정 되어 있다면

1
sysctl -w vm.swappiness=1

or

1
2
sudo vi /etc/sysctl.conf
vm.swappiness=1

추가 후

1
sysctl -p

참고로 vm.swapiness 설정은 swap 사용의 적극성 혹은 활용 수준을 정하는 커널 속성 입니다.
0으로 설정하면 사용 안함 1은 최소로 사용 ~ 100은 적극적으로 스왑 활용 인데 0으로 설정하지 않고 1로 설정하는 이유 는 0으로 설정할 시 OOM Killer가 스왑을 사용하려고 시도하는 어플리케이션!!!! 을 외치며 ES를 바로 kill 시켜 버릴수도 있기 때문에 0으로는 설정하지 않습니다.


인터넷에서 찾을 수 있는 포스트 내용 중 SSD일 경우 vm.swapiness를 10으로 줘도 문제가 없다고 하는 사람들도 있는데 저는 서버에 SSD를 사용해서 10으로 설정했다가 OS가 swap을 사용해 버리는 바람에 성능에 영향을 받았던 경험이 있습니다. 그래서 다시 1로 변경.

  • memory lock

    위 두 설정은 root나 sudoer 계정을 가지고 있을때만 가능한 설정 인데
    OS를 관리하는 쪽에서 절대로 위 설정들을 허용해 주지 않는다면 마지막으로 jvm자체에 설정을 주는 수 밖에 없습니다.
    elasticsearch.yml 파일에 다음과 같이 을 true를 주면 (버전에 따라 프로퍼티 네임이 다르니 주의!)
    이 옵션은 elasticsearch가 기동될때 자기한테 할당된 메모리를 lock 한 채로 뜨게 되며
    swap으로 메모리가 빠져나가는것을 방지 해 주는 옵션입니다.
    1
    2
    3
    4
    #ES 2.X
    bootstrap.mlockall: true
    #ES 5.X
    bootstrap.memory_lock: true

하지만 memory lock 설정과 vm.swappiness: 1로 설정은 elasticsearch 가 스왑을 최대한 안 쓰게 설정한 것이지만
스왑 자체가 OS에서 사용하는 자원이기 때문에 OS가 필요하다고 판단하면 쓸 수도 있습니다.
개인적으로는 vm.swappiness :1 설정 및 mlockall :true 둘 다 설정 했는데 그 이후로 거의 1년간 운영해 오면서 OS가 swap을 쓴적은 없습니다.
확인 할 수 있는 방법은 간단하게 free -m 등으로 확인해서 swap 영역에 할당된 영역을 확인해 보시면 됩니다.

# Zero-Based Compressed Oops (Ordinary Object Pointers)

# 권장 메모리 할당량 에서 30.5GB 이상으로 설정하지 말라고 했던 것의 연장선 입니다.
Oops 에 대해서 대략적으로 말씀드리면 오브젝트들이 어느 메모리에 locate 되어 있는지를 관리하는 JVM의 포인터 라고 생각 하면 쉽습니다.
이 Managed 포인터의 크기는 기본적으로 OS의 bit를 따라 가며 32bit OS에서는 2의 32승(=4GB)의 크기를 가지며 64bit OS에서는 2의 64승(=2EB 엑사바이트)의 크기를 가진다고 볼 수 있겠습니다.
그런데 생각해보면 2의 64승의 주소를 찾아가는 연산을 하려면 오브젝트의 메모리 주소를 찾는데만해도 많은 시간이 소요될 것이고 거의 쓸모없는 프로그램이 될것이기 때문에 JAVA는 64bit의 OS라 할지라도 4GB 이하의 힙을 가지고 JVM을 띄우면 32bit의 oops를 그대로 따라가도록 설계 되어 있습니다.
그리고 4GB가 넘어가는 영역에 대해서는 compressed oops라는 기법을 사용할 수 있는데 포인터가 실제 오브젝트가 들어있는 메모리 주소값을 가르키는것이 아니고 8byte단위로 나눠놓은 offset을 가르키게 됩니다.
이렇게 되면 2의 32승 bit (=4GB)을 표현할 수 있는 32bit oop를 이용하여 실제로는 8배 많은 32GB의 주소값까지 표현할 수 있게 되는것 입니다.
Compressed oops를 설계 한 사람들이 아무 생각없이 8byte (= 2의 3승 byte) 단위로 끊어 놓지는 않았을 테고.. 어떤이유가 있을까요?
아마도 적당히 큰 메모리 공간을 활용할 수 있으면서 성능도 놓치고 싶지 않는 범위내에서 끊어 놓았을텐데….
굉장히 복잡한 여러가지 이유가 있겠지만 기본적으로 java가 사용하는 메모리 스키마는 8byte의 구조를 가지고 있다고 합니다. 그렇기 때문에 8byte단위로 건너뛰면서 mapping을 해 놓으면 좌로 3번 시프트 ( << 3 ) 해 주는 연산만으로 원래 가르키고자 했던 주소값을 정확히 가르킬수 있게 되는것 입니다.

그런데 이렇게 되기 위해서는 한가지 필요충분조건이 필요 합니다.
바로 메모리주소의 시작점이 0부터 시작해야 한다는 조건인데요, 이 조건이 충족되지 않으면 Non-Zero based Compressed oops로 동작을 하게 되고 좌로 3번 시프트 << 3 해준 이후에 시작점만큼 + 해주는 연산이 추가로 들어가야 되서 메모리의 크기가 큰 경우 성능에 영향을 끼치게 됩니다.

자세히 보려면 너무 어렵고 한번에 다루기도 힘든 내용이니 관심 있으신 분들은 이 두 포스트를 읽어보시길 추천 합니다.
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html
https://wiki.openjdk.java.net/display/HotSpot/CompressedOops

# 결론 : 그럼 나는 얼마까지 ES에 메모리를 할당하면 좋을까요?

  1. 서버의 메모리가 63GB 미만이라면 => 내가 가진 메모리의 반.
  2. 서버의 메모리가 63GB 이상이라면 => 내 서버에서 Zero based 모드로 Compressed oops를 사용할 수있는 임계치

※ 임계치를 알아보는 방법

서버에서 커맨드를 그냥 날려보시면 됩니다!

내 메모리가 64GB라서 32GB부터 확인해보는 시나리오로 확인해 보는법을 보여드리겠습니다.

  • 32GB
    1
    2
    3
    java -Xmx32G -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode 2>/dev/null |grep Compressed |grep Oops
    bool PrintCompressedOopsMode := true {diagnostic}
    bool UseCompressedOops = false {lp64_product}

bool UseCompressedOops = false 임을 확인 할수 있습니다.

  • 31GB
    1
    2
    3
    4
    java -Xmx31G -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode 2>/dev/null |grep Compressed |grep Oops
    heap address: 0x00007f9348800000, size: 31744 MB, Compressed Oops mode: Non-zero based:0x00007f93487ff000, Oop shift amount: 3
    bool PrintCompressedOopsMode := true {diagnostic}
    bool UseCompressedOops := true {lp64_product}

31GB는 UseCompressedOops = true 입니다. 하지만! Compressed Oops mode 가 Non-zero based 인 것을 확인 할 수 있습니다. 우리가 원하는 결과가 아닙니다.

  • 30GB
    1
    2
    3
    4
    java -Xmx30G -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode 2>/dev/null |grep Compressed |grep Oops
    heap address: 0x0000000080000000, size: 30720 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
    bool PrintCompressedOopsMode := true {diagnostic}
    bool UseCompressedOops := true {lp64_product}

이번에는 CompressedOops 도 사용하고 Zero based 모드로 잘 되어 있네요.
30GB는 사용해도 좋습니다.

추가 Tip
30GB로 운영하는데 CPU도 별로 많이 안쓰는것 같고 메모리도 여유있는것으로 확인되면 FullGC 시간을 줄이기 위해 힙을 24GB정도로 내려서 사용하는것도 고려해 볼 만 합니다. 운영환경에서 최적의 힙 메모리를 적용하기 위해서는 일정기간 트렌딩을 살펴보고 결정하는게 좋겠습니다.

# references :

https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html
https://www.elastic.co/guide/en/elasticsearch/reference/5.5/setup-configuration-memory.html
https://www.elastic.co/blog/a-heap-of-trouble
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html
https://wiki.openjdk.java.net/display/HotSpot/CompressedOops

Share