elastic search heap size 결정 / 힙 사이즈 / 어느정도 크기로 / 메모리 확장 /
elasticsearch 에서 JVM heap size 설정
- 총 메모리의 1/2 까지만
- 그래도 최대치는 26GB ~ 30GB 이하면 된다.
Set the JVM heap size
기본적으로 Elasticsearch는 노드의 role(master, data 등) 및 총 메모리를 기반으로 JVM heap 크기를 자동으로 설정한다. 대부분의 프로덕션 환경에서는 기본 크기를 사용하는 것을 권장한다.
기본 heap 크기를 재정의하려면 최소 및 최대 힙 크기 설정(Xms 및 Xmx)을 설정하면 된다. 최소값(Xms)과 최대값(Xmx)은 같아야 한다.
heap 크기는 사용 가능한 RAM을 기준으로 해야 한다.
Xms 및 Xmx를 전체 메모리의 50% 이하로 설정한다. Elasticsearch에는 JVM heap 이외의 목적으로 메모리가 필요하다. 예를 들어 Elasticsearch는 효율적인 네트워크 통신을 위해 off-heap buffers를 사용하고 파일에 대한 효율적인 액세스를 위해 운영 체제의 파일 시스템 캐시에 의존한다. JVM 자체도 약간의 메모리가 필요하다. Elasticsearch는 Xmx 설정으로 구성된 제한보다 많은 메모리를 사용하는 것이 일반적이다.
- off-heap buffers
- 파일 시스템 캐시
- JVM 자체가 사용하는 memory
NOTE: Docker와 같은 컨테이너에서 실행되는 경우 총 메모리는 호스트의 “총 시스템 메모리”가 아니라 “컨테이너에 표시되는 메모리 양”으로 정의된다.
Xms 및 Xmx를 압축된 일반 개체 포인터(ordinary object pointers, oops)의 임계값(threshold) 이하로 설정한다. 정확한 임계값은 다양하지만 26GB는 대부분의 시스템에서 안전하며 일부 시스템에서는 30GB까지 정도 될 수 있다. 임계값 미만인지 확인하려면 Elastic search 로그에서 다음과 같은 항목을 확인하자.
heap size [1.9gb], compressed ordinary object pointers [true]
또는 노드 정보 API를 사용하여 노드에 대한 jvm.using_compressed_ordinary_object_pointers 값을 확인하자.
curl -X GET "localhost:9200/_nodes/_all/jvm?pretty"
Elasticsearch에 사용할 수 있는 heap이 많을수록 내부 캐시(internal cache)에 더 많은 메모리를 사용할 수 있다. 이렇게 하면 운영 체제가 파일 시스템 캐시(filesystem cache)에 사용할 메모리가 줄어듭니다. heap이 클수록 garbabe collection pause 들이 길어질 수도 있다.
heap size를 설정하려면 확장자가 .options인 사용자 지정 JVM 옵션 파일에
Xms
및Xmx
JVM 인수를 추가하고 jvm.options.d/ 디렉토리에 저장한다. 예를 들어 max heap size를 2GB로 설정하려면 Xms 및 Xmx를 모두 2g으로 설정한다.-Xms2g -Xmx2g
테스트를 할 때는,
ES_JAVA_OPTS
환경 변수를 사용하여 heap size를 설정할 수도 있다.ES_JAVA_OPTS="-Xms2g -Xmx2g" ./bin/elasticsearch
ES_JAVA_OPTS
변수는 다른 모든 JVM 옵션을 재정의(override) 한다. 운영에서는ES_JAVA_OPTS
를 사용하지 않는 것이 좋다.
Heap: Sizing and Swapping
ref.2 의 내용은 오래된 버전의 내용이지만 알아둬야 할 내용들이 있다.
Give (less than) Half Your Memory to Lucene
일반적인 문제는 heap을 너무 크게 구성하는 것이다. 64GB의 컴퓨터를 사용하고 있다면, 놀랍게도 Elasticsearch에 64GB의 모든 메모리를 제공하려고 한다. 많을수록 좋다!
heap은 Elasticsearch에서 확실히 중요하다. 빠른 동작을 제공하기 위해 많은 in-memory data structure들에서 사용된다. 하지만 같은 맥락으로, memory의 또 다른 주요 사용자인 Lucene이 있다.
Lucene은 in-memory data sturcture를 caching하기 위해 OS를 활용하도록 설계되었다. Lucene segment는 개별 파일에 저장된다. segment는 immutable하므로 이러한 파일은 변경되지 않는다. 이것은 cache 를 사용하는데 아주 적합한 구조다. OS는 더 빠른 액세스를 위해 hot segement를 메모리에 상주시킨다. 이러한 segment들에는 inverted index(fulltext search 를 위한)과 doc values(aggregations의 경우)이 모두 포함된다.
Lucene의 성능은 OS와의 이 상호 작용에 달려있다. 하지만 Elasticsearch의 heap에 사용 가능한 모든 메모리를 준다면 Lucene에게 남은 메모리가 없게 된다. 이는 성능에 심각한 영향을 미칠 수 있다.
표준 권장 사항은 사용 가능한 메모리의 50%를 Elasticsearch heap에 할당하고 나머지 50%는 사용 가능한 상태로 두는 것이다. Lucene 이 남은 메모리를 사용할 것이다.
분석된 문자열 필드를 aggregate하지 않는 경우(예: 필드 데이터가 필요하지 않음) heap을 더 낮추는 것을 고려할 수 있다. heap을 작게 만들수록 Elasticsearch(더 빠른 GC)와 Lucene(캐싱을 위한 더 많은 메모리) 모두에서 더 나은 성능을 기대할 수 있다.
Don’t Cross 32 GB!
Elastic search에 엄청난 heap을 할당하지 않는 또 다른 이유가 있다. HotSpot JVM은 heap이 약 32GB 미만일 때 객체 포인터(object pointer)를 압축하는 트릭을 사용한다.
자바에서 모든 object들은 heap에 할당되고 포인터에 의해 참조된다. 일반 객체 포인터(Ordinary object pointers, OOP)는 이러한 object를 가리키며, 전통적으로 프로세서의 cpu의 native word size 이다.(32비트 또는 64비트, cpu에 따라 다르다). 포인터는 값의 정확한 byte location를 reference 한다.
32비트 시스템의 경우 최대 heap 크기는 4GB입니다. 64비트 시스템의 경우 heap 크기가 훨씬 커질 수 있지만 64비트 포인터의 오버헤드는 포인터가 더 크기 때문에 낭비되는 공간이 더 많다는 것을 의미한다. 그리고 낭비되는 공간보다 더 나쁜 것은, 큰 포인터들이 메인 메모리와 다양한 캐시들(LLC, L1 등) 사이에서 값을 이동할 때 더 많은 대역폭을 소비한다는 것이다.
자바는 이 문제를 피하기 위해 compressed oops 라고 불리는 트릭을 사용한다. 포인터들은 메모리에서 정확한 바이트 위치를 가리키는 대신 object offset 들을 참조한다. 이것은 32비트 포인터가 메모리에 있는 exact byte location 40억 바이트가 아닌 40억 개의 객체를 참조할 수 있다는 것을 의미한다. 궁극적으로, 이는 heap이 한계치가 4GB 인 32비트 포인터를 사용하면서도 메모리가 32GB로 증가할 수 있음을 의미한다.
마법의 ~32GB 경계를 넘으면 포인터가 다시 일반 개체 포인터(ordinary object pointers)로 전환된다. 각 포인터의 크기가 커지고 더 많은 CPU 메모리 대역폭이 사용되며 사실상 메모리가 손실된다. 실제로 할당된 heap의 유효 메모리가 약 40-50GB가 되어야 compressed oops을 사용하여 32GB 미만의 heap을 사용할 수 있다.
이 이야기의 교훈은 다음과 같다. 메모리가 남아있을 때에도 32GB heap 경계를 넘지 않도록 해야한다. 그것은 메모리를 낭비하고 CPU 성능을 떨어뜨리며 큰 heap 로 인해 GC 를 오래걸리게 한다.