느린 Elasticsearch 쿼리 문제 해결: 식별 및 해결 단계
상태 점검, 느린 로그, 프로파일 API, 매핑, 샤드 및 더 안전한 쿼리 패턴을 사용하여 느린 Elasticsearch 쿼리를 진단하는 방법
느린 Elasticsearch 쿼리 문제 해결: 식별 및 해결 단계
느린 Elasticsearch 쿼리는 하나의 보편적인 설정으로 해결되는 경우가 드뭅니다. 쿼리가 느린 이유는 너무 많은 데이터를 스캔하거나, 너무 많은 샤드에 도달하거나, 비용이 많이 드는 집계를 요청하거나, 잘못된 필드에서 정렬하거나, 다른 작업을 기다리거나, 힙이나 디스크 대역폭이 이미 부족한 노드에 도달하기 때문일 수 있습니다.
가능하다면 실제 요청부터 시작하세요. "검색이 느리다"는 모호한 보고는 대응하기 어렵습니다. 복사된 요청 본문, 대상 인덱스 패턴, 시간 범위, 사용자 측 지연 시간 및 타임스탬프를 통해 느린 쿼리를 동일한 시점의 클러스터 메트릭과 비교할 수 있습니다.
Elasticsearch 쿼리 지연 시간 이해
문제 해결에 뛰어들기 전에 Elasticsearch에서 쿼리 성능에 영향을 미치는 주요 요소를 이해하는 것이 중요합니다.
- 데이터 볼륨 및 복잡성: 데이터의 양, 필드 수, 문서의 복잡성은 검색 시간에 직접적인 영향을 미칠 수 있습니다.
- 쿼리 복잡성: 간단한
term쿼리는 빠르지만, 많은 절, 집계 또는script쿼리가 포함된 복잡한bool쿼리는 리소스를 많이 사용할 수 있습니다. - 매핑 및 인덱싱 전략: 데이터가 인덱싱되는 방식(예:
text대keyword필드,fielddata사용)은 쿼리 효율성에 큰 영향을 미칩니다. - 클러스터 상태 및 리소스: 클러스터 노드의 CPU, 메모리, 디스크 I/O 및 네트워크 지연 시간이 중요합니다. 비정상적인 클러스터나 리소스가 제한된 노드는 필연적으로 느린 성능을 초래합니다.
- 샤딩 및 복제: 샤드의 수와 크기, 그리고 노드 간 분산 방식은 병렬 처리 및 데이터 검색에 영향을 미칩니다.
느린 쿼리를 위한 초기 점검
고급 프로파일링 도구를 사용하기 전에 항상 다음과 같은 기본 점검부터 시작하세요.
1. 클러스터 상태 모니터링
_cluster/health API를 사용하여 Elasticsearch 클러스터의 전반적인 상태를 확인하세요. red 상태는 기본 샤드가 누락되었음을 나타내고, yellow는 일부 복제본 샤드가 할당되지 않았음을 의미합니다. 둘 다 쿼리 성능에 심각한 영향을 미칠 수 있습니다.
GET /_cluster/health
status: green을 확인하지만, 여기서 멈추지 마세요. 녹색 클러스터도 과부하가 걸리거나, 샤딩이 잘못되었거나, 비효율적인 쿼리를 실행할 수 있습니다.
2. 노드 리소스 확인
개별 노드의 리소스 사용률을 조사하세요. 높은 CPU 사용률, 낮은 사용 가능 메모리(특히 힙), 또는 포화된 디스크 I/O는 병목 현상의 강력한 지표입니다.
GET /_cat/nodes?v
GET /_cat/thread_pool?v
cpu, load_1m, heap.percent 및 disk.used_percent에 주목하세요. 높은 search 스레드 풀 큐 크기도 과부하를 나타냅니다.
3. 느린 로그 분석
Elasticsearch는 정의된 임계값을 초과하는 쿼리를 기록할 수 있습니다. 이는 개별 요청을 깊이 파고들지 않고 특정 느린 쿼리를 식별하는 훌륭한 첫 번째 단계입니다.
느린 로그 임계값은 인덱스 설정입니다. 영향을 받는 인덱스 또는 인덱스 패턴에 적용하여 모든 노드 로그를 범람시키지 않으면서 유용한 예제를 캡처하세요.
PUT /my-index/_settings
{
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.fetch.warn": "1s"
}
그런 다음 Elasticsearch 로그에서 [WARN][index.search.slowlog]와 같은 항목을 모니터링하세요.
심층 분석: 프로파일 API로 병목 현상 식별
초기 점검으로 문제를 정확히 찾아낼 수 없거나 특정 쿼리가 느린 이유를 이해해야 하는 경우 Elasticsearch 프로파일 API가 가장 강력한 도구입니다. 이 API는 각 구성 요소가 소비한 시간을 포함하여 쿼리가 낮은 수준에서 어떻게 실행되는지에 대한 자세한 분석을 제공합니다.
프로파일 API란?
프로파일 API는 검색 요청에 대한 완전한 실행 계획을 반환하며, 각 쿼리 구성 요소(예: TermQuery, BooleanQuery, WildcardQuery) 및 수집 단계에 소요된 시간을 자세히 보여줍니다. 이를 통해 쿼리의 어떤 부분이 가장 많은 시간을 소비하는지 정확히 식별할 수 있습니다.
프로파일 API 사용 방법
기존 검색 요청 본문에 "profile": true를 추가하기만 하면 됩니다.
GET /your_index/_search?profile=true
{
"query": {
"bool": {
"must": [
{ "match": { "title": "elasticsearch" } }
],
"filter": [
{ "range": { "date": { "gte": "now-1y/y" } } }
]
}
},
"size": 0,
"aggs": {
"daily_sales": {
"date_histogram": {
"field": "timestamp",
"fixed_interval": "1d"
}
}
}
}
참고: 프로파일 API는 오버헤드를 추가하므로 특정 쿼리를 디버깅하는 데 사용하고 모든 요청에 대해 프로덕션에서 사용하지 마세요.
프로파일 API 출력 해석
출력은 장황하지만 구조화되어 있습니다. profile 섹션 내에서 찾아야 할 주요 필드는 다음과 같습니다.
type: 실행 중인 Lucene 쿼리 또는 수집기의 유형(예:BooleanQuery,TermQuery,WildcardQuery,MinScoreCollector).description: 구성 요소에 대한 사람이 읽을 수 있는 설명으로, 종종 작동 중인 필드와 값을 포함합니다.time_in_nanos: 이 구성 요소와 그 하위 항목이 소비한 총 시간(나노초)입니다.breakdown: 다양한 단계(예:rewrite,build_scorer,next_doc,advance,score)에서 소요된 시간에 대한 자세한 분석입니다.
예제 해석: WildcardQuery 또는 RegexpQuery가 높은 time_in_nanos를 보이고 상당한 부분이 rewrite에 소요되는 경우, 이는 쿼리 재작성(와일드카드 패턴 확장)이 특히 높은 카디널리티 필드나 대규모 인덱스에서 매우 비용이 많이 든다는 것을 나타냅니다.
...
"profile": {
"shards": [
{
"id": "_na_",
"searches": [
{
"query": [
{
"type": "BooleanQuery",
"description": "title:elasticsearch +date:[1577836800000 TO 1609459200000}",
"time_in_nanos": 12345678,
"breakdown": { ... },
"children": [
{
"type": "TermQuery",
"description": "title:elasticsearch",
"time_in_nanos": 123456,
"breakdown": { ... }
},
{
"type": "PointRangeQuery",
"description": "date:[1577836800000 TO 1609459200000}",
"time_in_nanos": 789012,
"breakdown": { ... }
}
]
}
],
"aggregations": [
{
"type": "DateHistogramAggregator",
"description": "date_histogram(field=timestamp,interval=1d)",
"time_in_nanos": 9876543,
"breakdown": { ... }
}
]
}
]
}
]
}
...
이 간단한 예에서 DateHistogramAggregator가 불균형적으로 높은 time_in_nanos를 보이면 집계가 병목 현상입니다.
느린 쿼리의 일반적인 원인 및 해결 전략
프로파일 API 결과 및 일반적인 클러스터 상태를 기반으로 한 일반적인 문제와 해결 방법은 다음과 같습니다.
1. 비효율적인 쿼리 설계
문제: 특정 쿼리 유형은 특히 대규모 데이터 세트에서 본질적으로 리소스를 많이 사용합니다.
wildcard,prefix,regexp쿼리: 많은 용어를 반복해야 하므로 매우 느릴 수 있습니다.script쿼리: 필터링이나 점수 매기기를 위해 모든 문서에서 스크립트를 실행하는 것은 매우 비용이 많이 듭니다.- 깊은 페이지 매김: 매우 큰 오프셋으로
from및size를 사용합니다. - 너무 많은
should절: 수백 또는 수천 개의should절이 있는 부울 쿼리는 매우 느려질 수 있습니다.
해결 단계:
- 대규모 필드에서 광범위한
wildcard/prefix/regexp쿼리 피하기:- 입력 중 검색의 경우 인덱스 시간에
completion suggesters또는n-grams를 사용하세요. - 정확한 접두사의 경우 목적에 맞게 구축된 접두사 필드,
index_prefixes또는 데이터와 일치하는keyword전략을 고려하세요.
- 입력 중 검색의 경우 인덱스 시간에
script쿼리 최소화: 논리를 수집(예: 전용 필드 추가)으로 이동하거나 표준 쿼리/집계로 처리할 수 있는지 재평가하세요.- 페이지 매김 최적화: 사용자 대상 깊은 페이지 매김의 경우 안정적인 정렬과 함께
search_after를 사용하세요. 대화형 검색 페이지가 아닌 일괄 추출 작업에는 스크롤 API를 사용하세요. should쿼리 리팩터링: 유사한 절을 결합하거나, 적절한 경우 클라이언트 측 필터링을 고려하세요.
2. 누락되거나 비효율적인 매핑
문제: 잘못된 필드 매핑은 Elasticsearch가 비용이 많이 드는 작업을 수행하도록 강제할 수 있습니다.
- 정확한 일치/정렬/집계에 사용되는 텍스트 필드:
text필드는 분석되고 토큰화되어 정확한 일치를 비효율적으로 만듭니다. 정렬이나 집계를 위해서는fielddata가 필요하며, 이는 힙을 많이 사용합니다. - 과도한 인덱싱: 검색되지 않거나 불필요하게 분석되는 필드를 인덱싱합니다.
해결 단계:
- 정확한 일치, 정렬 및 집계에는
keyword사용: 정확한 일치, 필터링, 정렬 또는 집계가 필요한 필드에는keyword필드 유형을 사용하세요. multi-fields활용: 동일한 데이터를 다른 방식으로 인덱싱합니다(예: 전체 텍스트 검색용title.text및 정확한 일치 및 집계용title.keyword).- 사용되지 않는 검색 가능 필드에 대해
index비활성화: 필드가 표시만 되고 검색되지 않는 경우"index": false를 고려하세요._source비활성화는 업데이트, 재인덱싱, 디버깅 및 복구 워크플로에 영향을 미치므로 주의하세요.
3. 샤딩 문제
문제: 부적절한 수 또는 크기의 샤드는 불균일한 로드 분산 또는 과도한 오버헤드를 초래할 수 있습니다.
- 너무 많은 작은 샤드: 각 샤드에는 오버헤드가 있습니다. 너무 많은 작은 샤드는 마스터 노드에 부담을 주고, 힙 사용량을 증가시키며, 요청 수를 증가시켜 검색 속도를 저하시킬 수 있습니다.
- 너무 적은 큰 샤드: 검색 중 병렬 처리를 제한하고 노드에 "핫스팟"을 생성할 수 있습니다.
해결 단계:
- 최적의 샤드 크기: 샤드 크기를 10GB에서 50GB 사이로 목표하세요. 시간 기반 인덱스(예:
logs-YYYY.MM.DD)를 사용하고 롤오버 인덱스를 사용하여 샤드 증가를 관리하세요. - 재인덱싱 및 축소/분할:
_reindex,_split또는_shrinkAPI를 사용하여 기존 인덱스의 샤드를 통합하거나 크기를 조정하세요. - 샤드 분포 모니터링: 샤드가 데이터 노드에 고르게 분포되어 있는지 확인하세요.
4. 힙 및 JVM 설정
문제: 불충분한 JVM 힙 메모리 또는 최적이 아닌 가비지 수집은 빈번한 일시 중지와 성능 저하를 유발할 수 있습니다.
해결 단계:
- 충분한 힙 할당:
Xms와Xmx를 동일한 값으로 설정하세요. 일반적인 시작점은 압축된 일반 객체 포인터 임계값(종종 약 30GB 미만) 미만을 유지하면서 물리적 RAM의 절반을 넘지 않는 것입니다. - JVM 가비지 수집 모니터링:
GET _nodes/stats/jvm?pretty또는 전용 모니터링 도구를 사용하여 GC 시간을 확인하세요. 빈번하거나 긴 GC 일시 중지는 힙 압력을 나타냅니다.
5. 디스크 I/O 및 네트워크 지연 시간
문제: 느린 스토리지 또는 네트워크 병목 현상은 쿼리 지연 시간의 근본적인 원인이 될 수 있습니다.
해결 단계:
- 빠른 스토리지 사용: Elasticsearch 데이터 노드에는 SSD를 적극 권장합니다. 고성능 사용 사례의 경우 NVMe SSD가 더 좋습니다.
- 충분한 네트워크 대역폭 보장: 대규모 클러스터 또는 많이 인덱싱/쿼리되는 환경의 경우 네트워크 처리량이 중요합니다.
6. Fielddata 사용
문제: 정렬 또는 집계를 위해 text 필드에서 fielddata를 사용하면 엄청난 양의 힙을 소비하고 OutOfMemoryError 예외로 이어질 수 있습니다.
해결 단계:
text필드에서fielddata: true피하기: 이 설정은 이유가 있어text필드에서 기본적으로 비활성화되어 있습니다. 대신multi-fields를 사용하여 정렬/집계를 위한keyword하위 필드를 만드세요.
쿼리 최적화를 위한 모범 사례
느린 쿼리를 사전에 방지하려면:
- 점수 매기기가 필요 없는 조건에는
filter컨텍스트 선호:range,term또는exists조건에 관련성 점수가 필요하지 않은 경우bool쿼리의filter절에 배치하세요. 필터는 점수 매기기를 건너뛰고 Elasticsearch가 최적화하기 더 쉬운 경우가 많습니다. - 필터링에는
constant_score쿼리 사용: 이는 캐싱 이점을 위해 필터 컨텍스트에서 실행하려는query(filter가 아님)가 있을 때 유용합니다. - 적합한 경우 캐시 재사용을 위해 설계: Elasticsearch는 자동으로 캐시할 항목을 결정합니다. 안정적인 데이터에 대한 반복 필터는 지속적으로 값이 변경되는 고유한 일회성 필터보다 더 많은 이점을 얻습니다.
indices.query.bool.max_clause_count조정: 많은should절로 기본 제한(1024)에 도달하는 경우 쿼리 재설계 또는 이 설정 증가(주의하여)를 고려하세요.- 정기적인 모니터링: 클러스터 상태, 노드 리소스, 느린 로그 및 쿼리 성능을 지속적으로 모니터링하여 문제를 조기에 발견하세요.
- 테스트, 테스트, 테스트: 프로덕션에 배포하기 전에 항상 스테이징 환경에서 실제적인 데이터 볼륨과 워크로드에 대해 쿼리 성능을 테스트하세요.
최상의 쿼리 수정은 일반적으로 증거에서 확인할 수 있습니다. 느린 로그는 요청 형태를 보여줍니다. 프로파일 API는 쿼리의 어떤 부분이 시간을 소모하는지 보여줍니다. 노드 통계는 쿼리가 실행될 때 클러스터에 충분한 CPU, 힙 및 디스크 I/O가 있었는지 여부를 보여줍니다. 설정을 변경하기 전에 이들을 함께 고려하면 실제 문제가 계속 실행되는 동안 증상을 조정하는 것을 피할 수 있습니다.