Elasticsearch 입문 31편 — Performance Tuning (heap·thread pool·query cache·fielddata·circuit breaker)

2026-05-19Elasticsearch 입문에서 운영까지

Elasticsearch 입문 31편 Performance Tuning. Heap·Thread Pool·Cache·Fielddata·Circuit Breaker·Tune indexing·search.

📚 Elasticsearch 입문에서 운영까지 · 31편 — Performance Tuning (heap·thread pool·query cache·fielddata·circuit breaker)

이 글은 Elasticsearch 입문에서 운영까지 시리즈 38편 중 31편이에요. 26~30편이 "클러스터를 죽지 않게 운영하나" 였다면, 이번 편은 한 단계 더 들어가서 "같은 하드웨어로 응답 시간을 절반으로 줄이고 처리량을 두 배로 늘리려면 무엇을 만지나" 자리예요. 운영 ES 의 성능을 결정하는 다섯 군데가 JVM Heap · Thread Pool · Cache · Fielddata · Circuit Breaker 인데, 이 다섯 자리를 모르고 만지면 튜닝이 아니라 OOM 으로 가는 지름길 이 됩니다.

📚 학습 노트

이 글은 Elasticsearch 8.x 공식 docs 의 Tune for indexing speed · Tune for search speed · Circuit breaker settings · Thread pools 챕터를 한국어 학습 노트로 풀어쓴 자료예요.

튜닝은 *측정 → 가설 → 한 변수 변경 → 재측정* 사이클이 핵심이에요. 한 번에 다섯 군데 만지면 어디가 효과인지 영영 모릅니다.

운영 ES 의 응답 시간·처리량을 결정하는 다섯 자리

운영 ES 에서 응답이 느려지거나 처리량이 안 나올 때 원인 분포가 거의 항상 다음 다섯 자리예요. JVM Heap 이 부족하거나 너무 커서 GC pause 가 길고, Thread Pool 이 막혀 요청이 대기열에 쌓이고, Query/Request Cache 가 동작 안 해 매번 풀스캔이 돌고, Fielddata 가 메모리를 폭주시키고, Circuit Breaker 가 트리거돼 503 이 떨어져요. 이 다섯 자리만 잡으면 운영 ES 의 90% 성능 사고가 사라져요.

튜닝의 첫 단계는 추측하지 않기 예요. ES 는 _nodes/stats · _cluster/stats · _cat/thread_pool · _nodes/hot_threads · profile API 같은 운영 진단 도구를 풍부하게 줘서, 어디가 병목인지 를 거의 항상 눈으로 확인 할 수 있어요. 30편(Monitoring) 의 도구로 병목 자리를 먼저 찾고, 그 자리에 맞는 설정만 만지는 게 순서예요.

이 글은 다섯 자리를 위에서부터 차례로 깊이 들어가요. 마지막에 Index 튜닝(쓰기 쪽)Search 튜닝(읽기 쪽) 의 표준 체크리스트로 마무리.

JVM Heap — 50% 룰과 31GB 천장

ES 의 첫 번째 성능 자리는 JVM Heap 이에요. 너무 작으면 GC 가 폭주하고, 너무 크면 GC pause 가 길어지면서 오히려 느려져요. 두 자리 사이에 50% 룰31GB 천장 이라는 두 개의 절대 규칙이 있어요.

50% 룰

서버 물리 RAM 의 50% 를 JVM Heap 에 할당 하고, 나머지 50% 는 OS Page Cache 로 남겨 둡니다. ES 는 Lucene 의 세그먼트 파일 을 mmap 으로 매핑해 읽기 때문에, OS Page Cache 가 풍부할수록 디스크 I/O 가 줄어들어요. Heap 을 80~90% 까지 욕심내면 Page Cache 가 모자라서 Heap 은 남아도는데 디스크 I/O 가 폭주 하는 역설이 생겨요.

64GB 서버라면 Heap = 32GB 처럼 보이지만, 다음 천장이 또 있어요.

31GB 천장 — Compressed Ordinary Object Pointers

JVM 은 Heap 32GB 미만 일 때 Compressed Oops (Compressed Ordinary Object Pointers) 라는 최적화를 켜요. 객체 포인터를 64bit 대신 35bit 로 압축해서 메모리 사용량과 CPU 캐시 효율 을 30% 가량 개선하는 기법이에요. Heap 을 32GB 이상으로 잡으면 이 최적화가 꺼져서Heap 을 늘렸는데 오히려 가용 메모리가 줄어드는 역설이 발생해요.

그래서 ES 권장 Heap 상한은 31GB 예요. 64GB 서버에서도 Heap = 32GB 가 아니라 Heap = 30~31GB 를 잡고 나머지를 Page Cache 로 두는 게 표준이에요. 더 큰 메모리가 필요하면 Heap 을 늘리지 말고 노드 수를 늘리는 게 답입니다.

설정 방법

8.x 의 Heap 설정은 config/jvm.options.d/heap.options 또는 환경 변수로 박아요.

# config/jvm.options.d/heap.options
-Xms30g
-Xmx30g

Xms = Xmx최소 = 최대 를 같게 박는 게 운영 표준이에요. Heap 동적 증가 는 GC pause 를 길게 만들고 메모리 단편화 도 일으켜요. 기동 시점에 최대 메모리를 미리 잡아 두는 게 안전.

자동 계산을 원하면 ES_JAVA_OPTS 를 비우고 ES 가 RAM 의 50% (최대 31GB) 를 자동으로 잡도록 두는 옵션도 있어요. Docker 환경에서는 컨테이너 메모리 제한이 인식돼서 그 50% 를 잡습니다.

GC 선택 — G1GC vs ZGC

ES 8.x 의 기본 GC 는 G1GC (Garbage-First Garbage Collector) 예요. 대용량 Heap·짧은 pause 목적의 제너레이셔널 GC 고, 대부분의 운영 환경이 이걸로 충분해요. Heap 4GB 미만은 자동으로 Parallel GC 로 떨어집니다.

Java 17+ 환경에서 서브밀리초 pause 가 필요하면 ZGC (Z Garbage Collector) 도 선택지예요. 다만 처리량은 G1GC 보다 5~10% 떨어진다 는 트레이드오프가 있어서, 쓰기 처리량 중심 노드에는 G1GC 그대로 두는 게 일반이에요. ZGC 전환은 jvm.options.d/zgc.options-XX:+UseZGC 를 박아 실험.

OOM 방지 — -XX:HeapDumpOnOutOfMemoryError

기본 jvm.options-XX:+HeapDumpOnOutOfMemoryError 가 켜져 있어서, OOM 발생 시점에 heap dump (.hprof) 가 자동 생성돼요. 30편(Monitoring) 에서 짚었듯 dump 디렉터리는 충분한 디스크 가 있는 자리로 박아 두세요. dump 한 번에 30GB 가 나옵니다.

Thread Pool — 종류·queue·rejection

ES 의 두 번째 자리는 Thread Pool 이에요. ES 는 요청 종류별로 별도 스레드 풀 을 운영해서, 검색 부하가 색인을 막거나 그 반대 사고를 구조적으로 차단합니다. 풀 종류와 queue 동작을 모르면 503 Rejection 의 원인을 영영 못 찾아요.

주요 Thread Pool 일곱 가지

GET _cat/thread_pool?v 로 현재 상태를 확인할 수 있어요. 자주 만지는 풀은 다음.

크기 (default) queue 자리
search int((CPU * 3) / 2) + 1 1,000 검색 요청 처리
search_throttled 1 100 searchable snapshot 등 throttled 검색
write CPU 수 (최대 32) 10,000 색인·bulk·update·delete
get CPU 수 1,000 GET by id
analyze 1 16 _analyze API
management 5 (스레드 한도) 클러스터 관리
snapshot min(CPU/2, 5) (한도) 스냅샷 작업

기본값이 대부분 충분 해서 만질 일이 거의 없어요. 만지더라도 크기보다 queue 를 우선 확인하는 게 운영 감각.

Queue 와 Rejection

각 풀은 고정 크기 스레드대기열(queue) 으로 구성돼요. 모든 스레드가 바쁘면 요청이 queue 에 쌓이고, queue 까지 가득 차면 EsRejectedExecutionException (HTTP 429 또는 503) 으로 떨어집니다. 이 rejection 카운트 가 운영 중 가장 먼저 봐야 할 신호예요.

GET _cat/thread_pool/write?v&h=node_name,name,active,queue,rejected

rejected 열에 숫자가 쌓이면 쓰기 부하가 처리 용량을 초과 한다는 뜻이에요. 대응 순서는 (1) bulk 사이즈를 줄여 한 번에 던지는 양을 낮추고, (2) refresh_interval 을 늘려 색인 비용을 줄이고, (3) Data 노드를 추가해 부하를 분산하는 거예요. queue 크기를 늘리는 건 마지막 수단 — queue 만 늘리면 latency 가 폭증하면서 OOM 으로 가는 경로가 됩니다.

Queue 늘리기 — 정말 필요한 경우만

elasticsearch.yml 에서 queue 크기를 조정할 수 있어요.

thread_pool.write.queue_size: 20000

운영 권고는 queue 를 늘리기 전에 부하 자체를 줄이는 쪽 이에요. queue 를 늘리면 처리되지 못한 요청이 메모리에 쌓여 heap 사용량이 동반 증가하고, 끝내 Circuit Breaker 가 트리거되거나 OOM 으로 갑니다.

Query Cache — node level

ES 의 세 번째 자리는 Query Cache 예요. 노드 단위 캐시고, term·range 같은 비-scoring 쿼리 를 캐싱해서 같은 필터가 반복될 때 응답 시간을 수십 ms → 1ms 로 줄여요.

캐싱 대상

Query Cache 는 filter context 에서 실행되는 쿼리만 캐싱해요. score 계산이 필요한 쿼리(match 같은 풀텍스트) 는 캐싱 X. 12편(Search API) 에서 짚었듯 — bool.filter 자리에 둔 쿼리 가 캐싱 대상이고, bool.must 자리 는 score 계산 때문에 캐싱 X 라는 차이가 여기서 드러나요.

GET products/_search
{
  "query": {
    "bool": {
      "must":   [ { "match": { "title": "노트북" } } ],
      "filter": [ { "term":  { "brand": "samsung" } },
                  { "range": { "price": { "gte": 500000 } } } ]
    }
  }
}

위 쿼리에서 filter 의 term/range 두 줄이 캐싱되고, 다음번에 다른 사용자가 같은 brand·price 조건으로 검색하면 캐시 히트 가 됩니다.

설정 — indices.queries.cache.size

기본값은 Heap 의 10% 예요. 캐시 히트율이 낮으면 늘리고, 메모리 압박이 심하면 줄여요. _nodes/stats/indices/query_cache 로 hit_count / miss_count 비율을 확인.

indices.queries.cache.size: 15%

인덱스별 비활성화

특정 인덱스에서 Query Cache 가 오히려 메모리만 먹는다 면 인덱스 settings 로 끌 수 있어요.

PUT logs-2026-05/_settings
{ "index.queries.cache.enabled": false }

시계열 로그 처럼 같은 필터가 거의 반복되지 않는 인덱스는 캐시 효율이 낮아서, 끄는 게 오히려 빠른 경우가 있어요.

Request Cache — shard level

네 번째 자리는 Request Cache 예요. 샤드 단위 캐시고, size=0 으로 집계 결과 만 받는 검색 요청을 캐싱해서 대시보드 응답 을 폭발적으로 빠르게 만들어 줘요.

캐싱 대상

size=0 인 검색 요청(=문서는 안 받고 집계 결과만 받음) 이 자동으로 Request Cache 에 들어가요. Kibana 대시보드 가 이 자리에 정확히 매칭돼서, ELK 운영 환경에서 가장 효과가 큰 캐시 예요.

GET orders/_search
{
  "size": 0,
  "aggs": {
    "by_status": { "terms": { "field": "status" } }
  }
}

이 요청은 각 샤드별로 집계 결과 가 캐시되고, 인덱스가 변경되지 않는 한 같은 요청은 수 ms 로 응답이 떨어져요. 시간 기반 인덱스(logs-2026-05-18) 처럼 어제 인덱스가 더 이상 안 바뀌는 자리에서 Request Cache 효율이 90% 이상 잡힙니다.

설정 — indices.requests.cache.size

기본값은 Heap 의 1% 로 작아요. Kibana 대시보드 부하가 큰 환경은 2~5% 로 늘리는 게 일반.

indices.requests.cache.size: 2%

?request_cache=true 또는 ?request_cache=false 쿼리스트링으로 개별 요청 강제도 가능해요. 최신 데이터를 매번 받아야 하는 실시간 대시보드는 false 로 막아 둡니다.

Fielddata — text 필드 정렬·집계의 함정

다섯 번째 자리, 그리고 운영 ES OOM 사고의 1순위 원인Fielddata 예요.

왜 위험한가

ES 의 text 필드 는 정렬·집계가 기본적으로 막혀 있어요. 시도하면 다음 에러가 떨어집니다.

Fielddata is disabled on text fields by default.
Set fielddata=true on [field_name] in order to load fielddata in memory by uninverting the inverted index.

이걸 해결한답시고 "fielddata": true 를 켜면, 색인 시점에 만들어 둔 역색인(inverted index)런타임에 반대로 뒤집어서(uninvert) 메모리에 통째로 올려요. 100만 건 인덱스에 1KB 짜리 text 필드가 있으면 Fielddata 가 1GB 를 그 자리에서 먹어요. 데이터가 늘어날수록 비례해서 Heap 을 갉아먹고, 결국 OOM 이거나 Circuit Breaker 가 트리거됩니다.

표준 답 — keyword + doc_values

운영 표준은 text 필드를 절대 정렬·집계에 쓰지 않는 것 이에요. 집계·정렬이 필요한 필드는 keyword 로 매핑 하고, ES 가 기본으로 켜 두는 doc_values (Lucene 의 컬럼 지향 저장소) 를 사용해요.

PUT products
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "raw": { "type": "keyword" }
        }
      }
    }
  }
}

이렇게 multi-field 로 박아 두면 풀텍스트 검색은 title 로, 정렬·집계는 title.raw 로 쓸 수 있어요. 8편(Mapping Deep) 의 표준 패턴이 여기서 다시 등장.

doc_values vs fielddata 차이

doc_values fielddata
저장 위치 디스크 (mmap, OS Page Cache) JVM Heap
생성 시점 색인 시점 검색 시점 (uninvert)
지원 타입 keyword·number·date·geo 등 (text 제외) text
운영 부담 거의 없음 매우 큼

doc_values 는 디스크에 컬럼 지향으로 미리 저장 돼 있어서, OS Page Cache 가 알아서 캐싱해 줘요. Heap 부담이 거의 0 이고, ES 의 대부분 집계·정렬 이 이 자리에서 돌아갑니다.

Circuit Breaker — 메모리 보호 마지막 방어선

여섯 번째 자리는 Circuit Breaker 예요. ES 가 메모리 폭주로 죽기 직전 에 요청을 거절해서 프로세스 자체를 살리는 마지막 방어선이에요.

종류 다섯 가지

GET _nodes/stats/breaker 로 현재 상태를 볼 수 있어요.

Breaker 한도 (default) 자리
parent Heap 95% (indices.breaker.total.limit) 모든 breaker 의 상위 한도
fielddata Heap 40% Fielddata 메모리
request Heap 60% 단일 요청이 사용하는 메모리
in_flight_requests Heap 100% 전송 중 요청 (HTTP body 포함)
accounting Heap 100% 색인이 들고 있는 메모리 (segment 등)

parent 가 최상위 한도예요. 모든 하위 breaker 의 합이 Heap 의 95% 를 넘으면 Circuit Breaker Trigger 가 발생하고, ES 가 다음 에러를 떨어뜨립니다.

[parent] Data too large, data for [<http_request>] would be [12345678/12.3mb],
which is larger than the limit of [11796480/11.2mb]

대응 순서

circuit_breaking_exception 이 보이면 원인 breaker 를 먼저 봅니다. fielddata 가 원인이면 → Fielddata 끄기·매핑 수정. request 가 원인이면 → 대형 집계(terms.size: 100000) 가 의심. in_flight_requests 가 원인이면 → bulk 사이즈 축소. 한도를 늘리는 건 마지막 선택 이고, 한도를 늘릴수록 OOM 위험 이 같이 커진다는 트레이드오프가 항상 같이 옵니다.

한도 동적 변경

런타임에 cluster setting 으로 한도 변경이 가능해요.

PUT _cluster/settings
{
  "persistent": {
    "indices.breaker.total.limit": "85%",
    "indices.breaker.fielddata.limit": "30%"
  }
}

기본 95% 가 너무 빡빡해서 정상 운영에서도 자주 트리거되는 환경은 85% 로 낮춰서 더 일찍 거절하게 만드는 패턴도 자주 써요. 늦게 거절하면 OOM, 빨리 거절하면 503 — 둘 사이의 균형을 잡아 가는 자리가 Circuit Breaker 튜닝이에요.

Index 튜닝 — 쓰기 처리량 두 배

여기까지가 공통 다섯 자리 였고, 이제 쓰기·읽기 따로 의 표준 체크리스트로 들어갑니다.

refresh_interval 30s+

ES 의 기본 refresh_interval1초 예요. 1초마다 메모리 버퍼Lucene 세그먼트 로 flush 되고 검색 가능 상태가 됩니다. 이게 near real-time 의 정체.

대량 색인 자리에서는 이 1초가 세그먼트 폭증 → merge 폭주 의 주범이에요. 대시보드용 로그 인덱스 같이 실시간성이 필요 없는 인덱스는 30s 또는 60s 로 늘리면 쓰기 처리량이 2~3 배 로 뜁니다.

PUT logs-2026-05/_settings
{ "index.refresh_interval": "30s" }

재색인이나 일회성 bulk 작업이라면 아예 끄고 작업 후 다시 켜는 패턴도 표준.

PUT bulk-target/_settings
{ "index.refresh_interval": -1 }

Replica = 0 (재색인 시)

초기 bulk 색인 이나 재색인(reindex) 자리에서는 Replica = 0 으로 시작해서 색인 끝난 후에 Replica 를 복구하는 패턴이 표준이에요. Replica = 1 이면 모든 쓰기가 두 번 일어나서 처리량이 절반으로 떨어져요.

PUT bulk-target/_settings
{ "index.number_of_replicas": 0 }

// bulk 작업 진행

PUT bulk-target/_settings
{ "index.number_of_replicas": 1 }

Force Merge after bulk

대용량 bulk 가 끝나면 세그먼트가 수백~수천 개 로 잘게 쪼개진 상태가 돼서 검색 성능이 떨어져요. 인덱스가 더 이상 안 바뀌는 상태 라면 force_merge 로 세그먼트를 1 개로 합칩니다.

POST logs-2026-05/_forcemerge?max_num_segments=1

주의 — force_merge 는 비용이 매우 큰 작업 이고, I/O 폭주CPU 폭주 를 동시에 일으켜요. 운영 중인 인덱스에는 절대 X. 시간 기반 인덱스의 어제 인덱스 처럼 더 이상 쓰지 않는 자리에서만 돌립니다.

Bulk 사이즈 — 5~15MB

Bulk API 의 한 번에 던지는 사이즈5~15MB 가 권장 범위예요. 너무 작으면 네트워크 왕복이 늘고, 너무 크면 circuit_breaker 가 트리거됩니다. 23편(Bulk API) 에서 깊이 들어간 자리.

Search 튜닝 — 읽기 응답 시간 절반

track_total_hits=false

기본 응답의 hits.total.value정확한 전체 매칭 수 를 계산해 줘요. 이게 수백만 건 인덱스 에서는 불필요한 비용 이 됩니다. 페이지네이션에 정확한 총합이 필요 없는 자리는 track_total_hits: false 로 끄면 응답 시간이 30~50% 빨라집니다.

GET products/_search
{
  "track_total_hits": false,
  "query": { "match_all": {} }
}

또는 대략 10,000 까지만 정확히 알고 싶다면 track_total_hits: 10000 처럼 임계값 만 잡아 줘도 됩니다.

_source 최소화

_source원본 문서 전체 를 응답에 담아 줘요. 큰 문서(>100KB) 가 자주 검색되는 환경은 필요한 필드만 골라 받는 게 빠릅니다.

GET products/_search
{
  "_source": ["id", "title", "price"],
  "query": { "match": { "title": "노트북" } }
}

_source: false 로 완전히 끄거나, fields API 로 runtime 가공된 값만 받는 패턴도 자리.

Search Profile API

특정 쿼리가 왜 느린지 가 안 보이면 Profile API 를 켭니다. ?profile=true 또는 본문에 "profile": true 를 박으면 쿼리의 각 단계별 시간 이 응답에 같이 나와요.

GET products/_search
{
  "profile": true,
  "query": { "match": { "title": "노트북" } }
}

응답의 profile.shards[].searches[].queryLucene 의 weight·scorer 호출별 시간(ns) 이 박혀서, 어느 sub-query 가 90% 의 시간을 먹는지 눈으로 확인 할 수 있어요.

preference 로 캐시 적중률 끌어올리기

같은 사용자의 연속 요청을 항상 같은 샤드 복제본 으로 보내면 Request Cache 적중률 이 폭증해요. ?preference=_local 이나 사용자 id 를 ?preference=user-12345 처럼 박아 두면 해시 기반 으로 같은 replica 로 라우팅됩니다.

자주 만나는 사고

사고 1 — Heap 32GB 초과 후 Compressed Oops 망함

원인Heap 이 클수록 좋다 는 직관으로 64GB 서버에 Heap = 48GB 를 박았더니, Compressed Oops 가 꺼져서 가용 메모리가 오히려 줄어들고 응답 시간이 30% 느려짐.

해결 — Heap 을 30~31GB 로 다시 낮춥니다. 더 많은 메모리가 필요하면 노드 수를 늘리는 게 정답이지, 한 노드의 Heap 을 키우는 게 답이 아닙니다.

사고 2 — Thread Pool queue 폭주 503

원인 — 야간 일괄 색인 작업이 bulk 100MB × 100 동시 로 들어가면서 write thread pool 의 queue 10,000 이 가득 차고, EsRejectedExecutionException 이 폭주.

해결 — 단계 (1) bulk 사이즈를 100MB → 10MB 로, (2) 동시성을 100 → 10 으로, (3) refresh_interval 을 1s → 30s 로 늘리는 순서. queue 크기를 늘리는 건 마지막. 늘려도 latency 폭증 + OOM 으로 가는 게 일반.

사고 3 — Query Cache TTL 없어 stale 데이터 응답

원인 — Query Cache 는 인덱스가 변경되지 않는 한 유지되는 near-LRU 캐시예요. 자주 갱신되는 카테고리 인덱스에서 오래된 카운트 가 자꾸 나오는 사고.

해결 — 운영 표준은 인덱스 갱신 시 Lucene 세그먼트 변경 → 캐시 자동 무효화대부분 자동 해결. 그래도 안 풀리면 POST <index>/_cache/clear 로 수동 무효화. 정말 stale 이 답이 아닌 자리는 그 인덱스의 Query Cache 자체를 끄는 것 이 답.

사고 4 — Fielddata Circuit Breaker triggered

원인 — Kibana 대시보드에서 text 필드 messageterms aggregation 을 돌렸더니, ES 가 Fielddata 를 메모리에 올리려다 Circuit Breaker 가 트리거.

해결 — 진짜 답은 매핑 수정 이에요. message 필드에 multi-field .keyword 를 추가하고, Kibana 에서 message.keyword 로 집계를 다시 설정. 임시 회피로 fielddata: true 를 켜는 건 내일 OOM 이 예약돼 있으니 X.

사고 5 — Force Merge 운영 중 실행

원인 — 응답이 느려져서 force_merge 를 운영 시간에 돌렸더니, I/O · CPU 폭주 로 검색 응답이 수십 초 단위 로 폭증.

해결 — force_merge 는 반드시 비운영 시간 에, 읽기 트래픽이 적은 인덱스 에만. 시간 기반 어제 인덱스 처럼 더 이상 쓰지 않는 자리에서만 돌려요.

사고 6 — Deep Pagination + track_total_hits=true

원인from: 100000, size: 100 + track_total_hits: true 의 검색이 각 샤드에서 100,100 개를 모두 정렬 해야 해서 수십 초 단위로 응답이 떨어짐.

해결 — 19편(Search Features) 의 search_after 또는 PIT(Point in Time) 으로 전환. 동시에 track_total_hits 를 false 또는 10000 임계값 으로 박아 둡니다.

사고 7 — Bulk 사이즈 100MB

원인bulk 한 번에 100MB 로 던져서 in_flight_requests circuit breaker 가 트리거.

해결 — bulk 사이즈를 5~15MB 로 분할하고 동시 요청 수CPU 수의 1~2 배 로 맞춥니다. 클라이언트 라이브러리(예: Spring Data ES, elasticsearch-java) 의 BulkProcessor 가 이 자리를 자동으로 잡아 줍니다.

운영 권장 패턴

운영 ES 의 튜닝은 다음 4가지 패턴이 거의 모든 사고를 막아 줘요.

Heap 은 30~31GB 고정, Xms = Xmx. 그 이상의 메모리가 필요하면 노드 수를 늘리는 게 답. 한 노드의 Heap 을 키우는 게 답이 아닙니다.

Thread Pool 은 거의 기본값 그대로, queue 를 늘리지 말고 부하 자체 를 줄여요. bulk 사이즈 축소·refresh 늘림·동시성 축소 순서가 표준 대응 사다리.

Fielddata 는 운영에서 영원히 끄고, 집계·정렬이 필요한 필드는 keyword + doc_values 로. multi-field 패턴 이 표준.

대량 색인 시점 패턴Replica = 0 + refresh_interval = -1 + bulk 5~15MB → 끝난 후 Replica·refresh 복구 → force_merge. 이 사이클을 잡아 두면 색인 처리량이 기본 대비 3~5 배 로 뜁니다.

대시보드에 Heap usage · GC pause · Thread pool rejected · Circuit breaker tripped · Query cache hit ratio · Request cache hit ratio 여섯 지표를 박아 두고, 지표가 임계값을 넘는 순간 알람 으로 잡는 게 30편(Monitoring) 과 이어지는 자리예요.

시험 직전 한 번 더 — 압축 노트

  • 운영 ES 성능 자리 다섯 = Heap · Thread Pool · Cache · Fielddata · Circuit Breaker.
  • Heap 50% 룰 — 서버 RAM 의 50% 를 Heap 에, 나머지는 OS Page Cache.
  • 31GB 천장 — Compressed Oops 가 Heap < 32GB 에서만 동작. Heap = 30~31GB 권장.
  • Xms = Xmx최소 = 최대 동일. Heap 동적 증가는 GC pause 증가.
  • 기본 GC 는 G1GC. 서브밀리초 pause 필요시 ZGC.
  • Thread Pool 주요 = search · write · get · analyze.
  • Rejection 카운트 가 가장 먼저 봐야 할 신호. queue 늘리기 전에 부하 축소.
  • Query Cache = 노드 레벨, filter context 만 캐싱. default Heap 10%.
  • Request Cache = 샤드 레벨, size=0 집계 만 캐싱. default Heap 1%.
  • Fielddata = text 필드 정렬·집계 시 메모리 폭주 → 운영에서 영원히 OFF.
  • 답은 keyword + doc_values + multi-field 패턴.
  • Circuit Breaker 종류 = parent · fielddata · request · in_flight_requests · accounting.
  • parent default = Heap 95%, fielddata default = Heap 40%.
  • 색인 튜닝 — refresh_interval 30s+ · Replica=0 (재색인) · force_merge after bulk · bulk 5~15MB.
  • 검색 튜닝 — track_total_hits=false · _source 최소화 · profile API · preference 라우팅.
  • force_merge 는 운영 중 X, 비운영 시간 + 어제 인덱스 에만.
  • 자주 만나는 사고 7가지 = Heap 32GB 초과·queue 폭주·Query Cache stale·Fielddata Breaker·force_merge 운영 중·Deep Pagination·bulk 100MB.

시리즈 다른 편

한 줄 정리 — Performance Tuning = Heap 30~31GB · Thread Pool 기본 + 부하 축소 · Query/Request Cache 활용 · Fielddata 영구 OFF · Circuit Breaker 한도 관리 · refresh·replica·force_merge 색인 사이클 · track_total_hits·_source·profile 검색 절약 의 8자리 체크리스트. 측정 → 한 변수 변경 → 재측정 사이클이 절대 규칙.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!