일반적인 Elasticsearch 성능 병목 현상 문제 해결

인덱싱, 검색, 힙, 스토리지 및 샤드 설계에서 Elasticsearch 성능 병목 현상을 찾기 위한 실용적인 워크플로우입니다.

일반적인 Elasticsearch 성능 병목 현상 문제 해결

Elasticsearch 성능 병목 현상을 해결할 때는 첫 번째로 떠오르는 쉬운 이론을 거부하는 것이 가장 효과적입니다. 느린 대시보드는 잘못된 쿼리 때문일 수 있지만, 핫 샤드, 포화된 디스크, 힙 문제, 매핑 실수 또는 I/O를 두고 경쟁하는 복구 프로세스 때문일 수도 있습니다. 증거부터 시작한 다음 범위를 좁히세요.

저는 보통 문제를 세 부분으로 나눕니다: 무엇이 느린지, 어디서 느린지, 그리고 무엇이 변경되었는지입니다. "Elasticsearch가 느리다"는 실행 가능한 정보가 아닙니다. "logs-prod-*에 대한 검색 지연 시간이 어제 매핑 변경 후 두 데이터 노드에서 주로 두 배로 증가했습니다"라는 정보가 작업할 수 있는 출발점을 제공합니다.

성능 문제 진단

구체적인 솔루션을 살펴보기 전에 성능 문제를 진단하기 위한 도구와 방법을 갖추는 것이 필수적입니다. Elasticsearch는 이 과정에서 매우 유용한 여러 API와 메트릭을 제공합니다.

주요 도구 및 메트릭:

  • 클러스터 상태 API (_cluster/health): 클러스터 상태(녹색, 노란색, 빨간색), 노드 수, 샤드 수 및 보류 중인 작업에 대한 개요를 제공합니다. 보류 중인 작업 수가 많으면 인덱싱 또는 복구 문제를 나타낼 수 있습니다.
  • 노드 통계 API (_nodes/stats): CPU 사용량, 메모리, 디스크 I/O, 네트워크 트래픽 및 JVM 힙 사용량을 포함한 각 노드의 상세 통계를 제공합니다. 리소스가 제한된 노드를 식별하는 데 중요합니다.
  • 인덱스 통계 API (_stats): 인덱싱 속도, 검색 속도 및 캐시 사용량과 같은 개별 인덱스의 통계를 제공합니다. 문제가 있는 인덱스를 정확히 찾아내는 데 도움이 됩니다.
  • 슬로우 로그: Elasticsearch는 느린 인덱싱 및 검색 요청을 기록할 수 있습니다. 슬로우 로그 임계값은 인덱스 설정이므로, 전체 클러스터를 로그 생성기로 만들지 않고 하나의 시끄러운 인덱스에 적용할 수 있습니다.
    • 인덱싱 슬로우 로그: 대량 쓰기가 일시 중지되거나 수집 지연 시간이 증가할 때 유용합니다.
    • 검색 슬로우 로그: 지연 시간 차트가 아닌 실제 요청 패턴이 필요할 때 유용합니다.
  • 모니터링 도구: Kibana의 모니터링 UI, Elasticsearch Exporter가 있는 Prometheus 또는 상용 APM 도구와 같은 솔루션은 심층 분석을 위한 대시보드와 과거 데이터를 제공합니다.

일반적인 병목 현상 및 해결 방법

1. 느린 인덱싱

느린 인덱싱은 네트워크 지연 시간, 디스크 I/O 병목 현상, 리소스 부족, 비효율적인 매핑 또는 최적이 아닌 대량 API 사용을 포함한 다양한 요인으로 인해 발생할 수 있습니다.

원인 및 해결 방법:
  • 디스크 I/O 포화: Elasticsearch는 인덱싱을 위해 빠른 디스크 I/O에 크게 의존합니다. SSD를 적극 권장합니다.

    • 진단: _nodes/stats 또는 OS 수준 도구를 사용하여 디스크 읽기/쓰기 IOPS 및 처리량을 모니터링합니다. 높은 큐 깊이를 찾으십시오.
    • 해결 방법: 더 빠른 스토리지(SSD)로 업그레이드하고, 샤드를 더 많은 노드에 분산시키거나, 노드당 I/O를 줄이기 위해 샤드 전략을 최적화합니다.
  • JVM 힙 압력: JVM 힙이 지속적으로 압력을 받으면 가비지 수집이 심각한 병목 현상이 되어 인덱싱을 포함한 모든 작업이 느려질 수 있습니다.

    • 진단: Kibana 모니터링 또는 _nodes/stats에서 JVM 힙 사용량을 모니터링합니다. 힙 사용량이 높고 빈번하고 긴 가비지 수집 일시 중지는 위험 신호입니다.
    • 해결 방법: JVM 힙 크기를 늘리되(시스템 RAM의 50%를 초과하지 않고 30.5GB를 초과하지 않음), 문서 크기를 줄이기 위해 매핑을 최적화하거나, 부하를 분산하기 위해 노드를 추가합니다.
  • 비효율적인 매핑: 지나치게 복잡한 매핑, 많은 새 필드가 생성되는 동적 매핑 또는 잘못된 데이터 유형은 인덱싱 오버헤드를 증가시킬 수 있습니다.

    • 진단: 인덱스 매핑(_mapping API)을 분석합니다. 중첩된 객체, 많은 수의 필드 또는 불필요하게 인덱싱된 필드를 찾으십시오.
    • 해결 방법: 적절한 데이터 유형으로 명시적 매핑을 정의합니다. 해당되는 경우 dynamic: false 또는 dynamic: strict를 사용합니다. 필수적이지 않으면 깊게 중첩된 구조를 피하십시오.
  • 네트워크 지연 시간: 노드 간 또는 클라이언트와 클러스터 간의 높은 지연 시간은 대량 인덱싱 요청을 느리게 할 수 있습니다.

    • 진단: 클라이언트/노드 간의 네트워크 지연 시간을 측정합니다. 대량 API 응답 시간을 분석합니다.
    • 해결 방법: 클러스터 노드를 지연 시간이 짧은 사설 네트워크에 유지하고, 가능하면 대량 클라이언트를 클러스터 가까이에 배치하며, 불필요한 교차 리전 트래픽을 줄입니다. 요청 캐시 설정은 네트워크 지연 시간을 해결하지 못합니다.
  • 최적이 아닌 대량 API 사용: 대량 요청 대신 개별 요청을 보내거나, 지나치게 크거나 작은 대량 요청을 보내는 것은 비효율적일 수 있습니다.

    • 진단: 대량 인덱싱의 처리량을 모니터링합니다. 대량 요청의 크기를 분석합니다.
    • 해결 방법: 모든 인덱싱 작업에 대량 API를 사용합니다. 대량 크기를 실험하여(일반적으로 대량 요청당 5-15MB가 좋은 시작점입니다) 처리량과 지연 시간 간의 최적 균형을 찾습니다. 대량 요청이 적절하게 배치되었는지 확인합니다.
  • 트랜스로그 내구성: index.translog.durability 설정은 트랜잭션 로그가 디스크에 플러시되는 빈도를 제어합니다. request(기본값)는 더 안전하지만 async에 비해 성능에 영향을 줄 수 있습니다.

    • 진단: 이것은 구성 설정입니다.
    • 해결 방법: 최대 인덱싱 처리량을 위해 async 내구성을 고려하십시오. 그러나 플러시 사이에 노드가 충돌할 경우 데이터 손실 위험이 증가한다는 점을 인식하십시오.

2. 느린 쿼리

쿼리 성능은 샤드 크기, 쿼리 복잡성, 캐싱 및 기본 데이터 구조의 효율성에 의해 영향을 받습니다.

원인 및 해결 방법:
  • 큰 샤드: 너무 큰 샤드는 Elasticsearch가 더 많은 데이터를 검색하고 더 많은 세그먼트의 결과를 병합해야 하므로 쿼리를 느리게 할 수 있습니다.

    • 진단: _cat/shards 또는 _all/settings?pretty를 사용하여 샤드 크기를 확인합니다.
    • 해결 방법: 샤드 크기를 10GB에서 50GB 사이로 목표로 합니다. 더 작은 샤드로 새 인덱스에 데이터를 다시 인덱싱하거나 ILM(인덱스 수명 주기 관리)을 사용하여 시간이 지남에 따라 샤드 크기를 관리하는 것을 고려하십시오.
  • 너무 많은 샤드: 과도하게 많은 수의 작은 샤드는 특히 검색 중에 클러스터에 높은 오버헤드를 초래할 수 있습니다. 각 샤드는 관리를 위한 리소스가 필요합니다.

    • 진단: _cat/shards를 사용하여 노드 및 인덱스당 총 샤드 수를 계산합니다.
    • 해결 방법: 가능하면 인덱스를 통합합니다. 데이터 모델을 최적화하여 인덱스 수와 총 샤드 수를 줄입니다. 시계열 데이터의 경우 ILM이 샤드 수를 관리하는 데 도움이 될 수 있습니다.
  • 비효율적인 쿼리: 복잡한 쿼리, 과도한 스크립팅을 포함하는 쿼리, 용어 시작 부분의 와일드카드 검색 또는 정규식은 매우 리소스 집약적일 수 있습니다.

    • 진단: 프로파일 API(_search?profile=true)를 사용하여 쿼리 실행 시간을 분석하고 느린 부분을 식별합니다. 슬로우 로그를 분석합니다.
    • 해결 방법: 쿼리를 단순화합니다. 선행 와일드카드와 값비싼 정규식을 피하십시오. 가능하면 정확히 일치하는 항목에 대해 match 대신 term 쿼리를 사용합니다. 자동 완성 제안을 위해 search_as_you_type 또는 completion suggester 사용을 고려하십시오. 필터 절을 최적화합니다(점수 매기지 않는 쿼리의 경우 query 컨텍스트 대신 filter 컨텍스트 사용).
  • 캐싱 부족: 불충분하거나 비효율적인 캐싱은 반복적인 계산 및 데이터 검색으로 이어질 수 있습니다.

    • 진단: _nodes/stats/indices/query_cache_nodes/stats/indices/request_cache를 사용하여 쿼리 캐시 및 요청 캐시의 적중률을 모니터링합니다.
    • 해결 방법: 적절한 캐싱이 활성화되었는지 확인합니다. 필터 캐시(쿼리 캐시의 일부)는 반복되는 필터 쿼리에 특히 중요합니다. 자주 실행되는 동일한 쿼리의 경우 요청 캐시를 활성화하는 것을 고려하십시오.
  • 세그먼트 병합 오버헤드: Elasticsearch는 백그라운드에서 작은 세그먼트를 더 큰 세그먼트로 병합합니다. 이 프로세스는 I/O 및 CPU 리소스를 소비하며, 이는 실시간 쿼리 성능에 영향을 줄 수 있습니다.

    • 진단: _cat/segments를 사용하여 샤드당 세그먼트 수를 모니터링합니다.
    • 해결 방법: 병합 설정을 함부로 변경하지 마십시오. 대규모 백필 중에는 새로 고침 빈도를 줄이고, 대량 동시성을 제어하며, 병합 제한 및 디스크 I/O를 주시하십시오. 강제 병합은 일반적으로 활성 핫 인덱스가 아닌 읽기 전용 인덱스용입니다.

3. 리소스 경합 (CPU, 메모리, 네트워크)

리소스 경합은 인덱싱 및 쿼리 성능 저하 모두에서 나타날 수 있는 광범위한 범주입니다.

원인 및 해결 방법:
  • CPU 과부하: 높은 CPU 사용량은 복잡한 쿼리, 집약적인 집계, 너무 많은 인덱싱 작업 또는 과도한 가비지 수집으로 인해 발생할 수 있습니다.

    • 진단: 노드별 CPU 사용량을 모니터링합니다(_nodes/stats). 가장 많은 CPU를 소비하는 작업(예: 검색, 인덱싱, JVM GC)을 식별합니다.
    • 해결 방법: 쿼리 및 집계를 최적화합니다. 더 많은 노드에 부하를 분산합니다. CPU를 압도하는 경우 인덱싱 속도를 줄입니다. GC 오버헤드를 최소화하기 위해 적절한 JVM 힙 설정을 보장합니다.
  • 메모리 문제 (JVM 힙 및 시스템 메모리): 불충분한 JVM 힙은 빈번한 GC로 이어집니다. 시스템 메모리가 부족하면 스와핑이 발생하여 성능이 크게 저하됩니다.

    • 진단: 각 노드의 JVM 힙 사용량과 전체 시스템 메모리(RAM, 스왑)를 모니터링합니다.
    • 해결 방법: 충분한 JVM 힙을 할당합니다(예: 시스템 RAM의 50%, 최대 30.5GB). 충분한 여유 시스템 메모리를 확보하여 스와핑을 방지합니다. 노드를 더 추가하거나 특정 역할(마스터, 데이터, 수집)에 전용 노드를 사용하는 것을 고려하십시오.
  • 네트워크 병목 현상: 높은 네트워크 트래픽은 노드 간 통신, 복제 및 클라이언트 요청을 느리게 할 수 있습니다.

    • 진단: 노드 및 클라이언트 간의 네트워크 대역폭 사용량과 지연 시간을 모니터링합니다.
    • 해결 방법: 네트워크 인프라를 최적화합니다. 불필요한 데이터 전송을 줄입니다. 최적의 샤드 할당 및 복제 설정을 보장합니다.
  • 디스크 I/O 포화: 인덱싱에서 언급했듯이 디스크에서 데이터를 읽을 때 쿼리 성능에도 영향을 미칩니다.

    • 진단: 디스크 I/O 메트릭을 모니터링합니다.
    • 해결 방법: 더 빠른 스토리지로 업그레이드하고, 더 많은 노드에 데이터를 분산시키거나, 읽는 데이터 양을 줄이기 위해 쿼리를 최적화합니다.

성능 튜닝을 위한 모범 사례

  • 지속적으로 모니터링: 성능 튜닝은 지속적인 프로세스입니다. 클러스터 상태와 리소스 사용률을 정기적으로 모니터링하십시오.
  • 매핑 최적화: 데이터에 맞게 명시적이고 효율적인 매핑을 정의합니다. 불필요한 필드나 인덱싱을 피하십시오.
  • 샤드 전략: 최적의 샤드 크기(10-50GB)를 목표로 하고 너무 많거나 너무 적은 샤드를 피하십시오.
  • 대량 API 사용: 인덱싱에는 대량 API를 사용하고, 독립적인 검색을 번들로 묶어야 할 때는 다중 검색 API를 사용하십시오.
  • JVM 힙 튜닝: 충분한 힙을 할당하되, 과도하게 할당하지 마십시오. 스와핑을 피하십시오.
  • 쿼리 성능 이해: 쿼리를 프로파일링하고, 단순화하며, 필터 컨텍스트를 활용하십시오.
  • 캐싱 활용: 쿼리 및 요청 캐시가 효과적으로 사용되는지 확인하십시오.
  • 하드웨어: 스토리지에 SSD를 사용하고 적절한 CPU와 RAM을 확보하십시오.
  • 전용 노드: 워크로드를 격리하기 위해 마스터, 데이터 및 수집 역할에 전용 노드 사용을 고려하십시오.
  • 인덱스 수명 주기 관리 (ILM): 시계열 데이터의 경우 ILM은 인덱스 관리, 샤드 롤오버 및 최종적으로 오래된 데이터 삭제에 필수적이며, 이는 샤드 수와 크기를 제어하는 데 도움이 됩니다.

병목 현상을 발견하면 이를 직접 해결하는 가장 작은 변경을 수행하십시오. 클러스터에 실제로 용량이 부족할 때 노드를 추가하십시오. 힙이 낭비될 때 매핑을 수정하십시오. 프로파일 출력이 비용이 많이 드는 절을 가리킬 때 쿼리를 다시 작성하십시오. 한 노드가 분산되어야 할 작업을 수행할 때 샤드 전략을 조정하십시오. 이러한 규율은 성능 작업이 관련 없는 튜닝 노브의 더미가 되는 것을 방지합니다.