Elasticsearch 검색 속도 3초에서 200ms로: 실전 샤딩 전략과 ILM 적용기

최근 운영 중인 로그 분석 클러스터에서 심각한 성능 저하가 발생했습니다. 평소 200ms 내외였던 검색 레이턴시가 트래픽이 몰리는 피크 시간대에 3초 이상 치솟았고, 심한 경우 타임아웃(Timeout)이 발생해 Kibana 대시보드가 먹통이 되는 현상이 반복되었습니다. 인프라 환경은 AWS r5.2xlarge(64GB RAM) 노드 3대로 구성되어 있었으나, 힙 메모리(Heap Memory) 사용률은 지속적으로 90%를 상회하며 'Stop-the-world' GC가 빈번하게 터지고 있었습니다. 단순히 노드를 늘리는 스케일 아웃(Scale-out)을 고려하기 전, 근본적인 원인이 잘못된 샤드(Shard) 설계데이터 수명주기 관리 부재에 있다는 것을 발견하고 이를 튜닝한 과정을 공유합니다.

샤딩 전략(Sharding Strategy) 실패 원인 분석

문제 해결을 위해 클러스터 상태를 점검하던 중 _cat/shards API를 통해 충격적인 사실을 확인했습니다. 하루에 생성되는 인덱스 크기는 약 100GB였는데, 샤드 개수는 디폴트 설정과 개발 편의성에 의해 과도하게 쪼개져 있었습니다. 결과적으로 클러스터 전체에 수만 개의 작은 샤드들이 흩어져 있는 '오버 샤딩(Oversharding)' 상태였습니다.

Elasticsearch에서 샤드는 Lucene 인덱스의 인스턴스입니다. 샤드가 너무 많으면 다음과 같은 문제가 발생합니다:

  • 힙 메모리 고갈: 각 샤드는 세그먼트 정보를 힙 메모리에 상주시켜야 합니다. 데이터가 적어도 샤드 자체가 메모리를 점유합니다.
  • 검색 성능 저하: 검색 요청 시 코디네이팅 노드(Coordinating Node)는 모든 샤드의 결과를 취합(Scatter-Gather)해야 하는데, 샤드가 많을수록 오버헤드가 커집니다.
증상 로그: [gc][young][84234][12] duration [2.3s], collections [1]/[2.8s], total [2.3s]/[1.5m], memory [32.1gb]->[31.8gb]/[32gb]
지속적인 Old GC 발생으로 노드가 응답하지 않음.

반대로 샤드가 너무 크면(예: 100GB 이상), 노드 장애 발생 시 복구(Recovery) 시간이 너무 오래 걸려 클러스터 상태가 Yellow나 Red에 머무는 시간이 길어집니다. 따라서 적절한 Elasticsearch 튜닝의 핵심은 '골디락스 존(Goldilocks Zone)'을 찾는 것입니다. 공식적으로 권장되는 샤드 크기는 10GB ~ 50GB 사이입니다.

초기 접근의 실패: 무작정 Reindex

처음에는 단순히 과거 데이터를 하나의 큰 인덱스로 합치는 Reindex 작업을 시도했습니다. 스크립트를 짜서 월 단위로 인덱스를 병합했는데, 이 과정에서 디스크 I/O가 폭발하여 실시간 로그 수집이 지연되는 2차 장애가 발생했습니다. 운영 중인 ELK 스택에서 대량의 I/O를 유발하는 작업은 반드시 트로틀링(Throttling)을 걸거나, 트래픽이 적은 새벽 시간에 수행해야 한다는 교훈을 얻었습니다.

해결책: ILM을 통한 Hot-Warm-Cold 아키텍처

수동 관리는 한계가 명확했습니다. 우리는 인덱스 수명주기 관리(ILM) 기능을 도입하여 데이터의 '온도'에 따라 저장소와 샤드 설정을 자동화하기로 결정했습니다. 이 전략은 비용 효율성을 높이면서 검색 속도를 최적화하는 검색 엔진 최적화 기술의 일환이기도 합니다.

다음은 우리가 적용한 ILM 정책의 핵심 로직입니다.

// PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50gb",       // 샤드 크기가 50GB에 도달하면 롤오버
            "max_age": "1d"           // 혹은 하루가 지나면 롤오버
          }
        }
      },
      "warm": {
        "min_age": "7d",              // 7일 후 Warm 단계로 이동
        "actions": {
          "forcemerge": {
            "max_num_segments": 1     // 세그먼트를 1개로 강제 병합하여 검색 속도 향상
          },
          "shrink": {
            "number_of_shards": 1     // 샤드 개수를 줄여 메모리 절약
          },
          "allocate": {
            "number_of_shards": 1,
            "require": {
              "data": "warm"          // Warm 노드로 이동 (HDD 등 저렴한 스토리지)
            }
          }
        }
      },
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}                // 30일 후 자동 삭제
        }
      }
    }
  }
}

위 설정에서 가장 중요한 부분은 forcemerge입니다. 로그 데이터는 한 번 기록되면 수정되지 않는(Immutable) 특성이 있습니다. Warm 단계로 넘어간 인덱스의 세그먼트를 1개로 강제 병합하면, 불필요한 삭제 마킹(Deleted Documents) 공간이 회수되고 검색 시 I/O 횟수가 획기적으로 줄어듭니다.

또한, rollover 설정을 통해 인덱스가 물리적인 크기(50GB)에 도달했을 때만 새로운 인덱스를 생성하도록 했습니다. 이는 날짜별로 데이터 양이 들쑥날쑥할 때(이벤트 기간 등) 샤드 크기를 균일하게 유지해주는 매우 강력한 전략입니다.

성능 검증 및 벤치마크

ILM 적용 및 샤드 재배치 작업이 완료된 후 일주일간의 모니터링 결과를 비교했습니다. 테스트 쿼리는 가장 빈번하게 사용되는 '지난 24시간 특정 에러 코드 집계' 쿼리를 기준으로 했습니다.

지표 (Metric) 최적화 전 (Legacy) 최적화 후 (Optimized) 개선율
평균 검색 레이턴시 3,200 ms 180 ms 약 17배 향상
힙 메모리 사용률 (Avg) 85% ~ 95% 40% ~ 50% 안정권 진입
Full GC 발생 빈도 일 10회 이상 주 1회 미만 90% 감소
샤드 개수 (Total) 4,200개 380개 관리 포인트 감소

수치상의 변화는 극적입니다. 특히 힙 메모리 사용률이 절반으로 줄어든 것은 오버 샤딩으로 낭비되던 세그먼트 메모리가 해소되었음을 의미합니다. 또한 Warm 노드에서의 검색 속도도 forcemerge 덕분에 크게 뒤처지지 않았습니다. 이는 비용이 비싼 NVMe SSD(Hot) 노드를 무한정 늘리지 않아도 됨을 증명합니다.

Elastic 공식 ILM 가이드 확인하기

주의사항 및 엣지 케이스

이 전략을 적용할 때 주의해야 할 Edge Case가 있습니다. 바로 '쓰기 작업이 멈추지 않는 인덱스'입니다. ILM의 Rollover는 기본적으로 새로 들어오는 데이터에 대해 수행되지만, 만약 과거 인덱스에 업데이트나 삭제 요청이 빈번하게 발생한다면 forcemerge를 수행해서는 안 됩니다. Force Merge된 큰 세그먼트는 이후 업데이트 시 막대한 I/O 비용을 발생시킵니다.

경고: Force Merge는 반드시 더 이상 데이터가 추가/수정되지 않는(Read-only) 인덱스에만 적용해야 합니다. 그렇지 않으면 백그라운드 병합 프로세스가 CPU를 점유하여 노드가 다운될 수 있습니다.

또한, 템플릿(Index Template) 설정 시 order 우선순위를 확실히 지정해야 합니다. 기존 레거시 템플릿과 충돌하여 ILM 정책이 적용되지 않은 채 인덱스가 생성되는 경우가 빈번하므로, _index_template API를 통해 시뮬레이션을 먼저 해보는 것이 좋습니다.

결론

Elasticsearch의 성능 문제는 대부분 하드웨어 스펙 부족이 아니라, 데이터 특성에 맞지 않는 샤드 배치와 수명주기 관리 부재에서 비롯됩니다. 이번 튜닝 경험을 통해 단순히 노드를 늘리는 것보다 샤딩 전략을 재수립하고 인덱스 수명주기 관리를 자동화하는 것이 훨씬 경제적이고 효과적이라는 것을 확인했습니다. 만약 여러분의 클러스터가 이유 없이 느려지고 있다면, 지금 바로 샤드 개수와 크기를 점검해 보시기 바랍니다.

Post a Comment