현대 애플리케이션의 발전은 실시간 데이터 처리 및 검색에 대한 증가하는 필요를 충족시키고 있으며, 이에 따라 확장성도 필요해졌습니다. 이러한 오픈 소스 분산 검색 및 분석 엔진 중 하나가 Elasticsearch로, 대량의 데이터 세트와 고속 쿼리를 처리하는 데 매우 효율적입니다. 그러나 Elasticsearch를 효과적으로 확장하는 과정은 미묘할 수 있으며, 이를 위해서는 그 뒤에 있는 아키텍처와 성능의 트레이드오프에 대한 올바른 이해가 필요합니다.
Elasticsearch의 분산 특성은 수평적으로 확장할 수 있게 하지만, 이는 데이터가 분산되고 쿼리가 제공되는 방식에 더 많은 복잡성을 도입합니다. Elasticsearch 확장과 관련된 이론적인 도전 중 하나는 그 고유한 분산 특성입니다. 대부분의 실제 시나리오에서는 독립 노드에서의 읽기가 샤드 클러스터에서의 읽기보다 항상 더 나은 성능을 보입니다. 이는 샤드 클러스터에서 데이터 소유권이 여러 노드에 분산되기 때문입니다. 즉, 각 쿼리는 여러 노드에 요청을 보내고, 결과를 조정 노드에서 집계한 후 결과를 반환해야 할 수 있습니다. 이 추가적인 네트워크 오버헤드는 데이터 접근이 간단한 단일 노드 아키텍처에 비해 쉽게 지연 시간을 증가시킵니다.
이와 관련하여, 샤드 클러스터는 Elasticsearch를 대규모 데이터 세트, 높은 트래픽 및 거의 실시간 인덱싱으로 확장하는 데 필수적입니다. 노드 간에 데이터를 분산시키는 것과 쿼리 지연 시간을 낮게 유지하는 것 사이의 미세한 균형을 아는 것이 최적의 성능을 달성하는 데 핵심입니다. 이어서 이 기사는 Elasticsearch 확장성의 이론적 측면, 클러스터 성능 최적화의 실용적인 전략, 그리고 실제 배포 경험에서 얻은 교훈을 다룹니다.
Swoo의 라이브 스트리밍 및 게임 앱에서는 Elasticsearch가 모든 검색의 중심이었으며, 사용자 수가 증가함에 따라 잘 작동했습니다. 동시 사용자 수가 150,000명을 초과하는 순간, 검색 페이지가 가끔 작동하지 않기 시작했고, Elasticsearch 클러스터에 병목 현상이 있음을 분명히 알 수 있었습니다. 이는 라이브 게임 환경에서는 용납될 수 없는 상황이 되었고, 우리는 일련의 최적화를 진행하여 마침내 검색 및 홈 페이지 경험을 안정화시켰습니다.
확장성을 위한 Elasticsearch 아키텍처 이해하기
Elasticsearch는 본래 분산 아키텍처를 지원하므로 시스템의 확장성이 매우 높지만, 동시에 전통적인 단일 노드 솔루션에 비해 복잡성이 증가합니다. Elasticsearch는 데이터를 인덱스로 나누고, 각 인덱스는 다시 샤드로 나뉩니다. 샤드는 Elasticsearch에서 데이터 저장 및 인덱싱의 기본 단위이며, 시스템은 클러스터의 여러 노드에 걸쳐 검색 작업을 분산하고 병렬 처리합니다.
일반적인 클러스터는 데이터 쿼리를 실행하는 데이터 노드의 여러 개를 포함하며, 각 노드는 데이터의 하위 집합을 호스팅합니다. 기본적으로 Elasticsearch는 데이터를 노드에 자동으로 분산할 수 있으므로 각 노드는 쿼리 부하의 일부만 실행합니다. 이렇게 하여 Elasticsearch는 수평적으로 확장됩니다: 노드를 추가하기만 하면 더 많은 데이터를 처리하고 더 많은 요청을 수행할 수 있습니다.
쿼리 성능과 확장성 간의 이러한 균형은 물론, Elasticsearch 클러스터를 설계할 때 고려해야 할 가장 중요한 사항 중 하나입니다. 특히 높은 쓰기 처리량과 낮은 읽기 대기 시간을 요구하는 애플리케이션의 경우 더욱 그렇습니다. 이러한 도전 과제는 클러스터의 신중한 구성과 다양한 최적화 기법의 조합을 요구합니다.
따라서 본질적으로, 우리 Elasticsearch 클러스터는 Swoo의 경우를 위해 몇 개의 데이터 노드와 세 개의 전용 마스터 노드를 가지고 있었습니다. 각 노드는 8코어 CPU와 16GB RAM에서 실행되었으며, 주로 게임 이벤트의 실시간 인덱싱 및 즉각적인 검색을 목표로 했습니다. 높은 동시성에서 작업하고 있기 때문에 노드 간의 최소 대기 시간을 보장하기 위해 상당한 네트워킹 대역폭을 할당해야 합니다.
확장 전략 계획하기
다시 말해, Elasticsearch를 효과적으로 확장하려면 현재 성능 지표 분석, 병목 현상 파악, 그리고 확장성에 대한 명확한 목표 설정이 필요합니다. 예를 들어, 쿼리 대기 시간, 처리량 및 클러스터 상태를 모니터링하면 클러스터의 한계를 이해하는 데 도움이 될 것입니다. 핫 샤드, CPU의 급증 및 메모리 문제를 식별함으로써 최적화를 위한 로드맵을 만들 수 있습니다.
Elasticsearch의 확장성과 관련하여 주의가 필요한 또 다른 중요한 활동은 용량 계획입니다. 디스크 사용량, 트래픽 패턴 및 데이터 보존 요구 사항을 추정하면 클러스터의 크기가 올바르게 조정됩니다. 일반적으로 수평 확장 (클러스터에 노드 추가)는 데이터와 트래픽 양 증가에 대한 가장 좋은 접근 방식입니다. 그러나 이 경우에는 수직 확장 – 개별 노드의 리소스를 업그레이드하는 것이 효과적일 수 있습니다.
우리의 예상에 따르면 활성 사용자 수가 매달 약 10-15% 성장할 것으로 보였으며, 각 사용자는 게임을 사용하는 과정에서 상당한 양의 이벤트 데이터를 생성했습니다. 이러한 예측을 바탕으로 우리는 클러스터가 동시 쿼리에서 건강한 증가를 유지하고 인덱스된 문서의 양이 증가할 것으로 기대했습니다. 따라서 우리는 더 많은 데이터 노드를 추가하여 수평으로 확장하는 것이 좋을지, 현재 노드를 업그레이드하여 수직으로 확장하는 것이 좋을지를 분석했습니다.
핵심 확장 기술
Elasticsearch 최적화는 시스템의 다양한 측면을 목표로 하는 여러 전략을 포함합니다. 이 중 가장 효과적인 기술에는 데이터 수집 최적화, 샤드 관리 및 메모리 사용 최적화가 포함됩니다.
주요 초점 영역은 데이터 수집입니다. Elasticsearch는 기본적으로 대량 인덱싱을 지원하므로, 한 번의 요청으로 매우 큰 문서 배치를 보낼 수 있습니다. 이는 오버헤드를 줄이고 일반적으로 인덱싱 프로세스를 가속화합니다. 두 번째로, 새로 고침 간격을 미세 조정하면 데이터 수집 속도에 큰 차이를 만들 수 있습니다. 기본 새로 고침 간격인 1초를 10초와 같은 더 높은 값으로 변경하면 클러스터에 대한 너무 빈번한 새로 고침의 부담이 줄어들고 쓰기가 더 빨라집니다.
Elasticsearch의 확장성 기능을 구성하는 다른 주요 이유는 샤드 관리입니다. Elasticsearch는 샤딩을 통해 수평적으로 확장할 수 있지만, 부적절한 샤드 크기는 실제로 성능 저하로 이어집니다. 샤드 수가 너무 많거나 너무 적으면 인덱싱 속도 및/또는 쿼리 성능이 저하됩니다. 최적의 성능을 위해 균형을 맞추는 것이 중요합니다.
물론, 메모리 관리 또한 Elasticsearch 확장에서 매우 중요한 요소입니다. JVM 힙 크기를 최적화하고, 필드 데이터 캐시를 구성하며, 쿼리 캐시를 활성화함으로써 리소스 소비를 줄이고 쿼리 성능을 개선할 수 있습니다. 메모리를 적절히 사용하고 적절한 캐싱 설정을 하면 메모리 부족 오류를 방지하고 가비지 컬렉션 오버헤드를 최소화할 수 있습니다.
Elasticsearch의 상당한 부하 원인은 실시간 게임 데이터의 지속적인 수집 때문이었습니다. 우리는 Bulk API를 통해 문서 배치를 이용하여 수집 파이프라인을 최적화했습니다. 특정 피크 로드 기간에는 배치 크기를 추가로 늘리고, 거의 실시간 인덱싱과 일반 클러스터 안정성 간의 적절한 균형을 위해 새로 고침 주기를 늘릴 수 있었습니다.
고급 확장 솔루션
규모가 거대해지면 Elasticsearch는 성능을 위해 더 고급 기술이 필요합니다. 그 중에서도 쿼리 최적화가 두드러집니다. 검색에 관련된 샤드의 수를 최소화하는 효율적인 쿼리를 작성함으로써 쿼리 대기 시간을 크게 줄일 수 있습니다. 예를 들어, 고객 ID나 제품 카테고리와 같은 키를 사용하여 쿼리를 특정 샤드로 라우팅하는 사용자 지정 라우팅을 수행할 수 있습니다. 이렇게 하면 Elasticsearch가 모든 샤드를 검색할 필요가 없어지므로, 사용되는 시간과 자원이 줄어듭니다.
ILM(인덱스 수명 주기 관리)은 데이터 세트가 노후화됨에 따라 Elasticsearch를 세밀하게 조정할 수 있는 또 다른 멋진 기능입니다. 핫-웜-콜드 아키텍처를 확장하여 가장 관련성이 높은 데이터를 더 빠르고 접근 가능한 저장소에 두면서, 오래된 데이터를 느리고 저렴한 저장소로 이동할 수 있습니다. 클러스터가 성장함에 따라 클러스터 성능을 뛰어나게 유지해 줍니다.
Elasticsearch 확장성에 대한 최종 논의 주제는 하드웨어 고려 사항입니다. 클러스터가 성장함에 따라 하드웨어가 증가된 부하에 적절하게 제공되도록 해야 합니다. 이는 노드에 효율적으로 운영할 수 있는 적절한 CPU, 메모리 및 디스크 I/O가 있는지 확인하는 것을 의미합니다. SSD 또는 NVMe 드라이브는 성능을 크게 향상시키며, 특히 높은 데이터 접근 속도를 요구하는 인덱싱 및 검색 작업에 유리합니다.
하드레슨 중 하나는 새 인덱스에 기본 샤드 수만 할당하는 것이 핫 스팟 문제를 일으킬 수 있다는 것이었습니다. 우리는 또한 여러 작은 샤드에 분산된 무거운 쓰기 인덱스를 통해 게임 플랫폼에서 발생하는 대부분의 실시간 업데이트로 인해 샤드가 과부하되지 않는 최적의 지점을 찾았습니다.
이론적 통찰력 및 최적화 트레이드오프
위의 실용적인 최적화 외에도 Elasticsearch 확장과 관련된 몇 가지 흥미로운 이론적 고려 사항이 있습니다. 한 가지 주요 통찰력은 분산 시스템 내에서의 확장성과 쿼리 성능 간의 트레이드오프입니다. 특히 샤드 클러스터는 수평 확장 가능하지만, 여러 노드의 결과를 결합해야 하기 때문에 쿼리 오버헤드가 증가한다는 점이 주목받고 있습니다. 이는 데이터가 로컬 머신에 있는 단일 노드와 대조적이며, 쿼리는 다른 노드와 ‘대화’하지 않고도 실행될 수 있습니다. 성능과 확장성을 균형 있게 유지해야 하는 Elasticsearch 클러스터를 설계할 경우 이 트레이드오프를 이해하는 것이 중요합니다.
Elasticsearch 확장의 또 다른 이론적 측면은 데이터 국소성의 개념입니다. preference=local
쿼리 매개변수를 사용하여 관련 샤드 데이터가 호스팅되는 로컬 노드에 대해서만 쿼리를 실행할 수 있습니다. 이는 노드 간 통신을 최소화하고 쿼리 대기 시간을 줄입니다. 이로 인해 데이터 복제 및 로드 밸런싱에서 발생하는 문제를 줄일 수도 있습니다. Elasticsearch 적응형 복제본 선택 알고리즘은 현재 조건에 따라 최상의 복제본 노드를 선택하여 쿼리 실행을 최적화하려고 합니다. 그러나 이는 반드시 쿼리의 가장 효과적인 실행을 가져오는 것은 아닙니다.
우리가 경험한 첫 번째 실패의 물결은 높은 JVM 힙 압력과 관련이 있습니다. Elasticsearch 서버는 초기에는 최소한의 힙 할당으로 실행되어, 높은 쿼리 부하 하에서 꽤 자주 가비지 컬렉션 사이클이 발생했습니다. 우리는 JVM을 조정하여 최소 및 최대 힙 크기를 8GB로 맞춤으로써 이 문제를 해결했습니다. 이렇게 하여 Elasticsearch는 힙에 과부하를 주지 않고 쿼리를 처리할 수 있는 충분한 공간을 확보했습니다.
일반적인 함정과 해결책
물론 Elasticsearch를 확장하는 것이 도전이 없는 것은 아닙니다. 피해야 할 일반적인 실수 중에는 불완전한 샤드 크기, 모니터링 부족, 캐싱에 대한 과도한 의존, 적절한 인덱스 수명 관리 사용 부족이 있습니다. 이러한 문제는 초기 단계에서 능동적인 구성 조정을 통해 진단하면 상당한 시간과 자원을 절약할 수 있습니다.
주요 함정 중 하나는 메모리 할당 및 샤드 관리와 관련하여 Elasticsearch의 기본 설정을 조정하지 않은 것입니다. 기본값은 중간 트래픽 조건에서는 잘 작동했지만 피크 트래픽에서는 무너졌습니다. 우리의 해결책은 다층적이었습니다: 힙 할당, 핫 인덱스 복제 및 반복 쿼리를 위한 단기 캐싱을 수정했습니다. 이 모든 조치는 전체 클러스터에서 반복적인 실패를 방지하고 타임아웃을 매우 유의미하게 줄였습니다.
실제 구현 가이드
Elasticsearch를 확장하려면 계획, 테스트, 배포 및 이후 유지 관리가 필요합니다. 따라서 좋은 관행, 구성 템플릿, 그리고 새로운 변경 사항을 생산 환경에 배포하기 전에 클러스터를 철저히 테스트하는 것이 장기적으로 안정적인 클러스터로 문제 없이 확장하는 데 도움이 될 것입니다.
- JVM 매개변수 미세 조정. 우리는 각 Elasticsearch 서버에서 Xms와 Xmx를 8GB로 설정하여 사용 가능한 시스템 메모리와 힙 요구 사항 간의 균형을 맞추었습니다.
- 샤드 분포 최적화. 대형 인덱스는 핫스팟을 방지하고 가장 활성화된 인덱스에 대한 복제본의 확장을 위해 더 작은 크기의 샤드로 분할되었습니다. 이를 통해 쿼리 트래픽이 클러스터 전반에 고르게 분포되었습니다.
- 짧은 TTL 캐싱 활성화. 우리는 홈페이지에서 자주 사용되는 정적 쿼리에 1초 캐시 윈도우를 적용했으며, 이를 통해 반복 요청에 대한 Elasticsearch의 부하가 크게 줄어들고 실시간 응답성을 잃지 않음을 확인했습니다.
- 모니터링 및 반복. 우리는 Kibana를 사용하여 샤드 상태, JVM 성능 및 쿼리 지연 시간을 실시간으로 관찰하기 위한 맞춤형 대시보드를 만들었습니다. 이러한 지표를 바탕으로 지속적으로 미세 조정을 진행했습니다.
미래 계획 또는 기술 스택 확장
또한 성장하기 위해 자주 접근하지 않는 데이터를 더 저렴한 노드로 이동시켜 핫-웜-콜드 인덱스 생애 주기를 관리하고 실시간 인덱싱 및 쿼리를 위해 상위 하드웨어를 유지하는 방안을 모색하고자 합니다. 우리는 사용자 경험에 영향을 미치기 전에 비정상적인 쿼리의 급증이나 지연을 사전에 파악하기 위해 고급 모니터링 솔루션과 AI 기반 이상 탐지 시스템을 검토하고 있습니다.
결론
Elasticsearch의 확장은 아키텍처에 대한 이해, 신중한 계획, 그리고 모범 사례를 수립하는 것을 필요로 합니다. Elasticsearch가 샤딩을 통해 수평적으로 확장될 수 있는 만큼, 최적의 성능을 위해 반드시 관리해야 하는 여러 가지 도전 과제가 존재합니다. 적절한 데이터 수집, 샤드 관리, 메모리 사용, 쿼리 최적화를 통해 Elasticsearch 클러스터의 높은 확장성과 성능을 보장할 수 있습니다.
우리의 경우 Solr에서 Elasticsearch로의 마이그레이션이 필요했으며, 기술적인 질문 외에도 분산 시스템에서 실제로 숨겨져 있는 이론적인 어려움을 이해하는 것이 중요한 기여가 되었습니다. 이러한 신중한 조정과 창의적인 문제 해결은 대규모 멀티 벤더 단일 Elasticsearch 클러스터를 성장시킬 수 있게 하며, 이는 하루에 수백만 개의 쿼리를 처리하며 응답 시간을 대폭 단축시켜 쿼리 성능을 향상시키는 것을 목표로 하고 있습니다. 이론적 요소와 실용적 요소가 결합되어 Elasticsearch 배포를 확장하여 장기적인 성공을 보장합니다.
Source:
https://dzone.com/articles/how-to-scale-elasticsearch-to-solve-scalability-issues