JVM 설정

elasticsearch JVM이라고 검색을 해보면 많은 글들이 JVM은 전체 메모리에 50%로 설정하라고 설명하는 곳이 많습니다. 그렇지만 단순히 50%라고만 하기에는 사용자의 환경이 다르기 때문에 설명이 부족합니다. elasticsearch 공식 문서에 따르면 전체 메모리의 50% 이하로 설정하는 것을 권장하고 있으며, 환경에 따라 다르지만 대중적으로 26GB 이하로 설정해야 된다고 설명을 하고 있지만 사용자의 환경에 따라 30GB까지도 설정을 할 수 있다고 합니다. 그 이유는 Zero base comporessed OOP 때문인데요 Compressed OOP는 아래에서 다루도록 하겠습니다. 그리고 JVM 설정에 유의해야 되는 것은 전체 메모리의 50% 이하를 JVM에 줬다면 나머지 메모리는 시스템과 Lucene이 사용하게 됩니다. 그렇기 때문에 통계 및 분석이 많은 쿼리를 자주 사용한다면 JVM에 메모리를 높게 주고(50%에 가깝게) 검색만 주로 하는 쿼리를 자주 사용한다면 JVM메모리를 보다 적게 주어 Lucene이 더 많은 메모리를 사용할 수 있게 하는 것이 좋습니다.

 

Compressed OOP

포인터의 공간 낭비를 줄이고 좀 더 빠른 연산을 위해 포인터를 압축해서 표현하는 메모리 최적화 기법 중 하나입니다. 이 기법의 원리는 포인터가 메모리 주소를 가리키는게 아닌 Object Offset을 가리키도록 변형해서 동작시키는 것입니다. 만약 8비트 포인터를 사용한다면 256바이트의 물리적인 주소를 표현하는게 아니라 256개의 객체를 가리킬 수 있게 되는 것입니다. 만약 JVM의 크기가 32GB를 넘어가게 된다면 포인터 구조가 Compressed OOP가 아닌 일반 OOP 방식으로 전환되어 Compressed OOP의 이점을 살릴 수가 없습니다. 그러므로 메모리의 여유가 있다면 차라리 노드를 더 늘려 주는 것이 더 현명한 선택이 될 것 같습니다.

 

Compressed OOP 메모리 주소

  • 32비트 OOP : 2의 32승 까지의 주소를 가리킬 수 있다. 4GB
  • 64비트 OOP : 2의 64승 까지의 주소를 가리킬 수 있다. 18EB
  • 32비트 Compressed OOP : 2의 32승 오브젝트를 가리킬 수 있습니다.
  • 메모리 공간으로는 2의 32승 곱하기 8만큼 표현이 가능합니다.

 

Zero base Compressed OOP 확인

Zero base Compressed OOP는 JVM의 힙 메모리가 0번지부터 시작하게 하는 설정이다. JVM의 크기가 32GB로 설정하여 Zero base Compressed OOP가 깨진 것과 30GB 이하로 설정하여 Zero base Compressed OOP가 활성화된 것의 차이를 확인해 보겠습니다.

 

JVM 32GB

$ java -Xmx32768m -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -XX:+PrintComressedOopsMode 2>/dev/null | grep Compressed | grep Oops

bool PrintCompressedOopsMode = true
bool UseCompressedOops = false

 

JVM 30GB

$ java -Xmx30720m -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -XX:+PrintComressedOopsMode 2>/dev/null | grep Compressed | grep Oops

heep address : 0x0000000...
bool PrintCompressedOopsMode = true
bool UseCompressedOops = true

 

JVM 여러가지 옵션

Elasticsearch 공식 문서에 따르면 Xms, Xmx의 값은 동일해야 하며 전체 메모리의 50% 이하로 설정하며 root jvm.options 파일을 수정하지 않는 것을 권장합니다. 저는 Xms, Xmx만 수정하고 나머지는 그냥 이런 것들이 있다는 것만 알고 넘어갔습니다.

  • -Xms : 기본 heep 사이즈 (기본값 : 64MB)
  • -Xmx : 최대 heep 사이즈 (기본값 : 256MB)
  • -XX:PermSize : 기본 Perm 사이즈
  • -XX:MaxPermSize : 최대 Perm 사이즈
  • -XX:NewSize : 최소 New 영역 사이즈
  • -XX:MaxNewSize : 최대 New 영역 사이즈
  • -XX:SurvivorRatio : New 영역 / Survivor 영역 비율 설정
  • -XX:NewRatio : Young Gen, Old Gen의 비율 설정
  • -XX:+DisableExplicitGC : System.gc() 무시 설정
  • -XX:+CMSPermGenSweepingEnabled : Perm Gen 영역도 GC의 대상이 되도록 설정
  • -XX:+CMSClassUnloadingEnabled : Class 데이터도 GC의 대상이 되도록 설정

요약

  • JVM 설정은 전체 메모리의 50%이하로 설정
  • 사용자 환경에 따라 다르지만 최대 26GB이하로 설정하는 것을 권장합니다. 물론 환경에 따라 30GB까지 가능합니다.
  • 26GB 또는 30GB가 넘으면 Zero base compressed OOP가 깨짐
  • 통계 및 분석을 많이 사용하면 JVM을 50%에 가깝게 설정

 

참고

https://www.elastic.co/guide/en/elasticsearch/reference/current/advanced-configuration.html#set-jvm-options

 

Advanced configuration | Elasticsearch Guide [8.10] | Elastic

This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.

www.elastic.co


노드 역할 분리 및 노드 개수

소규모 프로젝트 및 개발 환경에서는 리소스를 절약하게 위해 단일 노드로 사용해도 무방합니다. 그러나 프로젝트의 규모가 거대해지고 데이터의 양이 많아지면 그때는 단일 노드로는 불가능한 시점이 오게 되고 Elasticsaerch의 클러스터가 깨지는 위험이 발생할 수 있습니다. 이럴 때 자원을 효율적(효율적이라는 표현은 자원을 적게 쓰는 것이 아닌 역할에 맞게 잘 활용한다는 의미)으로 사용하기 위해서는 노드의 역할을 분배하는 것을 권장하고 있습니다. 노드의 종류는 여러 가지가 있지만 대표적으로 마스터 노드와 데이터 노드를 분리하는 것만으로도 클러스터를 안정적으로 운영할 수 있습니다. 마스터 노드는 클러스터 운영관리 및 노드, 샤드를 관리하는 역할을 수행하고 데이터 노드는 데이터를 저장하고 검색하는 역할을 수행합니다. 마스터와 데이터의 역할을 분리하게 되면 마스터는 온전히 마스터의 역할만 수행하게 되어 마스터 노드에 문제가 발생할 문제가 줄어들게 됩니다. 마스터 노드를 설정할 때는 마스터 선출을 위한 Vorting이 이루어 지게 되므로 홀수개로 설정하는 것을 권장합니다. 마스터 노드를 여러 개로 설정할 경우 실제로 마스터의 역할을 수행하는 것은 1개이며 나머지는 현재 마스터 노드의 문제가 발생할 경우 마스터로 승격되어 마스터 노드의 역할을 수행할 수 있도록 Standby형태로 대기하고 있습니다.

 

Scale In시 주의사항 (in kubernetes)

인덱스의 데이터는 샤드로 분할하여 데이터가 저장됩니다. 이때 Replica Shard를 지정하지 않았다면 데이터 노드의 Scale In을 수행할 때 데이터의 손실이 발생할 수 있습니다. 물론 Replica Shard를 구성하더라도 무턱대로 Scale In을 수행하면 데이터의 손실일 발생할 수 있습니다. 아래 그림은 Scale In 수행시 데이터 손실이 발생하는 경우입니다.

그림에서 볼 수 있듯이 Replica Shard가 존재하더라도 Data Node 2번과 3번이 같이 드랍되면 Shard2번의 데이터는 손실되게 됩니다. 그렇기 때문에 Scale In을 수행할 때는 1개씩 줄여가면서 데이터의 복사가 완전히 이루어지면 다음 노드를 줄이는 것을 권장합니다.

 

Scale Up시 주의사항 (in kubernetes)

저 같은 경우 Elasticsearch를 Kubernetes 환경에서 사용하고 있습니다. 만약 마스터 노드가 1개밖에 없는 상황에서 마스터 노드를 스케일업을 수행하게 되면 클러스터가 깨질 수 있습니다. 그 이유는 해당 마스터 노드가 드랍될 때 마스터의 역할을 수행할 수 있는 노드가 없기 때문입니다. 그렇기 때문에 Kubernetes환경에서 마스터 노드가 1개 일 경우 스케일업을 수행할 때는 반듯이 먼저 스케일 아웃을 하여 해당 마스터 노드가 드랍되더라도 마스터의 역할을 대신할 수 있는 노드가 있기 때문에 문제없이 클러스터를 운영할 수 있습니다.

 

노드는 무조건 많으면 좋을까?

노드의 개수가 많으면 데이터를 분할 저장하고 부하를 나눠 받기 때문에 무조건 좋게 느껴질 수 있습니다. 그러나 각각의 노드들은 서로 통신을 하며 클러스터를 유지하기 때문에 노드가 너무 많게 되면 서로 통신하느라 클러스터의 문제가 발생할 수 있습니다. 그래서 저는 스토리지가 부족하면 스케일아웃을 수행하고 CPU, Memory 리소스가 부족하면 스케일업을 수행하고 있습니다. 무엇이 부족한지 측정하기 위해서는 Elasticsearch Exporter를 설치하여 Prometheus, Grafana를 통하여 측정할 수 있습니다.

vm.max_map_count

Elasticsearch는 파일을 메모리에 매핑하고 해당 Lucene MMapDirectory을 통하여 인덱스 샤드가 저장됩니다. 이러한 동작 과정에서 많은 데이터를 처리하기 위해 어느 정도 크기의 가상 주소 공간이 필요합니다. 그러나 기본적으로 대부분의 운영체제에서는 65,630 만큼의 mmap 개수로 설정되어 있는데 이 수치는 elasticsearch를 운영하는데 매우 작은 양입니다. 그래서 별도로 설정을 하지 않고 Elasticsearch를 실행시키면 bootstrap에 의해 해당 vm.map_map_count의 값이 매우 작다는 Error가 발생하면서 Elasticsearch가 종료합니다. 이러한 문제를 해결하기 위해 vm.max_map_count의 값을 262,144 값으로 수정하여 사용하시면 됩니다.

수정 방법

# 명령어로 변경하면 재부팅 시 유지되지 않는다.
$ sudo sysctl -w vm.max_map_count = 262144

# 파일 수정으로 영구적으로 값 유지 vm.max_map_count 수정
$ vim /etc/sysctl.conf

# max_map_count 값 확인
$ sysctl vm.max_map_count

Kubernetes 환경에서 설치된 Elasticsearch는?

kubernetes 환경에서 helm을 통하여 elasticsearch를 설치하셨다면 해당 설정을 굳이 하실 필요가 없는데요 그 이유는 Elasticsearch StatefulSet의 initcontainer의 "configure-sysctl"이라는 컨테이너가 Elasticsearch가 구동되기 전에 해당 설정을 적용하고 Elasticsearch가 동작하기 때문입니다.

 

elasticsearch/template/statefulset.yaml

      - name: configure-sysctl
        securityContext:
          runAsUser: 0
          privileged: true
        image: "{{ .Values.image }}:{{ .Values.imageTag }}"
        imagePullPolicy: "{{ .Values.imagePullPolicy }}"
        command: ["sysctl", "-w", "vm.max_map_count={{ .Values.sysctlVmMaxMapCount}}"]

참고

https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-store.html

 

Store | Elasticsearch Guide [8.10] | Elastic

This is a low-level setting. Some store implementations have poor concurrency or disable optimizations for heap memory usage. We recommend sticking to the defaults.

www.elastic.co

https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html

 

Virtual memory | Elasticsearch Guide [8.10] | Elastic

Elasticsearch uses a mmapfs directory by default to store its indices. The default operating system limits on mmap counts is likely to be too low, which may result in out of memory exceptions. On Linux, you can increase the limits by running the following

www.elastic.co


메모리 스와핑 비활성화

대부분의 운영체제에서는 메모리 관리를 효율적으로 하기 위해서 메모리 스와핑이라는 기술을 사용합니다. 메모리 스와핑은 메모리가 부족하거나 현재 사용되지 않거나 우선순위가 낮은 프로세스의 데이터를 메모리 스와핑을 통하여 디스크에 이동시켜 메모리를 효율적으로 사용하는 방법인데 Elasticsearch에서는 이러한 메모리 스와핑이 치명적인 성능 저하의 원인이 될 수 있습니다.

Elasticsearch는 메모리를 많이 사용하는 특성을 가지고 있는데 메모리 스와핑 작업에 의해 가비지 컬렉션이 비정상적으로 느려지며 노드가 데이터를 디스크로 쓰이고 읽어지는 과정에서 데이터를 처리하는데 응답속도가 매우 느려지고 이러한 문제로 인하여 클러스터와의 연결이 끊어지는 문제가 발생하게 됩니다. 그래서 Elasticsearch 공식 문서에서는 메모리 스와핑을 비활성화하는 것을 권장하고 있습니다.

메모리 스왑핑 비활성

# 명령어로 일시적으로 스와핑 비활성화
$ sudo swapoff -a

# 파일 수정으로 영구적으로 비활성화
$ vi /etc/fstab


# 스왑 상태 확인 명령어
$ free
# 스왑 디렉토리 확인
$ cat /proc/swaps
# 스왑 통계 확인
$ sar -B 2 5

메모리 스와핑을 비활성화하기 때문에 Elasticsearch가 동작하는 노드에 다른 애플리케이션이 같이 동작하는 것은 권장하지 않습니다.

메모리 스와핑 비활성을 할 수 없는 상황이라면?

환경 특성상 메모리 스와핑을 비활성화할 수 없는 상황이라면 최대한 스와핑 주기를 낮춰 발생 빈도를 줄이는 방법이 있습니다.

# 명령어로 설정
$ sudo sysctl vm.swappiness=1

# 설정 확인
$ cat /proc/sys/vm/swappiness

memory_lock 설정

만약에 현제 root 권한이 없어 메모리 스와핑 설정이 불가능한 경우에는 bootstrap.memory_lock 설정을 통하여 메모리 스와핑을 최소화할 수 있습니다. 커널 수준에서 제공되는 함수 중에 mlockall()가 있는데 이 함수는 프로세스의 페이징을 금지하고 모든 메모리가 램에 상주하는 것을 보장해 줍니다. memory_lock을 이용하여 애플리케이션에서 메모리를 강제로 스와핑 하지 못하도록 설정합니다.

memory_lock 설정

# memory_lock 설정하기
$ vi ./elasticsearch.yml
bootstrap.memory_lock: true

# Request를 보내 memory_lock 설정 여부 확인하기
curl -XGET http://localhost:9200/_nodes?filter_path=**.mlockall

Kubernetes 환경에서 설치된 Elasticsearch는?

kubelet에서는 메모리 스왑이 기본적으로 off된 환경이라고 합니다. 그렇기 때문에 메모리 스왑핑에 대해서는 kubernetes 환경에서는 굳이 고민을 안 해도 될 것 같습니다. (https://github.com/elastic/helm-charts/issues/7)

참고

https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html

 

Disable swapping | Elasticsearch Guide [8.10] | Elastic

mlockall might cause the JVM or shell session to exit if it tries to allocate more memory than is available!

www.elastic.co


Max open file 설정

Elasticsearch에서는 클러스터의 노드끼리 소켓을 사용해 통신을 하며, 루씬 인덱스에서 데이터를 색인 처리하는데 많은 수의 파일을 사용하고 있습니다. 그렇기 때문에 파일을 열람할 수 있는 크기가 매우 작으면 문제가 될 수 있기 때문에 설정을 통하여 max open file의 수를 지정해 주어야 합니다.

max open file 설정

# 명령어를 통하여 일시적인 적용
$ sudo su
$ ulimit -n 81920

# 파일 수정을 통하여 영구적인 적용
$ vim /etc/security/limits.conf
irterm  softnofile  81920
irterm  hardnofile  81920

max file 설정 값 확인

curl -XGET http://localhost:9200/_nodes/stats/process

위 Request를 통해 open_file_descriptors로는 현재 열려 있는 파일 디스크립트의 개수를 알 수 있으며, max_file_descriptors를 통하여 최대 사용 가능한 파일 디스크립터의 개수를 확인할 수 있습니다.

참고

https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-system-settings.html

 

Configuring system settings | Elasticsearch Guide [8.10] | Elastic

Ubuntu and limits.conf Ubuntu ignores the limits.conf file for processes started by init.d. To enable the limits.conf file, edit /etc/pam.d/su and uncomment the following line: # session required pam_limits.so

www.elastic.co

 

+ Recent posts