ElasticSearch
이번 글에서는 ElasticSearch (이하 ES)의 클러스터를 설계하기 위해 필요한 요소들 중 샤드의 개수가 검색 성능에 미치는 영향을 바탕으로 적정한 샤드의 개수와 데이터 노드의 개수를 산정하는 방법에 대해 이야기해 보려고 합니다. ES는 대부분의 경우 기본 설정 만으로도 충분한 성능을 보여 주지만 서비스에 투입하기 위한 검색 엔진으로 사용하거나 대용량의 로그 수집 시스템을 구축하기 위해서는 테스트를 거쳐 적정한 수치를 확인하고 적용하는 것이 필요합니다.
우선 ES에서 검색이 어떻게 일어나는지에 대해서 살펴보겠습니다. ES의 검색은 1 쿼리 1 샤드 1 쓰레드를 바탕으로 일어납니다. 먼저 아래와 같은 상황을 가정해 보겠습니다. 해당 클러스터는 총 3개의 노드를 가지고 있고 각각의 노드는 샤드를 1개씩 가지고 있습니다. 또한 각각의 노드는 4개의 코어를 가지고 있어서 검색 쓰레드 풀에 4개의 검색 쓰레드를 가지고 있습니다.
위 그림에서 보이는 것처럼 먼저 사용자가 query #1을 Node C로 날립니다. Node C는 Node A와 Node B에게 자신이 받은 query #1을 전달해 줍니다. 각각의 노드는 자신이 가지고 있는 샤드들에게 query #1을 날리게 되고 이때 검색해야 할 샤드의 수가 하나이기 때문에 4개의 쓰레드 풀에서 1개의 쓰레드를 꺼내서 쿼리를 날립니다.
단일 쿼리를 기준으로 생각해 보면 각각의 노드는 샤드가 하나씩 이기 때문에 쓰레드를 하나만 사용하게 되고 나머지 3개의 쓰레드는 놀게 됩니다. 비효율적인 상황이 발생한다고 생각할 수 있습니다. 그럼 샤드의 개수를 코어의 개수만큼 만들면 어떻게 될까요?
같은 데이터를 저장하고 있는 서로 다른 클러스터를 가정해 보겠습니다. 한 클러스터는 노드당 샤드를 1개, 다른 클러스터는 노드당 샤드를 4개 가지고 있습니다. 그리고 이번에는 쿼리가 1개가 아니라 동시에 4개가 인입된다고 가정해 보겠습니다.
첫 번째 클러스터의 경우는 아래와 같이 4개의 쿼리가 인입되고 각 노드의 샤드는 하나이기 때문에 쿼리 하나 당 쓰레드 하나씩을 사이좋게 나눠서 실행하게 됩니다. 그렇기 때문에 4개의 쿼리가 거의 동시에 종료됩니다.
하지만 두 번째 클러스터의 경우 노드 별로 쿼리를 날려야 할 샤드가 4개이므로, query #1에 대한 결과를 조회하기 위해 4개의 쓰레드 풀에서 4개의 쓰레드를 모두 사용해서 각각의 샤드 별로 쿼리를 날리게 됩니다. 이렇게 되면 뒤에 인입된 #2 ~ #4의 쿼리는 #1의 쿼리가 완료될 때까지 실행되지 못하고 쓰레드 큐에 쌓이게 됩니다.
이렇게 되면 query #2 ~ query #4 의 경우는 응답 결과가 느려지게 되며 먼저 처리된 쿼리와 나중에 처리된 쿼리 사이의 응답 속도 차가 점점 벌어지게 됩니다. 그리고 너무 많은 양의 쿼리가 인입될 경우 큐가 꽉 차서 rejected 현상이 발생할 수 있습니다. (https://brunch.co.kr/@alden/36)
이렇게 떨어진 응답 속도는 쿼리 자체의 문제가 아니고 쓰레드 경합의 문제이기 때문에 slowlog 에도 남지 않습니다.
정리하자면, 샤드의 수가 너무 적으면 단일 쿼리의 응답 속도는 느려질 수 있으나 대량의 쿼리가 인입될 경우 고른 성능을 보여줄 수 있고, 샤드의 수가 너무 많으면 단일 쿼리의 응답 속도는 빠를 수 있으나 대량의 쿼리가 인입될 경우 쿼리 별 성능 차이가 심해질 수 있습니다.
그렇기 때문에 구축하고자 하는 클러스터의 목적에 맞게 적정한 수준의 샤드 수가 결정되어야 합니다.
그렇다면 적정한 샤드의 개수를 결정하기 위해서 어떤 것들을 고민해야 할까요?
먼저 클러스터가 저장해야 할 전체 데이터의 크기입니다. 예를 들어 로그성 데이터들이 저장되는 클러스터라면 일 별 몇 GB의 로그가 저장되며 유지 기간은 어느 정도인지를 예측해야 하며, 데이터를 검색하기 위한 검색 엔진이라면 초기 데이터의 크기가 어느 정도이고 주기적으로 어떤 형태로 데이터의 갱신이 일어나는지를 예측해야 합니다.
두 번째로는 예상하는 최대 동시 인입 쿼리 수입니다. 내부 어드민 용도이고 자주 검색이 발생하지 않는다면 크게 염두에 두지 않아도 되는 부분이긴 합니다만 서비스에 노출되고 사용자에게 직접적으로 검색 결과를 돌려주어야 한다면 최대 몇 개의 쿼리가 동시에 인입되는지는 매우 중요한 부분입니다. 특히 블로그 검색 시스템 등으로 사용하게 된다면 더욱 중점을 두고 생각해야 합니다.
세 번째로는 목표로 하는 검색 응답 시간입니다. 빠르면 빠를수록 좋다는 두리뭉실한 목표보다는 최소한 몇 ms 안에 응답을 줄 수 있게 하고 싶다와 같은 구체적인 목표가 필요합니다. 정확한 목표가 있어야 테스트를 통해서 적합한 샤드의 개수를 결정할 수 있습니다.
마지막으로는 서비스에 실제 사용할 수 있는 하드웨어의 스펙을 결정하는 것입니다. 테스트 장비를 실제 서비스용 장비와 동일한 스펙으로 맞추어서 테스트를 하고 샤드의 개수를 산정해야 실제 서비스에 들어갔을 때 발생하는 이슈들을 추적하고 해결하기 쉬워집니다.
그럼 실제로 아래와 같은 클러스터를 구축한다고 가정하고 테스트를 시작해 보겠습니다.
테스트 시나리오는 아래와 같습니다.
아마도 위 시나리오 중에서 왜 샤드를 1개만 생성해야 하는지 의문이 들 수 있습니다. 우리가 앞 절에서 이야기했던 1 쿼리 1 샤드 1 쓰레드를 다시 떠올려 보겠습니다. 하나의 데이터 노드에 하나의 샤드만을 만들어서 쿼리를 날리게 되면 어떻게 될까요? 아래 그림과 같이 하나의 쓰레드가 하나의 샤드에 검색을 날리게 됩니다. 이 상태에서 샤드의 크기를 점점 늘려가면서 쿼리를 날리게 되면 샤드가 담고 있는 데이터의 크기가 커짐에 따라서 검색 쓰레드의 검색 결과가 점점 늘어나는 것을 볼 수 있으며 최종적으로 하나의 쓰레드가 하나의 샤드를 검색하는 데 걸리는 시간을 알게 됩니다.
데이터 인입은 elasticdump와 같은 툴을 이용해서 해도 되고 logstash를 이용해서 인입해도 됩니다. 검색 테스트는 search api를 직접 날리면서 해봐도 되며, 별도의 스크립트를 활용해도 됩니다.
테스트하는데에 도움이 될까 싶어서 내부적으로 사용하는 스크립트를 아래 주소에 올려 두었습니다.
https://github.com/alden-kang/elasticsearch-scripts/blob/master/search-test.py
테스트 결과를 그래프로 그려본다면 아래와 같은 그래프가 될 겁니다. Y축은 쿼리 응답에 대한 took (ms)를 기록한 것이고 X축은 문서의 수와 샤드의 크기를 기록한 것입니다. 중간중간 스파이크는 무시하고 살펴보면 전체적으로 문서의 수와 샤드의 데이터 크기가 늘어나는 만큼 검색에 소요되는 시간도 선형적으로 증가하는 것을 볼 수 있습니다.
이 테스트 결과를 바탕으로 살펴보면 앞 절에서 산정했던 검색 성능 100ms를 달성하기 위해서는 샤드의 크기가 약 26GB 정도 되어야 함을 알 수 있습니다. 오차 범위를 포함해서 20GB 정도로 샤드의 크기를 맞추게 된다면 약 70~80 ms 정도의 검색 성능을 보여줄 수 있다는 것을 알 수 있습니다.
물론 이 수치는 도큐먼트에 따라 다릅니다. 제가 이번 글 작성을 위해 테스트 한 도큐먼트 기준으로 말씀드리는 겁니다.
지금까지 테스트를 통해서 샤드 하나의 최대 크기를 20GB 정도로 설정하게 되면 우리가 목표로 하고 있는 100 ms 정도의 성능을 충분히 만족할 수 있다는 것을 확인했습니다. 그럼 하루에 쌓이는 데이터의 양이 최대 100GB 정도 되기 때문에 하루치 인덱스의 샤드 개수는 5개로 설정하면 된다는 것을 알 수 있습니다. 그럼 이 5개를 몇 개의 노드에 어떻게 배치할까요? 이 문제는 초당 인입되는 쿼리 수를 통해서 계산하게 됩니다.
인덱스 별 샤드의 개수는 5개이고 한 번에 처리되어야 하는 쿼리 수는 10개 이므로 클러스터에 필요한 검색 쓰레드의 전체 개수는 50개가 됩니다. 즉 클러스터의 데이터 노드들의 검색 쓰레드들을 모두 합친 게 50개가 된다면 10개의 쿼리를 동시에 처리할 수 있게 됩니다. 데이터 노드 하나의 코어 수가 24개 이므로 최소 3대가 필요하고, replicas를 1로 두고자 한다면 replicas 샤드의 배치를 고려하여 최종적으로 6대의 데이터 노드가 필요하게 됩니다. 이를 바탕으로 아래와 같은 공식을 만들어 낼 수 있습니다.
이번 글에서는 검색이 일어나는 과정과 테스트를 통해서 클러스터에 적합한 샤드의 개수 및 데이터 노드의 수를 산정하는 이야기를 해봤습니다. 이외에도 클러스터를 구축하기 위해서 고려해야 할 것들이 더 있습니다. 인덱스의 개수, 전체 샤드의 개수, rollover 전략 등등.. 이다음 편 글을 통해서 오늘 다루지 못한 부분들에 대해서 다뤄보도록 하겠습니다. 긴 글 읽어 주셔서 감사합니다.
https://www.elastic.co/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster