2017년 9월 18일 엔지니어링

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

By Christian Dahlqvist조인석 (Chris Cho)

Elasticsearch는 다양한 유스케이스를 지원하는 매우 다재다능한 플랫폼이며, 데이터 구성 및 복제 전략에 따라 큰 유연성을 제공합니다. 그러나 이러한 유연성은 여러분 데이터의 효율적인 인덱스와 샤드 구성을 복잡하게 만듭니다. 특히, 여러분이 Elastic Stack을 처음 접하는 분이라면 더욱 그렇습니다. 충분한 이해 없이 궁여지책으로 구성한 시스템은 처음 시작할 때 꼭 문제가 되지 않지만, 시간이 지나고 데이터 볼륨이 커짐에 따라 성능 문제를 일으킵니다. 클러스터에 저장된 데이터가 많아질수록 문제를 바로 잡는 것은 더욱 어려워집니다. 가령, 문제를 해결하기 위해 대량 데이터의 재색인(reindexing)을 시도하는 것과 같이 말이죠. (역자주: 성능 최적화를 위한 데이터 샤드 개수 조정 작업은 재색인 작업이 필수지만, 데이터 볼륨이 커지면 재색인 시간이 증가하며 운영 환경에서 쉽게 작업을 할 수 없습니다.)

사용자가 성능 문제를 제기할 때, 클러스터 내의 데이터 인덱스 구성과 샤드의 개수를 확인하게 마련입니다. 이는 멀티태넌시(multi-tenancy) 및/혹은 시간 기반 인덱스를 포함한 유스케이스 모두에게 해당합니다. 커뮤니티 행사장이나 회의장, 포럼 혹은 한국 사용자 커뮤니티에서 가장 흔히 접하는 질문들은 “얼마나 많은 샤드로 구성해야 하나요?”와 “샤드의 최대 크기는 어떻게 되나요?” 입니다. (역자주: 한국에서는 주로 인덱스의 크기, 개수, 분할 방법에 대한 문의가 주를 이루며, 성능 최적화를 위해 가장 중요한 샤드 크기에 대한 질문은 많지 않습니다.)

이 블로그 포스트는 이 질문들에 대한 답을 찾는 것을 도울 것이며, 시간 기반 인덱스(로깅, 보안 분석 등) 사례를 위한 기본 가이드라인을 제공할 것입니다.

샤드란 무엇인가?

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

Elasticsearch에 저장된 데이터는 인덱스라고 부릅니다. 각 인덱스는 한 개 혹은 여러 개의 샤드로 구성됩니다. 각 샤드는 루씬 인덱스이며, 이는 Elasticsearch  클러스터 내에 인덱싱을 하거나 데이터 일부를 조회하기 위한 독립적인 검색 엔진으로 생각하실 수 있습니다.

 Elasticsearch 내부 구조 예시

<그림 1> Elasticsearch 내부 구조 예시

데이터를 샤드에 입력할 때, Elasticsearch는 주기적으로 디스크에 불변의(immutable) 루씬 세그먼트 형태로 저장(publish)하며, 이 작업 이후에 조회가 가능해 집니다. 이러한 과정을 우리는 리프레쉬(refresh) 작업이라고 부릅니다.이 과정에 대한 상세 설명은 이 가이드에서 확인하세요. (역자주: 샤드는 한개 혹은 여러개의 세그먼트로 구성됩니다.)

세그먼트의 개수가 많아지면, 주기적으로 더 큰 세그먼트로 병합됩니다. 이 과정을 우리는 병합(merging) 작업이라고 부릅니다. 모든 세그먼트는 불변의 성질을 갖고 있기 때문에, 병합 대상 세그먼트가 삭제되기 전에 병합을 위한 신규 세그먼트가 생성되며, 일반적으로 디스크 사용량은 변하게 됩니다. 병합 작업은 리소스에 무척 민감하며, 특히 디스크 I/O에 큰 영향을 받습니다.

샤드는 Elasticsearch가 데이터를 클러스터 내에 분산 저장하기 위한 단위입니다. 장애 발생시 Elasticsearch가 데이터를 재배치할 때의 수행 속도는 네트워크와 디스크 성능뿐만 아니라, 샤드의 크기와 개수에 따라 결정됩니다.


TIP: 매우 큰 샤드를 만드는 것을 피하세요. 이는 장애시 클러스터의 복구 능력에 좋지 않은 영향을 끼칩니다. 샤드 최대 크기에 대한 제한은 없습니다만, 다양한 실 사용사례를 비춰봤을 때 50GB를 넘지 않는 것이 좋습니다.


인덱스 보관 주기

세그먼트는 불변의 성질을 갖기 때문에, 도큐먼트를 업데이트하는 것은 일단 Elasticsearch가 해당 도큐먼트를 찾은 다음, 삭제 대상이라는 것을 표시한 다음에 새 버전의 도큐먼트를 추가해야만 합니다. 도큐먼트를 삭제하는 것 역시 삭제 대상 도큐먼트를 찾아서 삭제 대상이라는 것을 표기하는 것입니다. (역자주: 실제로 삭제되는 것이 아닙니다.) 이러한 이유로, 삭제된 도큐먼트는 병합되기 전까지는 디스크 공간을 차지하고 시스템  자원을 사용하게 하며, 이는 많은 시스템 자원을 소비하게 합니다.

Elasticsearch는 명시적으로 모든 레코드를 하나하나씩 삭제하지 않고, 파일 시스템에서 인덱스들을 바로 삭제할 수 있는 무척 효율적인 기능을 제공합니다. 이 기능은 현재 Elasticsearch에서  데이터를 삭제하는 가장 효율적인 방법입니다.


TIP: 데이터 보관 주기를 관리하기 위하여 가능한한 시간 기반 인덱스를 사용하세요. 보관 주기에 따라 데이터를 그룹핑하여 인덱스에 저장하세요. 시간 기반 인덱스는 프라이머리 샤드와 리플리카의 개수 조정을 쉽게 해주며, 후속 인덱스 생성시 이를 쉽게 변경할 수도 있습니다. 또한, 데이터의 볼륨과 요구사항을 쉽게 반영할 수도 있게 해줍니다.


각 샤드에는 메모리에 보관하고 힙 공간을 차지하는 데이터가 있습니다. 이 곳에는 샤드 레벨의 데이터 구조와 세그먼트 레벨의 디스크 저장 위치와 같은 정보들을 포함하고 있습니다. 데이터 구조 크기는 고정되어 있지 않으며 유스케이스에 따라 달라집니다.

세그먼트와 관련있는 부하의 가장 중요한 특징은 부하의 정도가 세그먼트의 크기와 엄격하게 비례하지 않는다는 점입니다. 즉, 큰 세그먼트는 작은 세그먼트에 비해 데이터 볼륨 당 부하가 적습니다. 이 차이는 상당히 클 수 있습니다. (역자주: 세그먼트가 두 배로 커진다고 해서, 부하가 두 배로 커지는 것이 아니기 때문에, 상대적으로 작은 세그먼트보다 큰 세그먼트가 부하 상황을 완화할 수 있습니다.)

각 노드에 최대한 많은 데이터를 저장하기 위해서는 힙 사용량과 오버헤드를 최대한 줄이는 것이 중요합니다. 노드의 힙 공간을 많이 차지하면 차지할수록, 더 많은 데이터와 샤드가 필요합니다. 

그렇기 때문에 각각의 인덱스와 샤드는 리소스 부하에 어느 정도 영향을 끼치며, 클러스터 관점에서 보았을 때 공짜라고 볼 수 없습니다. 


TIP: 작은 샤드는 작은 세그먼트를 만들며 부하를 증가시킵니다. 평균 샤드 크기를 수 GB와 수십 GB 사이를 유지하세요. 시간 기반 데이터를 사용한 과거 사례를 보면, 20GB ~ 40GB 정도의 사이즈가 적당합니다.

TIP: 각 샤드의 부하는 세그먼트 개수와 크기에 따라 결정됩니다. forcemerge 기능을 사용하여 작은 세그먼트를 큰 세그먼트로 병합시키세요. 이 작업은 이상적으로 인덱스에 더 이상 데이터가 입력되지 않을 때 실행되어야 합니다. 그리고 무척 부하가 큰 작업이니 피크 시간을 피하여 수행해야 하는 것을 명심하세요.

TIP: 하나의 노드에 저장할 수 있는 샤드의 개수는 가용한 힙의 크기와 비례하지만, Elasticsearch에서 그 크기를 제한하고 있지는 않습니다. 경험상 하나의 노드에 설정한 힙 1GB 당 20~25개 정도가 적당합니다. 따라서 30GB 힙을 가진 노드는 최대 600~750개의 샤드를 가지는 것이 가능하지만, 이 보다는 적게 유지하는 것이 더 좋습니다. 일반적으로 이러한 구성은 클러스터를 건강하게 유지하는데 도움이 됩니다.


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

Elasticsearch에서는 샤드당 단일 쓰레드가 각 쿼리를 실행합니다. 반면에 동일한 샤드에 대해 여러 쿼리 및 Aggregation(집계)을 수행할 수 있는 것처럼, 여러 샤드가 동시에 처리될 수도 있습니다.

즉, 캐싱을 사용하지 않을 때, 최소 쿼리 응답 시간은 데이터, 쿼리 유형 및 샤드 크기에 따라 달라집니다. 많은 개수의 작은 샤드를 조회하면 각 샤드마다 처리 속도는 빨라지지만 더 많은 작업을 큐에 넣고 순서대로 처리해야하므로, 더 적은 개수의 큰 샤드를 검색하는 것보다 반드시 빠르다고 보장할 수 없습니다. 동시에 여러 쿼리가 실행될 때, 여러 개의 작은 샤드가 오히려 쿼리 처리량(throughput)을 줄일 수도 있겠죠. (역자주: 결론적으로 1개 샤드의 권장하는 최대 크기를 넘지 않는 선이라면, 작은 크기의 여러 샤드보다는 큰 크기의 적은 샤드가 더 효율적입니다.)


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


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

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


TIP: 고정 주기로 저장되는 시간 기반 인덱스를 사용한다면, 목표 샤드 크기를 유지하기 위하여 데이터 보관 주기와 예상 데이터 볼륨에 따라 각 인덱스를 적절하게 조정하세요.


고정 시간 주기로 되어 있는 시간 기반 인덱스는 데이터 볼륨이 예측 가능하고 변동폭이 적을 때 잘 동작합니다. 하지만, 데이터 인덱싱량이 빈번하게 변하는 경우라면, 균일하게 목표 샤드 크기를 유지하는 것이 무척 어렵죠.

이러한 시나리오를 잘 통제하기 위하여, Rollover 및 Shrink API가 있습니다. 이는 특히 시간 기반 인덱스를 사용하는 경우 인덱스와 샤드를 관리하는 데 큰 유연성을 제공합니다.

Rollover 인덱스 API는 클러스터가 저장해야 하는 도큐먼트와 인덱스의 개수를 지정하거나, 저장해야 할 도큐먼트의 최대 기간을 지정할 수 있습니다. 위 두 조건 중 하나의 제약 조건을 넘어서면, Elasticsearch는 다운타임 없이 신규 인덱스를 생성합니다. 각 인덱스가 특정 기간을 처리하는 대신, 특정 크기가 되면 신규 인덱스로 전환하는 것이 가능해지기 때문에 모든 인덱스의 샤드 크기를 원하는 수준으로 유지하는 것이 더욱 쉬워집니다.

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


TIP: 시간 기반이며 시간이 지남에 따라 데이터 볼륨이 불규칙적인 불변 데이터를 가지고 있다면, 최적의 목표 샤드 크기를 유지하기 위하여 Rollover 인덱스 API를 사용하는 것을 고려하세요. 이는 데이터 볼륨을 예측하기 힘든 상황에서 지나치게 크거나 작은 샤드를 만드는 것을 피할 수 있게 해줍니다.


Shrink 인덱스 API는 기존 인덱스를 더 적은 개수의 프라이머리 샤드를 가진 신규 인덱스로 변경(shrink)하게 해줍니다. 인덱싱 중에 전체 노드에 샤드를 균등하게 분배하고 싶은데 샤드의 크기가 너무 작다면, 이 API를 사용하여 더 이상 인덱싱이 이뤄지지 않은 인덱스의 프라이머리 샤드 개수를 줄입니다. 이는 더 큰 샤드를 만들게 해주며 긴 기간 동안 보관해야하는 데이터인 시나리오에 더욱 적합합니다.


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


결론

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

여러분의 데이터를 어떻게 인덱스와 샤드에 분배하는지에 대한 방법은 무척 많지만, 이는 명확한 유스케이스에 따라 달라지며, 이 블로그 포스트에서 소개한 조언들을 최적의 방법으로 적용하는 방법을 찾기가 쉽지 않을 것입니다. 만약, 더욱 상세한 개별적인 조언이 필요하다면 저희의 상용 기술 지원 서비스를 통해 받을 수 있으며, 저희의 기술지원팀과 컨설팅팀이 여러분의 프로젝트를 최적화/가속화하는데 도움을 드릴 수 있습니다. 공개 석상에서 여러분의 유스케이스를 논의하고 싶다면 저희 커뮤니티나 공식 포럼을 통해서도 도움을 받으실 수 있으니 참고하시기 바랍니다. (역자주: 한국 커뮤니티는 페이스북에 위치한 한국 엘라스틱서치 유저 그룹에서 한국 커뮤니티 여러분의 도움을 받으실 수 있습니다.)