엘라스틱서치는 메모리를 많이 사용하는 애플리케이션이다. 시스템에서 제공되는 물리 메모리를 JVM 힙에 할당해서 엘라스틱서치가 사용하도록 설정할 수 있다. 일반적으로 힙 메모리가 많을수록 성능도 올라간다.
너무 작은 힙 크기는 OOM을 발생시킬 수 있으며, 너무 큰 힙 크기는 FullGC가 발생할 때 시스템이 마비되는 STW를 발생시킬 수 있다.
엘라스틱서치와 힙 크기
cat jvm.options
Xms = 4G
Xmx = 4G
- 엘라스틱서치를 기본 설정대로 설치하면 힙 크기가 1GB로 되어있다.
- 1GB는 엘라스틱서치를 처음 사용하는 사람들의 테스트 용도로 제공되는 값으로 운영에는 더 높은 값으로 설정이 필요하다.
- config.options 파일을 열어 Xms(최소 힙 크기), Xmx(최대 힙 크기) 설정을 변경하면 된다.
- JVM이 초기 실행될 때 Xms 크기로 동작하다가 힙 크기가 부족하면 최대 Xmx까지 자동으로 늘어난다.
- 엘라스틱서치는 기본적으로 메모리를 많이 활용하는 애플리케이션임으로 Xms와 Xmx 크기를 같게 설정하는 것이 유리하다.
- 엘라스틱서치에서 힙 크기의 최댓값으로 32GB 이하를 설정하는 것을 권장한다.
엘라스틱서치의 힙 설정시, 고려사항
1) 운영체제에 50%의 메모리 공간을 보장하자.
- 실시간 검색을 지원하기 위해서는 루씬이 최대한 많은 시스템 캐시를 확보하도록 지원해야 한다.
- 시스템 캐시는 운영체제가 가지고 있는 메모리 공간으로 커널 내부에 존재
- 물리적인 메모리 공간의 50% 정도는 운영체제가 자유롭게 사용하도록 하고, 나머지는 엘라스틱서치 힙으로 할당하는 것이 적합
2) Java 8 기반에서는 힙 크기 32GB 이상 사용하지 말자

- 엘라스틱서치에서는 힙크기를 가급적 크게 설치하되 32GB 이상으로 사용하지 말 것을 권장한다.
- 만약 128GB 물리 메모리가 탑재된 고성능 하드웨어 장비에 엘라스틱서치를 설치한다면, 32GB 힙을 가지는 엘라스틱서치 노드 2개를 설치하는 것을 권장한다.
- 핫스폿 JVM의 Object Pointer 정책 때문에 자바기반 애플리케이션은 기본적으로 32GB 제한하는 것이다.
- Object Pointer : 객체의 메모리 번지를 표현하는 주소값
- JVM은 64비트, 32비트 상관없이 기본적으로 32 Object Pointer를 사용한다.
Ordinary Object Pointer
- 자바에서 모든 객체는 힙 영역에 생성되고, 생성된 객체는 모두 포인터를 가지고 있고, 포인터를 이용해 객체에 접근한다.
- JVM은 힙 영역에 생성된 객체에 접근하기 위한 포인터 주소를 OOP(Ordinary Object Pointer)라고 한다.
- OOP는 CPU 처리 단위에 따라 동작하는 방식이 약간 다르다.
- 32bit 시스템: 32bit까지 표현하기 때문에 최대 4GB까지 주소 공간을 가리킬 수 있음
- 64bit 시스템: 64bit까지 표현하지 때문에 이론상 18EB까지 주소 공간을 가리킬 수 있음
- 자바의 경우 32Bit에서 64Bit로 넘어가면서 메모리 낭비가 커지게 되었고, 이러한 문제를 해결하기 위해 Compressed OOP라는 개념을 도입했다.
참고
32비트 프로그램 -> 64비트 패러다임을 변화
32비트 시스템은 하나의 포인트를 표현하기 위해서는 32비트가 필요하며, 32 비트를 이용하면 최대 4GB의 메모리 주소밖에 가리킬 수 없다. 이 때문에 과거에는 4GB 물리적인 메모리만 사용할 수 있었지만, 하드웨어가 발전하면서 현재는 64 비트 시스템을 사용하게 되었고 64 비트 시스템은 하나의 포인트를 표현하기 위해서는 64비트가 필요하며, 이를 통해 고성능 물리 메모리도 인식할 수 있게 되었다.
단, 64비트 시스템 같은 경우 메모리상 주소를 가리키는 포인터 1개를 64Bit로 표현하다 보니 만은 메모리 공간 낭비를 초래하게 되고, CPU가 캐시 적중률을 높이기 위해 주메모리와 캐시 사이에서의 이동되는 데이터 역시 64 비트로 큰 대역폭을 소모하게 된다.
Compressed Ordinary Object Pointer
- Java는 성능 향상과 효율적인 메모리 사용을 위해 Compressed OOP 개념을 도입하였으며, 최신 JDK에서는 64비트 환경에서 Compressed OOP로 인해 힙 메모리를 효율적으로 사용할 수 있다.
- Compressed OOP는 포인터의 공간 낭비를 줄이고 좀 더 빠른 연산을 위해 포인터를 압축해서 표현하는 일종의 트릭이다.
- 포인터가 객체의 정확한 메모리 주소가 아닌 상대적인 오브젝트 오프셋을 가리키도록 변형해서 동작시키는 것이다.
- 만약 8비트 포인터를 이용해 이 트릭을 사용하면 256 바이트 공간이 아닌 256개의 객체를 가리킬 수 있게 되고, 8배나 큰 주소 공간을 사용하는 것이다.
- Java는 데이터 타입에 따라 8비트~64비트 까지 힙 메모리에 생성하기 때문에 Compressed OOP를 이용해 표현하게 된다면 32비트만으로 최대 32GB까지의 힙 메모리 공간을 가리키는 것이 가느아다.
- 32Bit Compressed OOP: 2의 32승까지의 객체를 가리킬 수 있다 되며, 객체의 최소 단위가 8비트이기 때문에 2의 32승 * 8까지 메모리 공간을 가리킬 수 있다.
- 결과적으로 Compressed OOP를 사용할 경우 32비트 포인터를 이용하면서도 64 비트 포인터가 가지는 메모리 낭비 등의 단점을 우회해서 동작할 수 있다.
- 단, 이러한 트릭은 힙 크기가 32GB를 넘어가면 더 이상 사용할 수 없으며, 32GB를 넘어가는 순간 자동으로 64비트 OOP로 자동 변환이 된다. (엘라스틱서치가 32GB로 설정하라는 이유)
자바 입장에서는 설정된 최대 힙 메모리 크기가 너무 커지면 그에 따라 애플리케이션 측면의 부담도 늘어나며, 각자의 환경에 맞게 최적화하는 것이 중요하다.
엘라스틱서치에서 힙 크기 설정하기
엘라스틱서치에서는 32GB 이상으로 힙크기를 설정하면, 일반 OOP가 전환되어, Compressed OOP의 이점이 사라진다. 추가로 너무 큰 힙은 FullGC를 수행하는 시간도, 늘어나고 그에 따라 시스템이 처리 불능에 빠지는 시간(STW)도 늘어나기 때문에 32GB의 여러 개의 인스턴스를 생성하는 것을 추천한다.
물리 메모리를 설정하는 예시 상황
1) 적절한 성능 서버를 가지고 있을 때
- 가급적 고성능 서버를 피하는 것이 좋다(64GB 물리서버 여러대가 더 유리, 32GB는 엘라스틱이 나머지는 운영체제가 가지도록 하는 게 유리)
- 총 물리 메모리 64GB -> 운영체제: 32GB, 엘라스틱서치 인스턴스: 1개(32GB)
2) 고성능 서버를 가지고 있을 때
- 탑재된 물리 메모리의 반을 32GB 나눠서, 그 수만큼 엘라스틱서치 인스턴스를 생성한다.
- 총 물리 메모리 128GB -> 운영체제: 64GB, 엘라스틱서치 인스턴스: 2개(32GB * 2)
3) 전문(Full Text) 검색을 주목적으로 엘라스틱 서치를 사용할 경우
- 엘라스틱서치 힙에 32GB를 할당하고 나머지는 운영체제에 남겨 루씬이 시스템 캐시를 통해 메모리를 최대한 사용할 수 있게 한다.
- 전문 검색은 메모리 연산보다 루씬의 역색인 구조를 이용하는 것이 많기 때문에 시스템 캐시에 세그먼트를 많이 캐시 할수록 전문 검색이 빠르다
- 총 물리 메모리 128GB -> 운영체제: 96GB, 엘라스틱서치 인스턴스: 1개(32GB)
4) 일반적인 데이터 필드(Not Analyzed)에서 정렬/집계 작업을 많이 수행할 경우
- 숫자, 날짜, geo_point, keyword 같은 데이터 타입은 별도의 분석 과정이 필요 없다.
- 정렬이나 집계시, 루씬의 DocValues를 사용하기 때문에 힙 공간은 거의 사용하지 않는다.
- 총 물리 메모리 128GB -> 운영체제: 96GB, 엘라스틱서치 인스턴스: 1개(32GB)
5) 전문(Full Text) 필드(Analyzed)에서 정렬/집계 작업을 많이 수행할 경우
- 분석된 문자열 필드에서 정렬이나 집계를 수행할 경우 루씬의 DocValues를 사용할 수 없기에 fielddata 힙 기반 캐시를 사용해야 한다.(연산에 힙 메모리 많이 사용)
- 다만 힙이 너무 크면 메모리 낭비와 FullGC 문제가 발생하므로 여러 개의 엘라스틱서치 인스턴스를 생성하는 게 좋다.
- 총 물리 메모리 128GB -> 운영체제: 64GB, 엘라스틱서치 인스턴스: 2개(32GB * 2)
참고
하나의 물리서버에서 다수의 엘라스틱서치 인스턴스 실행 시 주의점
엘라스틱서치의 레플리카 샤드는 물리적으로 프라이머리 샤드와 다른 서버에 생성된다.(물리적으로 분산되어야 서버가 다운 시 즉시 복구 가능).
하지만 하나의 물리서버에 다수의 인스턴스가 실행될 경우 고가용성 문제가 생길 수 있는데, 엘라스틱서치는 이를 방지하기 위해 cluster.routing.allocation.same_shard.host 옵션을 제공한다. 인스턴스가 실행될 때 이 설정을 통해 프라이머리 샤드와 레플리카 샤드가 같은 서버에 배치되는 것을 방지할 수 있다.
만약 하나의 물리서버에서 다수의 인스턴스가 실행되는 환경이라면 반드시 이 옵션을 활성화해서 고가용성을 보장해야 한다.
엘라스틱서치에서 Compressed OOP 사용하기
-XX: +UseCompressed0ops
- 최신 JDK에서는 Compressed OOP가 기본 설정으로 동작하기 때문에 단순 힙 크기를 32GB 이하로 설정하면 된다.(JDK 8 이상)
- Compressed OOP의 Limit 값은 JVM 버전과 플랫폼에 따라 달라진다.
- 시스템마다 UseCompressedOops 조회를 통해 Limit을 확인해야 한다.
- 시스템마다 다른 이유는 JVM 힙 메모리가 0번지부터 시작되는게 아니기 때문에, 실제 시작 번지 부터 0번지 까지는 활용할 수 없기 때문이다.
- Compressed OOP로 동작 가능한 limit 값을 정확히 설정하기 위해서는 엘라스틱서치가 실행될 때 실제 시스템에서 limit 값을 확인하고 힙 메모리 크기 설정을 고려해야 한다.
Zero-Based Compressed OOP
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressed0opsMode 2>/dev/null |grep Compressed
Igrep Oops
- 최신 JVM에 도입된 개념으로 JVM이 시작될 때 힙 메모리의 시작 번지가 0번지부터 시작되도록 논리적으로 강제한다.
- Compressed OOP는 0번지부터 시작되는 것이 아니라 100% Shift 연산을 통한 연산이 불가능하지만, Zero-Based Compressed OOP는 0번지 부터 시작되기 때문에 Shift 연산을 통해 특정 번지수를 찾는 게 가능하다
- Compressed OOP에서 Zero-based Compressed OOP로 동작시키기 위해서는 힙 메모리 크기를 조금 더 줄여야 하며, 모든 조건이 만족하면 Zero-based Compressed OOP로 동작하게 된다.
힙 크기에 따른 OOP 설정



각자의 환경에 맞게 사용여부를 선택하면 된다.
엘라스틱서치 로그로 Compressed OOP 사용 여부 확인하기

- 엘라스틱서치 노드가 Compressed OOP로 동작하는지 시작 로그를 통해 확인할 수 있다.
- 만약 compressed ordinary object pointers [false] 일 경우 limit 계산이 틀린 것으로 재확인이 필요하다
'Elastic Search' 카테고리의 다른 글
| Ch10. 대용량 처리를 위한 시스템 최적화 - 분산환경에서의 메모리 스와핑 (0) | 2025.10.10 |
|---|---|
| Ch10. 대용량 처리를 위한 시스템 최적화 - 엘라스틱서치와 가상 메모리 (0) | 2025.10.10 |
| Ch10. 대용량 처리를 위한 시스템 최적화 - 노드 실행 환경과 JVM 옵션 (0) | 2025.10.10 |
| Ch09. 엘라스틱서치와 루씬 이야기- 샤드 최적화 (0) | 2025.10.09 |
| Ch09. 엘라스틱서치와 루씬 이야기- 고가용성을 위한 Translog의 비밀 (0) | 2025.10.08 |