엔지니어링

내가 운영하는 Elasticsearch 클러스터에 얼마나 많은 샤드가 필요할까?

편집자 노트: "힙 메모리의 GB당 20 샤드 이하를 목표로 하는 것"에 대한 경험 법칙은 버전 8.3에서 더 이상 사용되지 않습니다. 이 블로그는 새로운 권장 사항을 반영하도록 업데이트되었습니다.

Elasticsearch는 다양한 사용 사례를 지원하고 데이터 구성 및 복제 전략에 대한 뛰어난 유연성을 제공하는 매우 다재다능한 플랫폼입니다. 그러나 이러한 유연성으로 인해 특히 Elastic Stack을 처음 사용하는 경우 데이터를 인덱스와 샤드로 가장 잘 구성하는 방법을 사전에 결정하기가 어려울 수 있습니다. 충분한 이해 없이 이루어진 차선의 선택이 처음 시작할 때 꼭 문제를 일으키는 것은 아니지만, 시간이 지나면서 데이터 볼륨이 증가함에 따라 성능 문제가 발생할 수 있습니다. 클러스터가 더 많은 데이터를 보유할수록, 때때로 많은 양의 데이터를 재색인해야 하기 때문에 문제를 해결하는 것도 더 어려워집니다.

성능 문제를 겪고 있는 사용자의 경우, 성능 문제가 데이터 색인 방법 및 클러스터의 샤드 개수 관련 문제로 거슬러 올라가게 되는 경우가 드물지 않습니다. 이는 특히 멀티테넌시 및/또는 시계열 인덱스의 사용과 관련된 사용 사례에 적용됩니다. 이벤트나 모임에서 직접 만나서 또는 포럼을 통해 사용자와 이 문제를 논의할 때, 가장 흔히 받게 되는 질문 몇 가지는 "얼마나 많은 샤드로 구성해야 하나요?"와 "샤드의 최대 크기는 어떻게 되나요?"입니다.

이 블로그 포스트는 이러한 질문에 답하고, 시계열 인덱스(로깅, 보안 분석 등)를 한곳에서 사용하는 것과 관련된 사용 사례를 위해 실용적인 지침을 제공해 드리기 위한 것입니다.

샤드란 무엇인가?

시작하기에 앞서, 앞으로 사용할 몇 가지 사실과 용어에 대해서 알아보겠습니다.

Elasticsearch의 데이터는 인덱스로 구성됩니다. 각 인덱스는 하나 이상의 샤드로 이루어집니다. 각 샤드는 Lucene 인덱스의 인스턴스이며, Elasticsearch 클러스터에 있는 데이터의 하위 집합에 대한 쿼리를 색인하고 처리하는 자체 포함 검색 엔진이라고 생각하실 수 있습니다.

데이터가 샤드에 기록되면 디스크의 변경 불가능한 새 Lucene 세그먼트에 주기적으로 게시되며, 이때 쿼리 작업에 사용할 수 있게 됩니다. 이를 새로 고침이라고 합니다. 이 기능의 작동 방법은 Elasticsearch: 최종 가이드(Elasticsearch: the Definitive Guide)에 자세히 설명되어 있습니다.

세그먼트의 개수가 증가함에 따라, 이러한 세그먼트는 주기적으로 더 큰 세그먼트로 통합됩니다. 이 프로세스를 병합이라고 합니다. 모든 세그먼트는 변경 불가능하므로, 이것은 대체 세그먼트를 삭제하기 전에 병합된 새 세그먼트를 만들어야 하기 때문에 일반적으로 색인 중에 사용되는 디스크 공간이 변동된다는 뜻입니다. 병합은 특히 디스크 I/O와 관련하여 상당히 리소스 집약적일 수 있습니다.

샤드는 Elasticsearch가 클러스터에서 데이터를 배포하는 단위입니다. 데이터 균형 다시 맞추기 작업을 할 때(예: 장애 발생 후) Elasticsearch가 샤드를 빠르게 이동할 수 있는 속도는 네트워크 및 디스크 성능은 물론 샤드의 크기와 개수에 따라 달라집니다.

팁: 크기가 아주 큰 샤드는 사용하지 마세요. 클러스터의 장애 복구 기능에 부정적인 영향을 미칠 수 있습니다. 샤드의 크기에 대한 제한은 없지만, 50GB의 샤드 크기는 다양한 사용 사례에 적용되는 한계로 종종 인용됩니다.

보존 기간별 인덱스

세그먼트는 변경 불가능하므로, 도큐먼트를 업데이트하려면 먼저 Elasticsearch가 기존 도큐먼트를 찾은 다음, 삭제됨으로 표시하고 업데이트된 버전을 추가해야 합니다. 또한 도큐먼트를 삭제하려면 도큐먼트를 찾아 삭제됨으로 표시해야 합니다. 이러한 이유로, 삭제된 도큐먼트는 병합될 때까지 디스크 공간과 일부 시스템 리소스를 계속 묶어두게 되기 때문에 시스템 리소스가 많이 소모될 수 있습니다.

Elasticsearch를 통해 모든 레코드를 개별적으로 삭제할 필요 없이 파일 시스템에서 직접 전체 인덱스를 매우 효율적으로 삭제할 수 있습니다. 이것은 Elasticsearch에서 데이터를 삭제하는 가장 효율적인 방법입니다.


팁: 가능하면 시계열 인덱스를 사용하여 데이터 보존을 관리하세요. 보존 기간을 기준으로 데이터를 인덱스로 그룹화하세요. 또한 시계열 인덱스는 시간이 지남에 따라 프라이머리 샤드와 복제본의 개수를 쉽게 조정할 수 있게 해주며, 다음 인덱스 생성을 위해 이를 쉽게 변경할 수도 있습니다. 따라서 변화하는 데이터 볼륨 및 요구사항에 쉽게 적응할 수 있습니다.


인덱스와 샤드는 무료가 아닌가요?

각 Elasticsearch 인덱스에 대해, 매핑 및 상태에 대한 정보가 클러스터 상태에 저장됩니다. 이것은 빠른 접근을 위해 메모리에 보관됩니다. 따라서 클러스터에 많은 수의 인덱스와 샤드가 있으면, 특히 매핑이 큰 경우, 크기가 큰 클러스터 상태가 될 수 있습니다. 클러스터 전체에 변경 사항이 배포되기 전에 일관성을 보장하기 위해 모든 업데이트가 단일 스레드를 통해 수행되어야 하므로 이 경우 업데이트 속도가 느려질 수 있습니다.


팁: 인덱스 개수를 줄이고 대규모로 확장되는 매핑을 방지하려면, 데이터 출처에 따라 별도의 인덱스로 분할하기보다는 유사한 구조의 데이터를 동일한 인덱스에 저장하는 것이 좋습니다. 각 개별 인덱스의 매핑 크기 및 인덱스 개수와 샤드 개수 간의 균형을 잘 맞추는 것이 중요합니다. 클러스터 상태는 모든 노드(마스터 포함)의 힙에 로드되고 힙의 양은 인덱스 개수, 인덱스당 필드 개수 및 샤드에 정비례하므로 마스터 노드의 힙 사용량을 모니터링하여 크기가 적절한지 확인하는 것도 중요합니다.


각 샤드에는 메모리에 보관하고 힙 공간을 사용해야 하는 데이터가 있습니다. 여기에는 데이터가 디스크에 있는 위치를 정의하기 위해 샤드 레벨뿐만 아니라 세그먼트 레벨에서도 정보를 유지하는 데이터 구조가 포함됩니다. 이러한 데이터 구조의 크기는 고정되지 않으며 사용 사례에 따라 달라집니다.

그러나 세그먼트 관련 오버헤드의 한 가지 중요한 특성은 세그먼트의 크기에 엄격하게 비례하지 않는다는 것입니다. 즉, 더 큰 세그먼트는 더 작은 세그먼트에 비해 데이터 볼륨당 오버헤드가 적습니다. 차이가 상당할 수 있습니다.

노드당 가능한 많은 데이터를 저장할 수 있으려면 힙 사용량을 관리하고 오버헤드를 최대한 줄이는 것이 중요합니다. 노드가 더 많은 힙 공간을 가질수록 처리할 수 있는 데이터와 샤드가 늘어납니다.

따라서 각 인덱스 및 샤드에 대해 일정 수준의 리소스 오버헤드가 있기 때문에 인덱스와 샤드는 클러스터 관점에서 보았을 때 무료라고 볼 수 없습니다.


팁: 샤드가 작으면 세그먼트가 작아져 오버헤드가 증가합니다. 평균 샤드 크기를 최소 몇 GB에서 수십 GB 사이로 유지하는 것을 목표로 하세요. 시계열 데이터가 있는 사용 사례의 경우 20GB에서 40GB 사이의 샤드가 일반적입니다.

팁: 샤드당 오버헤드는 세그먼트 개수와 크기에 따라 달라지기 때문에 강제 병합 작업을 통해 더 작은 세그먼트를 더 큰 세그먼트로 병합하면 오버헤드를 줄이고 쿼리 성능을 향상시킬 수 있습니다. 이 작업은 이상적으로 인덱스에 더 이상 데이터가 입력되지 않을 때 실행되어야 합니다. 그리고 무척 부하가 큰 작업이니 피크 시간을 피하여 수행해야 하는 것을 명심하세요.

팁: 하나의 노드에 저장할 수 있는 샤드의 개수는 가용한 힙의 크기와 비례하지만, Elasticsearch에서 그 크기를 제한하고 있지는 않습니다. 경험상으로 보면 노드의 샤드 개수를 하나의 노드에 설정한 힙 1GB당 20개 미만으로 유지하는 것이 좋습니다. 따라서 힙이 30GB인 노드는 최대 600개의 샤드를 가질 수 있지만 이보다 훨씬 더 적게 유지하는 것이 더 좋습니다. 일반적으로 이러한 구성은 클러스터를 건강하게 유지하는데 도움이 됩니다. (편집자 노트: 8.3을 기준으로, 샤드당 힙 사용량을 대폭 줄여서 이 블로그의 경험 법칙을 업데이트했습니다. Elasticsearch 8.3 이상 버전은 아래 팁를 따르세요.)

새로운 팁: 데이터 노드의 인덱스당 필드당 1kB 힙 + 오버헤드를 허용하세요
각 매핑된 필드의 정확한 리소스 사용량은 그 유형에 따라 다르지만, 경험 법칙에 따르면 각 데이터 노드가 보유한 인덱스당 매핑된 필드당 힙 오버헤드를 약 1kB까지 허용합니다. 또한 색인, 검색 및 집계와 같은 워크로드뿐만 아니라 Elasticsearch의 기준 사용에도 충분한 힙을 허용해야 합니다. 0.5GB의 추가 힙은 많은 합리적인 워크로드에 충분하며, 워크로드가 크면 더 많이 필요하고 워크로드가 매우 적으면 훨씬 더 적은 양의 워크로드가 필요할 수 있습니다.

예를 들어, 데이터 노드가 각각 4,000개의 매핑된 필드를 포함하는 1,000개의 인덱스의 샤드를 보유하는 경우, 필드에 대해 약 1000 × 4000 × 1kB = 4GB 힙을 허용하고 그 워크로드 및 기타 오버헤드에 대해 추가로 0.5GB의 힙을 허용해야 합니다. 따라서 이 노드에는 4.5GB 이상의 힙 크기가 필요합니다.


샤드의 크기가 어떻게 성능에 영향을 미치나요?

Elasticsearch에서 각 쿼리는 샤드당 단일 스레드로 실행됩니다. 그러나 동일한 샤드에 대해 여러 쿼리 및 집계가 수행될 수 있듯이 여러 개의 샤드를 동시에 처리할 수도 있습니다.

즉, 캐싱을 사용하지 않을 때, 최소 쿼리 응답 시간은 데이터, 쿼리 유형 및 샤드 크기에 따라 달라집니다. 많은 개수의 작은 샤드에 대한 쿼리 작업을 하면 샤드당 처리 속도는 빨라지지만 더 많은 작업을 대기열에 넣고 순서대로 처리해야 하므로, 더 적은 개수의 큰 샤드를 검색하는 것보다 반드시 빠르다고 보장할 수 없습니다. 동시에 여러 쿼리가 실행될 때, 여러 개의 작은 샤드가 오히려 쿼리 처리량을 줄일 수도 있습니다.


팁: 쿼리 성능 관점에서 최대 샤드 크기를 결정하는 최고의 방법은 실제 데이터와 쿼리로 벤치마크 테스트를 해보는 것입니다. 쿼리 하나로 최적화 작업을 하면 왜곡된 결과가 발생할 수 있으니, 항상 운영 환경에서 실제로 처리해야 하는 쿼리와 인덱싱 부하를 고려하여 벤치마크 테스트하시기 바랍니다.


샤드 크기는 어떻게 관리하나요?

시계열 인덱스를 사용할 때, 각 인덱스는 오래전부터 고정된 기간을 활용하였습니다. 일 단위 인덱스는 무척 일반적으로 활용되고 있으며, 데이터 보존 기간이 짧거나 일 단위로 대량 데이터를 저장하는 경우 자주 사용됩니다. 이는 데이터 보존 기간을 정교하게 관리할 수 있게 해주며, 매일 변하는 데이터 볼륨을 쉽게 조정할 수 있습니다. 보존 기간이 긴 데이터는, 특히 일일 데이터 볼륨이 상대적으로 작아 일 단위 인덱스로 관리하는 것이 비효율적인 경우라면, 보통 주 단위 혹은 월 단위 인덱스를 만들어 샤드의 크기를 적당히 크게 유지합니다. 따라서 시간이 지남에 따라 클러스터에 저장해야 하는 인덱스 및 샤드 개수가 줄어듭니다.


팁: 고정 주기로 저장되는 시계열 인덱스를 사용하는 경우, 목표 샤드 크기에 도달하기 위해 각 인덱스의 보존 기간과 예상 데이터 볼륨을 기준으로 각 인덱스의 적용 기간을 조정하세요.


고정 시간 주기로 되어 있는 시계열 인덱스는 데이터 볼륨을 합리적으로 예측할 수 있고 변동폭이 적을 때 잘 작동합니다. 색인량이 매우 빠르게 변할 수 있는 경우라면, 균일하게 목표 샤드 크기를 유지하는 것이 무척 어렵습니다.

이러한 시나리오를 잘 통제하기 위하여, 롤오버 및 축소 API가 도입되었습니다. 이러한 기능은 특히 시계열 인덱스에 대해 인덱스와 샤드를 관리하는 방법에 많은 유연성을 제공합니다.

롤오버 인덱스 API를 사용하면 클러스터가 저장해야 하는 도큐먼트와 인덱스의 개수를 지정하거나, 저장해야 할 도큐먼트의 최대 기간을 지정할 수 있습니다. 이러한 기준 중 하나를 초과하면, Elasticsearch는 가동 중단 시간 없이 신규 인덱스를 생성하도록 트리거할 수 있습니다. 각 인덱스가 특정 기간을 처리하는 대신, 특정 크기가 되면 신규 인덱스로 전환하는 것이 가능해지기 때문에 모든 인덱스의 샤드 크기를 원하는 수준으로 유지하는 것이 더욱 쉬워집니다.

데이터가 업데이트될 수 있는 경우, 이벤트 발생 시간 정보와 이 API 호출시 사용하는 인덱스간의 관계가 끊어지기 때문에, 검색시 매번 업데이트가 실행되는 것과 같은 큰 성능 저하를 야기할 수 있습니다.


팁: 시간이 지남에 따라 볼륨이 크게 달라질 수 있는 시계열 불변 데이터가 있는 경우, 롤오버 인덱스 API를 사용하여 각 인덱스가 적용되는 기간을 동적으로 변경하여 최적의 목표 샤드 크기를 유지하는 것을 고려하세요. 이는 데이터 볼륨을 예측하기 힘든 상황에서 지나치게 크거나 작은 샤드를 만드는 것을 피할 수 있게 해줍니다.


축소 인덱스 API를 사용하면 기존 인덱스를 더 적은 개수의 프라이머리 샤드를 가진 신규 인덱스로 축소(shrink)할 수 있습니다. 색인을 하는 동안 전체 노드에 샤드를 균등하게 분배하고 싶은데 샤드의 크기가 너무 작다면, 이 API를 사용하여 더 이상 색인이 이뤄지지 않는 인덱스의 프라이머리 샤드 개수를 줄일 수 있습니다. 이렇게 하면 더 큰 샤드가 생성되어 데이터의 장기 저장에 더 적합합니다.


팁: 특정 기간을 저장하는 각 인덱스를 큰 규모의 노드에 분산하여 저장하고 싶다면, 색인이 더 이상 이뤄지지 않는 시점에 축소 API를 사용하여 프라이머리 샤드 개수를 줄여보세요. 이는 초기에 너무 많은 샤드를 설정한 경우, 샤드의 개수를 줄이는 데 유용할 수 있습니다.


결론

이 블로그 포스트에는 Elasticsearch에서 데이터를 최적으로 관리할 수 있는 팁과 실질적인 가이드라인을 제공하고 있습니다. 좀더 깊이 알아보고 싶으시면, "Elasticsearch: 최종 가이드(Elasticsearch: the Definitive Guide)”에서 소개하고 있는 확장을 위한 설계(designing for scale)섹션을 참고하시기 바랍니다. 자료가 좀 오래되긴 했지만, 충분히 읽을 가치가 있습니다.

여러분의 데이터를 어떻게 인덱스와 샤드에 분배하는지에 대한 방법은 많지만, 이는 사용 사례의 구체적인 내용에 따라 달라지며, 이 블로그 포스트에서 소개한 조언들을 최적의 방법으로 적용하는 방법을 찾기가 쉽지 않을 것입니다. 보다 심도깊은 개별적인 조언이 필요하시면, 구독을 통해 상업적으로 Elastic에 참여하실 수 있으며, Elastic의 지원 및 컨설팅 팀이 귀사의 프로젝트를 가속화할 수 있도록 도와드릴 수 있습니다. 공개적으로 여러분의 사용 사례에 대해 논의하고 싶으시면, Elastic 커뮤니티와 공개 포럼을 통해 도움을 받으실 수도 있습니다.


이 포스트는 원래 2017년 9월 18일에 게시되었습니다. 2022년 7월 6일에 업데이트되었습니다.