brunch

You can make anything
by writing

C.S.Lewis

by 강진우 Mar 08. 2017

ElasticSearch와 Heap 메모리

Linux Opensource

이번 글은 ElasticSearch (이하 ES)Heap 메모리에 대해서 이야기해 보려고 합니다. 아시겠지만 ESJava 기반이기 때문에 여타 다른 Java 애플리케이션들과 마찬가지로 Heap 메모리를 얼마나 세팅하느냐에 따라 성능에 큰 영향을 미치게 됩니다. 그래서 이번 글에서는 주의해야 할 사항 두 가지를 바탕으로 Heap 메모리에 대해 이야기해 보겠습니다. 더 자세한 정보는 https://www.elastic.co/blog/a-heap-of-trouble#fn5와 https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html 여기를 참고해 주시면 됩니다.


Don't Cross 32GB!


ES에 대해 공부하고 설정해 보신 분들은 아마 다 아실 겁니다. Heap 영역을 32GB 이상 설정하지 말라고 권고하고 있습니다. 왜 그럴까요? 

Heap 메모리는 JVM이 자신에게 필요한 데이터들을 메모리 공간에 올려놓기 위해 필요합니다. 이 곳에 연산을 위해 필요한 다양한 데이터들을 올려놓고 사용하게 됩니다. 이때 이 데이터들을 Object라고 부르며 이 Object에 접근하기 위한 메모리 상의 주소를 Ordinary Object Pointer (이하 OOP)라는 구조체에 저장합니다. 각각의 OOP는 시스템의 아키텍처에 따라 32bit 혹은 64bit 기반의 크기를 가지게 되는데, 32bit라면 2의 32 제곱까지 표현할 수 있기 때문에 최대 4GB까지의 주소 공간을 가리킬 수 있게 됩니다. 반면 64bit는 2의 64 제곱까지 표현할 수 있기 때문에 이론상 18EB까지의 주소 공간을 가리킬 수 있습니다. 하지만 64bit의 경우 32bit 보다 포인터 자체의 크기가 크기 때문에 성능이 떨어질 수밖에 없습니다. 더 많은 연산을 필요로 하고 더 많은 메모리 공간을 필요로 하기 때문입니다. 그래서 JVM64bit 시스템이라도 Heap 영역이 4GB보다 작다면 32bit 기반의 OOP를 사용합니다. 문제는 Heap 영역이 4GB보다 클 경우에 발생합니다. 32bit 기반의 OOP로는 4GB를 넘어가는 영역에 있는 Object들을 가리킬 수 없기 때문이죠. 그렇다고 64bit 기반의 OOP를 사용하자니 급작스럽게 성능 저하가 발생할 수 있게 됩니다. 그래서 JVM은 하나의 꼼수를 만들어 냅니다. 바로 Compressed OOP입니다. 32bit 기반으로 사용하되 4GB 이상의 영역을 가리킬 수 있도록 하는 겁니다. 그게 어떻게 가능할까요?


Compressed OOP

Native OOP와 Compressed OOP

위 그림을 살펴보면 Native OOP의 경우는 OOP가 바로 Object의 주소를 가리킵니다. 값이 1이면 1번 주소를 가리키고, 2이면 2번 주소를 가리킵니다. 하지만 Compressed OOP의 경우는 OOP주소가 아닌 주소의 오프셋을 가리킵니다. 그리고 그 오프셋은 8의 n배수로 계산되어, 값이 1이면 8번 주소를, 2이면 16번 주소를 가리키게 됩니다. 이렇게 되면 n개의 OOP로 기존보다 8배 더 많은 주소 공간을 표시할 수 있게 됩니다. 

그래서 표현할 수 있는 주소의 영역이 기존 4GB에서 32GB로 증가하게 됩니다.

8의 배수 단위로 Object를 저장하기 때문에 Compressed OOP의 경우 실제 메모리 주소를 참조하기 위해서 약간의 연산이 필요합니다. 바로 아래와 같은 left shift 연산입니다.

native oop = (compressed oop << 3)

아시겠지만 shift 연산은 CPU의 연산 중에서도 꽤 빠른 편에 속하는 연산입니다. 그렇기 때문에 주소 변환에 사용되는 CPU 사용이 크지 않아 크게 성능 저하가 일어나지는 않습니다. 다만 일부 메모리 영역이 버려질 수 있있지만, 그만큼 더 큰 Object를 채워 넣을 수 있기 때문에 실제 손실은 그리 크지 않습니다. 그리고 Heap 영역이 32GB를 넘게 되면 그때부터 JVM은 64bit 기반의 OOP를 사용하게 됩니다. 그리고 64bit 기반의 OOP를 사용하게 되면 성능이 급격하게 저하됩니다. 이런 이유로 ES의 Heap 영역을 32GB 이상으로 잡지 않도록 권고하는 것입니다.

그렇다면 정확하게 Compressed OOP가 사용되는 임계치를 알 수 있을까요? 시스템마다 다르지만 간단한 테스트를 통해서 확인해 볼 수 있습니다. Java를 아래와 같이 실행해 보면 언제부터 64bit OOP가 사용되는지 확인할 수 있습니다.

[root@es-data01 ~]# java -Xmx32766m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
     bool UseCompressedOops                        := true

[root@es-data01 ~]# java -Xmx32767m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
     bool UseCompressedOops                         = false

위 시스템은 32766MB 까지가 Compressed OOPS를 사용할 수 있는 임계치입니다. 그렇기 때문에 ES의 Heap 영역도 32766MB 이하로 설정해 주어야 합니다.


Zero Based Heap Memory


두 번째 이슈는 Zero Based Heap Memory입니다. JVM은 실행될 때 OS로부터 Heap 영역을 할당받게 되는데 이때 64bit 시스템에서 Compressed OOP를 사용해야 하는 상황이 되면 운영체제에게 Heap 영역의 시작 주소를 0에서부터 시작할 수 있도록 요청하게 됩니다. Compressed OOP의 경우 앞에서 이야기한 것처럼 shift 연산이 필요하기 때문에 주소 공간이 0에서부터 시작한다면 shift 외의 연산이 필요 없어집니다. 하지만 그렇지 않다면 shift 연산 외에 base 주소를 기반으로 한 더하기 연산까지 필요하기 때문에 조금 더 주소 변환에 시간이 걸립니다. 많은 영역의 메모리를 사용하는 경우에는 이 연산이 성능에 영향을 줄 수 있습니다. 

zero base로 할당받을 수 있는 임계치가 어디까지 인지는 JAVA_OPTS에 아래 항목을 넣어서 ES를 실행시키면 알 수 있습니다.

-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode

그럼 아래와 같이 Zero Based 인지 아닌지를 알 수 있습니다.

heap address: 0x0000000080000000, size: 30720 MB, Compressed Oops mode: Zero based, Oop shift amount: 3

적당한 Heap 메모리


그렇다면 적당한 Heap 메모리는 어느 정도를 의미하는 걸까요? 너무 작은 Heap 메모리 영역은 Out-Of-Memory를 일으킬 수 있으며 너무 큰 Heap 메모리 영역은 GC가 발생할 때 긴 pause time을 만들어 내서 응답 속도를 떨어지게 할 수 있습니다. 워크로드마다 다르겠지만 나름의 최소한의 기준은 아래와 같을 겁니다.


1. 시스템 전체 메모리의 절반만 사용한다.

2. Compressed OOP를 사용할 수 있도록 32GB 이하로 사용한다. 이때 테스트를 통해서 Compressed OOP를 사용할 수 있는 임계치를 확인하는 것이 좋다.

3. Zero Base Compressed OOP를 사용할 수 있는 임계치 값을 확인한다. 


정리하자면, 시스템 전체 메모리의 절반 이하를 유지하며 Zero Based Compressed OOP를 사용할 수 있는 임계치보다는 낮은 값이 되어야 할 겁니다.


하지만 위 기준은 Heap 사이즈를 결정하기 위한 최소한의 기준일 뿐, 어떤 워크로드를 발생시키느냐에 따라 조금씩 다르게 설정될 겁니다. 이 부분은 좀 더 공부해서 다음 글에서 다뤄볼까 합니다.


이 글이 저와 같은 고민을 하는 분들에게 도움이 되었으면 좋겠습니다.


혹시라도 잘못된 내용이 있으면 언제든 말씀해 주세요. 감사합니다.


* 참고 사이트 :

https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html

https://www.elastic.co/blog/a-heap-of-trouble

http://dev-punxism.tistory.com/entry/JVM-OOPSOrdinary-object-pointer

매거진의 이전글 시스템 엔지니어의 ES 삽질기 #1
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari