규모의 장벽 허물기: Intercom에서 Elasticsearch 사용을 최적화한 방법

게시 됨: 2022-09-22

Elasticsearch는 Intercom에서 없어서는 안될 부분입니다.

Inbox, Inbox Views, API, Articles, 사용자 목록, 보고, Resolution Bot 및 내부 로깅 시스템과 같은 핵심 Intercom 기능을 뒷받침합니다. Elasticsearch 클러스터에는 350TB 이상의 고객 데이터가 포함되어 있고 3,000억 개 이상의 문서를 저장하며 피크 시 초당 60,000개 이상의 요청을 처리합니다.

Intercom의 Elasticsearch 사용량이 증가함에 따라 지속적인 성장을 지원하도록 시스템을 확장해야 합니다. 최근 차세대 받은 편지함이 출시되면서 Elasticsearch의 안정성이 그 어느 때보다 중요해졌습니다.

우리는 가용성 위험을 제기하고 미래의 다운타임을 위협하는 Elasticsearch 설정 문제를 해결하기로 결정했습니다. Elasticsearch 클러스터의 노드 간에 트래픽/작업이 고르지 않게 분포되어 있습니다.

비효율의 초기 징후: 부하 불균형

Elasticsearch를 사용하면 데이터를 저장하는 노드(데이터 노드)의 수를 늘려 수평으로 확장할 수 있습니다. 우리는 이러한 데이터 노드 간의 로드 불균형을 알아차리기 시작했습니다. 일부는 더 높은 디스크 또는 CPU 사용으로 인해 다른 노드보다 더 많은 압력을 받고(또는 "더 뜨거워") 있었습니다.

그림 1 CPU 사용량의 불균형
(그림 1) CPU 사용량의 불균형: 평균보다 CPU 사용량이 ~20% 더 높은 두 개의 핫 노드.

Elasticsearch의 내장 샤드 배치 로직은 각 노드에서 사용 가능한 디스크 공간과 노드당 인덱스 샤드 수를 대략적으로 추정하는 계산을 기반으로 결정을 내립니다. 샤드별 리소스 사용률은 이 계산에 고려되지 않습니다. 결과적으로 일부 노드는 더 많은 리소스를 필요로 하는 샤드를 수신하여 "핫"해질 수 있습니다. 모든 검색 요청은 여러 데이터 노드에서 처리됩니다. 최대 트래픽 동안 핫 노드가 제한을 초과하면 전체 클러스터의 성능이 저하될 수 있습니다.

핫 노드의 일반적인 이유는 클러스터에 큰 샤드(디스크 사용률 기준)를 할당하는 샤드 배치 논리로 인해 균형 잡힌 할당 가능성이 낮아집니다. 일반적으로 노드에는 다른 노드보다 하나의 큰 샤드가 더 할당되어 디스크 사용률이 더 높아집니다. 데이터 노드를 추가한다고 해서 모든 핫 노드의 부하 감소가 보장되는 것은 아니기 때문에 큰 샤드가 있으면 클러스터를 점진적으로 확장하는 데 방해가 됩니다(그림 2).

그림 4 노드 추가

(그림 2) 데이터 노드를 추가해도 호스트 A의 부하가 감소하지 않습니다. 다른 노드를 추가하면 호스트 A의 부하가 줄어들지만 클러스터에는 여전히 고르지 않은 부하 분산이 있습니다.

대조적으로, 더 작은 샤드를 사용하면 "핫" 노드를 포함하여 클러스터가 확장됨에 따라 모든 데이터 노드의 로드를 줄이는 데 도움이 됩니다(그림 3).

그림 3 많은 작은 조각들

(그림 3) 더 작은 샤드가 많으면 모든 데이터 노드의 부하를 줄이는 데 도움이 됩니다.

참고: 문제는 큰 크기의 샤드가 있는 클러스터에만 국한되지 않습니다. "크기"를 "CPU 사용률" 또는 "검색 트래픽"으로 바꾸면 유사한 동작을 관찰할 수 있지만 크기를 비교하면 시각화하기가 더 쉽습니다.

클러스터 안정성에 영향을 줄 뿐만 아니라 로드 불균형은 비용 효율적으로 확장하는 능력에도 영향을 미칩니다. 우리는 항상 더 뜨거운 노드를 위험한 수준 아래로 유지하는 데 필요한 것보다 더 많은 용량을 추가해야 합니다. 이 문제를 해결하면 인프라를 보다 효율적으로 활용하여 가용성이 향상되고 비용이 크게 절감됩니다.

문제에 대한 깊은 이해는 다음과 같은 경우 부하가 더 고르게 분산될 수 있다는 것을 깨닫는 데 도움이 되었습니다.

  • 데이터 노드 수에 비해 더 많은 샤드 . 이렇게 하면 대부분의 노드가 동일한 수의 샤드를 수신하게 됩니다.
  • 데이터 노드의 크기에 비해 더 작은 샤드 . 일부 노드에 몇 개의 추가 샤드가 제공된 경우 해당 노드에 대한 로드가 의미 있는 증가를 초래하지 않습니다.

컵케이크 솔루션: 더 적은 수의 더 큰 노드

데이터 노드 수에 대한 샤드 수의 비율, 데이터 노드 크기에 대한 샤드 크기의 비율은 더 많은 수의 더 작은 샤드를 사용하여 조정할 수 있습니다. 그러나 더 적지만 더 큰 데이터 노드로 이동하면 더 쉽게 조정할 수 있습니다.

우리는 이 가설을 확인하기 위해 컵케이크로 시작하기로 결정했습니다. 우리는 몇 개의 클러스터를 더 적은 수의 노드로 더 크고 더 강력한 인스턴스로 마이그레이션하여 동일한 총 용량을 유지했습니다. 예를 들어 클러스터를 40개의 4xlarge 인스턴스에서 10개의 16xlarge 인스턴스로 이동하여 샤드를 더 고르게 분산하여 로드 불균형을 줄였습니다.

그림 4 디스크와 CPU 전반에 걸친 더 나은 부하 분산

(그림 4) 더 적은 수의 더 큰 노드로 이동하여 디스크와 CPU에 더 나은 부하 분산.

더 적은 수의 더 큰 노드 완화는 데이터 노드의 수와 크기를 조정하면 부하 분산을 개선할 수 있다는 가정을 검증했습니다. 우리는 거기에서 멈출 수 있었지만 접근 방식에는 몇 가지 단점이 있었습니다.

  • 시간이 지남에 따라 샤드가 커지거나 트래픽 증가를 설명하기 위해 클러스터에 더 많은 노드가 추가되면 부하 불균형이 다시 발생한다는 것을 알고 있었습니다.
  • 더 큰 노드는 증분 확장을 더 비싸게 만듭니다. 약간의 추가 용량만 필요하더라도 단일 노드를 추가하면 비용이 더 많이 듭니다.

과제: 압축 OOP(일반 개체 포인터) 임계값 초과

더 적은 수의 더 큰 노드로 이동하는 것은 인스턴스 크기를 변경하는 것만큼 간단하지 않았습니다. 우리가 직면한 병목 현상은 마이그레이션할 때 사용 가능한 총 힙 크기(한 노드의 힙 크기 x 총 노드 수)를 유지하는 것이었습니다.

우리는 데이터 노드의 힙 크기를 Elastic에서 제안한 대로 ~30.5GB로 제한하여 JVM이 압축된 OOP를 사용할 수 있도록 컷오프 미만으로 유지되도록 했습니다. 더 적은 수의 더 큰 노드로 이동한 후 힙 크기를 ~30.5GB로 제한하면 더 적은 수의 노드로 작업하므로 전체적으로 힙 용량이 줄어듭니다.

"마이그레이션 대상인 인스턴스는 거대했고 파일 시스템 캐시를 위한 충분한 공간이 남아 있는 포인터를 위한 공간이 있도록 RAM의 많은 부분을 힙에 할당하고 싶었습니다."

이 임계값을 넘을 때의 영향에 대한 조언을 많이 찾지 못했습니다. 마이그레이션하려는 인스턴스는 거대했고 파일 시스템 캐시를 위한 충분한 공간이 남아 있는 포인터를 위한 공간이 있도록 RAM의 많은 부분을 힙에 할당하기를 원했습니다. 우리는 프로덕션 트래픽을 테스트 클러스터에 복제하여 몇 가지 임계값을 실험했고 200GB 이상의 RAM이 있는 시스템의 힙 크기로 RAM의 ~33% ~ 42%로 결정했습니다.

힙 크기의 변경은 다양한 클러스터에 다르게 영향을 미쳤습니다. 일부 클러스터는 "사용 중인 JVM % 힙" 또는 "젊은 GC 수집 시간"과 같은 메트릭에 변화가 없었지만 일반적인 추세는 증가였습니다. 그럼에도 불구하고 전반적으로 긍정적인 경험이었으며 클러스터는 이 구성으로 9개월 이상 문제 없이 실행되었습니다.

장기 수정: 많은 작은 조각

장기적인 해결책은 데이터 노드의 수와 크기에 비해 더 많은 수의 더 작은 샤드를 갖는 방향으로 이동하는 것입니다. 두 가지 방법으로 더 작은 샤드에 접근할 수 있습니다.

  • 더 많은 기본 샤드를 갖도록 인덱스 마이그레이션: 인덱스의 데이터를 더 많은 샤드에 배포합니다.
  • 인덱스를 더 작은 인덱스(파티션)로 나누기: 인덱스의 데이터를 더 많은 인덱스에 분산합니다.

백만 개의 작은 샤드를 만들거나 수백 개의 파티션을 만들고 싶지 않다는 점에 유의하는 것이 중요합니다. 모든 인덱스와 샤드에는 약간의 메모리와 CPU 리소스가 필요합니다.

"우리는 '완벽한' 구성에 집착하기보다 시스템 내에서 차선의 구성을 더 쉽게 실험하고 수정할 수 있도록 하는 데 중점을 두었습니다."

대부분의 경우 작은 크기의 분할된 조각은 많은 작은 조각보다 적은 리소스를 사용합니다. 그러나 다른 옵션이 있습니다. 실험을 통해 사용 사례에 더 적합한 구성에 도달할 수 있습니다.

시스템을 보다 탄력적으로 만들기 위해 "완벽한" 구성에 고정하기보다 시스템 내에서 차선의 구성을 실험하고 수정하기 쉽게 만드는 데 중점을 두었습니다.

인덱스 분할

기본 샤드의 수를 늘리면 때때로 데이터를 집계하는 쿼리의 성능에 영향을 미칠 수 있습니다. 이는 Intercom의 보고 제품을 담당하는 클러스터를 마이그레이션하는 동안 경험한 것입니다. 반대로 인덱스를 여러 인덱스로 분할하면 쿼리 성능을 저하시키지 않으면서 더 많은 샤드에 로드를 분산합니다.

인터콤은 여러 고객의 데이터를 함께 배치할 필요가 없으므로 고객의 고유 ID를 기반으로 파티션을 나누기로 했습니다. 이는 파티셔닝 로직을 단순화하고 필요한 설정을 줄여 가치를 더 빠르게 제공하는 데 도움이 되었습니다.

"엔지니어의 기존 습관과 방법에 최소한의 영향을 주는 방식으로 데이터를 분할하기 위해 먼저 엔지니어가 Elasticsearch를 사용하는 방법을 이해하는 데 많은 시간을 투자했습니다."

엔지니어의 기존 습관과 방법에 가장 적은 영향을 미치는 방식으로 데이터를 분할하기 위해 먼저 엔지니어가 Elasticsearch를 사용하는 방법을 이해하는 데 많은 시간을 투자했습니다. 우리는 관찰 가능성 시스템을 Elasticsearch 클라이언트 라이브러리에 깊이 통합하고 우리 팀이 Elasticsearch API와 상호 작용하는 다양한 방법에 대해 알아보기 위해 코드베이스를 휩쓸었습니다.

우리의 실패 복구 모드는 요청을 재시도하는 것이었습니다. 그래서 우리는 멱등성이 없는 요청을 할 때 필요한 변경을 했습니다. 우리는 `update/delete_by_query`와 같은 API의 사용을 방지하기 위해 일부 린터를 추가했습니다. 멱등성이 아닌 요청을 쉽게 만들 수 있도록 했기 때문입니다.

전체 기능을 제공하기 위해 함께 작동하는 두 가지 기능을 구축했습니다.

  • 한 인덱스에서 다른 인덱스로 요청을 라우팅하는 방법입니다. 이 다른 인덱스는 파티션이거나 파티션되지 않은 인덱스일 수 있습니다.
  • 여러 인덱스에 데이터를 이중으로 쓰는 방법입니다. 이를 통해 마이그레이션되는 인덱스와 파티션을 동기화된 상태로 유지할 수 있었습니다.

"속도 저하 없이 모든 사고의 폭발 반경을 최소화하도록 프로세스를 최적화했습니다."

인덱스를 파티션으로 마이그레이션하는 프로세스는 전체적으로 다음과 같습니다.

  1. 새 파티션을 만들고 이중 쓰기 기능을 켜서 파티션이 원래 인덱스의 최신 상태를 유지하도록 합니다.
  2. 모든 데이터의 백필을 트리거합니다. 이러한 백필 요청은 새 파티션에 이중으로 기록됩니다.
  3. 백필이 완료되면 이전 인덱스와 새 인덱스에 동일한 데이터가 있는지 확인합니다. 모든 것이 괜찮아 보이면 기능 플래그를 사용하여 일부 고객의 파티션 사용을 시작하고 결과를 모니터링합니다.
  4. 일단 확신이 서면 모든 고객을 파티션으로 이동하는 동시에 이전 인덱스와 파티션 모두에 이중 쓰기를 수행합니다.
  5. 마이그레이션이 성공했다고 확신하면 이중 쓰기를 중지하고 이전 인덱스를 삭제합니다.

이 단순해 보이는 단계는 많은 복잡성을 포함합니다. 속도 저하 없이 모든 사고의 폭발 반경을 최소화하도록 프로세스를 최적화했습니다.

혜택 받기

이 작업은 Elasticsearch 클러스터의 로드 균형을 개선하는 데 도움이 되었습니다. 더 중요한 것은 이제 기본 샤드가 더 적은 파티션으로 인덱스를 마이그레이션하여 허용할 수 없게 될 때마다 부하 분산을 개선할 수 있다는 것입니다. 두 세계의 장점인 인덱스당 더 적은 수의 샤드를 달성할 수 있다는 것입니다.

이러한 학습을 ​​적용하여 중요한 성능 향상과 비용 절감을 실현할 수 있었습니다.

  • 두 클러스터의 비용을 각각 40%와 25% 절감했으며 다른 클러스터에서도 상당한 비용 절감 효과를 보았습니다.
  • 특정 클러스터에 대한 평균 CPU 사용률을 25% 줄이고 요청 대기 시간 중앙값을 100% 개선했습니다. 높은 트래픽 인덱스를 원본에 비해 파티션당 기본 샤드 수가 적은 파티션으로 마이그레이션하여 이를 달성했습니다.
  • 인덱스를 마이그레이션할 수 있는 일반적인 기능을 통해 인덱스의 스키마를 변경하여 제품 엔지니어가 고객을 위한 더 나은 경험을 구축하거나 Elasticsearch 8로 업그레이드할 수 있는 새로운 Lucene 버전을 사용하여 데이터를 다시 인덱싱할 수 있습니다.

그림 5 부하 불균형 및 CPU 활용도 개선

(그림 5) 높은 트래픽 인덱스를 파티션당 기본 샤드가 더 적은 파티션으로 마이그레이션하여 로드 불균형이 50% 개선되고 CPU 사용률이 25% 개선되었습니다.

그림 6 중앙값 요청 지연 시간

(그림 6) 트래픽이 높은 인덱스를 파티션당 기본 샤드가 더 적은 파티션으로 마이그레이션하여 요청 지연 중앙값이 평균 100% 향상되었습니다.

무엇 향후 계획?

새로운 제품과 기능을 강화하기 위해 Elasticsearch를 소개하는 것은 간단해야 합니다. 우리의 비전은 최신 웹 프레임워크가 관계형 데이터베이스와 상호 작용할 수 있도록 하는 것처럼 엔지니어가 Elasticsearch와 상호 작용하는 것을 간단하게 만드는 것입니다. 팀은 요청이 처리되는 방식에 대해 걱정할 필요 없이 인덱스 생성, 인덱스 읽기 또는 쓰기, 스키마 변경 등을 쉽게 수행할 수 있어야 합니다.

Intercom에서 엔지니어링 팀이 일하는 방식에 관심이 있습니까? 여기에서 자세히 알아보고 우리의 열린 역할을 확인하십시오.

채용 CTA - 엔지니어링(가로)