discord4분 읽기

큐레이션 요약

디스코드가 수조 개의

원문 읽기(새 탭에서 열림)

Discord는 메시지 검색을 Elasticsearch 기반으로 운영했지만, 메시지와 트래픽이 조 단위로 증가하면서 Redis 큐 유실, 장애에 취약한 벌크 색인, 대규모 클러스터의 운영 부담, 단일 인덱스의 문서 수 제한 문제가 발생했다. 이를 해결하기 위해 Kubernetes와 Elastic Kubernetes Operator(ECK)를 도입하고, 거대한 클러스터 대신 여러 개의 작은 클러스터를 운영하는 셀(cell) 아키텍처로 전환하려 했다. 목표는 장애 격리, 무중단 업그레이드, 확장성, 비용 효율성을 동시에 확보하는 것이었다.

기존 메시지 검색 구조

  • 2017년 Discord는 Elasticsearch에 메시지를 색인했다.
  • 메시지는 Discord 서버(guild) 또는 DM 단위로 Elasticsearch 인덱스에 분산했다.
    • 같은 guild의 메시지를 한곳에 모아 검색 속도를 높였다.
    • 클러스터를 여러 개로 나누어 관리 가능한 규모를 유지했다.
  • 사용자가 검색을 이용하지 않는 경우를 고려해 메시지는 지연 색인(lazy indexing)했다.
  • Redis 기반 메시지 큐와 작업자가 메시지를 묶음으로 가져와 Elasticsearch 벌크 색인을 수행했다.

Redis 메시지 큐의 메시지 유실

  • Elasticsearch 장애로 색인 작업이 밀리면 Redis 큐에 메시지가 빠르게 쌓였다.
  • 큐가 과도하게 커지면서 Redis CPU 사용량이 한계에 도달했고, 결국 메시지가 유실됐다.
  • 즉, 색인 대상 메시지를 안정적으로 보관해야 할 큐 자체가 장애 지점이 되었다.

장애에 취약한 벌크 색인

  • 한 번의 벌크 요청에 서로 다른 인덱스와 Elasticsearch 노드에 속한 메시지가 함께 포함됐다.
  • 예를 들어 50개 메시지를 색인하는 요청이 최대 50개 노드로 분산될 수 있었다.
  • 그중 단 하나의 노드라도 실패하면 벌크 요청 전체가 실패하고, 모든 메시지를 다시 큐에 넣어 재시도해야 했다.
  • 100개 노드 클러스터에서 50개 메시지를 무작위로 색인할 때, 특정 노드 하나가 장애 나면 요청 중 약 40%가 실패할 수 있었다.
  • 실제 장애 하나가 색인 실패와 재시도를 대량으로 유발해 Redis 큐 적체를 악화시켰다.

대규모 Elasticsearch 클러스터의 운영 부담

  • 메시지와 guild 수가 증가할 때 노드와 인덱스를 추가하는 방식으로 수평 확장했다.
  • 하지만 클러스터가 커질수록 하나의 벌크 작업이 더 많은 인덱스와 노드로 분산됐다.
  • 이로 인해 노드 간 조정과 네트워크 팬아웃 비용이 커져 색인 성능이 저하됐다.
  • 노드 수가 많아질수록 어느 한 노드에서 장애가 발생할 가능성도 증가했다.

롤링 재시작과 보안 업데이트의 어려움

  • 단일 노드 장애에도 색인 시스템이 크게 영향을 받았기 때문에 안전한 롤링 재시작이 어려웠다.
  • 200개가 넘는 노드와 수 테라바이트의 데이터를 가진 클러스터를 노드별로 비우고 재시작하는 데 지나치게 오랜 시간이 걸렸다.
  • 그 결과 오래된 운영체제와 Elasticsearch 버전을 계속 사용해야 했고, 보안 패치와 성능 개선을 적용하지 못했다.
  • Log4Shell 대응 당시에는 log4j2.formatMsgNoLookups=true 설정을 적용하기 위해 전체 검색 시스템을 중단하고 모든 노드를 재시작해야 했다.

대형 guild의 인덱스 크기 제한

  • 일부 인덱스에는 매우 큰 guild의 메시지가 집중됐다.
  • Elasticsearch 인덱스는 내부적으로 하나의 Lucene 인덱스이며, 약 20억 개 문서라는 MAX_DOC 제한이 있다.
  • 이 한도에 도달하면 해당 인덱스의 모든 색인 작업이 실패한다.
  • 당시에는 Safety 팀과 협력해 스팸 목적의 guild를 찾아 삭제하는 방식으로 복구했다.
  • 그러나 정상적인 대규모 커뮤니티가 20억 개 이상의 메시지를 축적하는 상황도 지원해야 했다.

Kubernetes와 Elastic Operator 도입

  • Discord는 무상태 서비스 운영에서 이미 Kubernetes의 편의성과 비용 최적화 효과를 경험하고 있었다.
  • 이후 Elasticsearch 같은 상태 저장 서비스에도 Elastic Kubernetes Operator(ECK)를 적용하는 방안을 선택했다.
  • ECK를 사용하면 다음을 선언적으로 관리할 수 있다.
    • Elasticsearch 클러스터 토폴로지
    • 노드 구성과 설정
    • Kubernetes 노드풀 위의 클러스터 배포
  • 운영체제 업그레이드를 자동화하고, 안전한 롤링 재시작과 Elasticsearch 업그레이드 도구를 활용할 수 있게 됐다.

여러 소형 클러스터를 사용하는 셀 아키텍처

  • 기존처럼 200개가 넘는 노드를 가진 거대한 클러스터 하나를 운영하는 대신, 더 많은 수의 작은 Elasticsearch 클러스터를 운영하는 구조를 구상했다.
  • 작은 클러스터는 다음과 같은 이점을 제공한다.
    • 장애 범위 축소
    • 클러스터 자체의 조정 오버헤드 감소
    • 노드 장애가 전체 검색 시스템에 미치는 영향 완화
    • 업그레이드와 유지보수의 단순화
  • Kubernetes와 ECK를 기반으로 이러한 클러스터들을 표준화하고 운영하려 했다.

대규모 검색 시스템에서는 단순히 노드를 추가하는 것보다 장애 격리와 운영 가능성을 함께 설계하는 것이 중요하다. 특히 벌크 작업을 부분 실패에 강하게 만들고, 클러스터를 작은 단위로 나누며, 롤링 업그레이드가 가능한 플랫폼을 채택하는 것이 장기적인 안정성에 유리하다.

큐레이션 요약을 이어서 읽어보세요.