elixir

2 개의 포스트

discord4분 읽기큐레이션 요약

메일이 (너무 많이) 도착했습니다: 3/25/26 음성 서비스 장애의 이면

Discord의 2026년 3월 25일 음성·영상 장애는 세션 관리 서버의 설정 변경에서 시작되어 여러 시스템을 거친 연쇄 장애로 확대됐다. Kubernetes 마이그레이션 중 세션 서버의 복제본 수를 줄이자 한 가용 영역에서 세션의 상당 부분이 비정상 종료됐고, 재연결·상태 정리 트래픽이 급증했다. 그 결과 사용자는 통화에 참여하지 못하고 “Awaiting Endpoint” 메시지를 보았으며, 장애는 12:13부터 15:30 PDT까지 이어졌다. ## 장애의 규모와 직접적인 증상 - 장애 발생 시점은 3월 25일 12:13 PDT, 복구 시점은 15:30 PDT였다. - Discord의 음성·영상 통화를 시작하거나 참여하기 어려웠다. - 많은 사용자가 통화 상태에서 **“Awaiting Endpoint”** 메시지를 확인했다. - 세션 관리 서버의 약 17%가 동시에 사라지면서 후속 시스템에 대규모 부하가 전달됐다. - 최종적으로 음성·영상 통화를 적절한 서버로 라우팅하는 서비스가 과부하를 겪었다. ## Kubernetes 마이그레이션과 변경 배경 - Discord는 Elixir 기반 실시간 서비스를 Kubernetes 환경으로 이전하고 있었다. - Elixir 서비스는 각 호스트에서 수천 개의 상태를 가진 프로세스를 실행한다. - 길드 - 사용자 presence - 통화 - 사용자 세션 - 서버를 종료할 때는 프로세스의 상태를 다른 노드로 넘긴 뒤 종료해야 서비스 중단을 피할 수 있다. - 배포 시스템은 서버의 엔터티 수가 0이 될 때까지 기다린 후 pod를 종료하도록 설계돼 있었다. - 주말 CPU 사용률이 높아지자 다음과 같은 리소스 조정을 시도했다. - pod당 CPU와 메모리 증가 - 전체 pod 수를 비례적으로 감소 - 스케줄러 사용량이 pod 수에 따른 고정 비용인지 측정 ## 세션 서버의 비정상 종료 - 변경 사항은 먼저 한 가용 영역에 배포됐다. - 복제본 수를 줄이는 과정에서 Kubernetes가 해당 영역의 pod 중 50%를 종료했다. - 서비스는 Kubernetes의 종료 신호를 받으면 프로세스를 다른 노드로 이전하려고 한다. - 그러나 진행 중인 다른 이벤트가 끝날 때까지 기다리는 안전 검사 때문에, Kubernetes의 종료 유예 시간이 먼저 만료됐다. - 결과적으로 프로세스 핸드오프가 시작되기 전에 pod가 종료됐다. - 세 영역이 균등하게 구성돼 있었기 때문에 Discord 전체 세션의 약 17%가 비정상적으로 중단됐다. ## Elixir GenServer와 모니터링 메시지의 폭증 - Discord의 실시간 시스템은 Elixir의 `GenServer` 프로세스를 기반으로 동작한다. - 각 프로세스는 자신의 mailbox에서 한 번에 하나의 메시지만 처리한다. - 단일 메시지 처리 방식은 동시성 문제를 줄인다. - 반대로 짧은 시간에 메시지가 폭증하면 처리 지연이 발생할 수 있다. - Elixir의 `Process.monitor` 또는 Discord의 확장형 `ZenMonitor`는 감시 대상 프로세스가 종료되면 `{:DOWN, ...}` 메시지를 전달한다. - 세션 17%가 동시에 종료되면서 해당 세션을 감시하던 여러 프로세스에 종료 알림이 일제히 전송됐다. - 이 메시지 폭풍은 실시간 시스템 전반으로 전파됐고, 가장 먼저 Gateway 서비스에 영향을 미쳤다. ## 사용자 재연결로 확대된 부하 - Gateway 서비스는 Discord의 모든 WebSocket 트래픽에 대한 입구이자 출구 역할을 한다. - 클라이언트가 연결되면 Gateway는 세션 서비스를 통해 사용자 세션을 만들고, 길드·채널·DM 등의 데이터를 전달한다. - 세션의 갑작스러운 종료는 클라우드 장애, 네트워크 오류, 클라이언트 환경 등에서도 발생할 수 있으므로 Gateway는 이를 감지하고 재연결을 유도한다. - 세션이 끊기면 Gateway는 사용자를 즉시 재연결시키고, 기존 세션을 낙관적으로 복구하려 한다. - 이번 장애에서는 대규모 세션 종료가 동시에 발생하면서 정상적인 복구 절차 자체가 대규모 재연결 부하로 변했다. - 제공된 글 내용은 이 재연결 과정과 이후 음성 라우팅 시스템의 상세한 연쇄 장애를 설명하기 직전에서 끝난다. ## 실용적인 교훈 - 상태를 가진 서비스를 Kubernetes에서 축소할 때는 replica 감소 자체보다 **상태 이전과 종료 유예 시간의 상호작용**을 검증해야 한다. - 장애 복구용 재연결 로직도 대규모 동시 장애에서는 부하 증폭기가 될 수 있으므로 재연결 속도 제한과 단계적 복구가 필요하다. - 한 서비스의 작은 설정 변경이 모니터링 메시지, WebSocket 재연결, 음성 라우팅 등 여러 계층을 거쳐 전혀 다른 병목을 압박할 수 있다. - 분산 시스템 변경은 단일 서비스의 CPU·메모리 지표뿐 아니라 장애 전파 경로와 최악의 동시 부하까지 함께 검증해야 한다.

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

디스코드의 엘릭 (새 탭에서 열림)

Discord는 Elixir의 강력한 동시성 메커니즘을 활용하여 각 서버(길드)를 독립적으로 운영함으로써 수억 명의 사용자에게 실시간에 가까운 채팅 경험을 제공합니다. 그러나 급격한 트래픽 증가로 시스템 자정 능력이 한계에 도달할 때, 기존의 메트릭이나 자체 개발한 메모리 기반 분석 도구만으로는 복잡한 성능 병목 현상과 사용자 경험의 실질적인 저하 원인을 파악하는 데 한계가 있었습니다. 이를 해결하기 위해 Discord는 Elixir 환경에 맞춤화된 분산 추적(Distributed Tracing) 시스템을 직접 구축하여 서비스 중단 없이 시스템 전반의 가시성을 확보하는 데 성공했습니다. **기존 관측 도구의 한계와 실무적 어려움** * **지표와 로그의 한계:** 대시보드는 엔진 온도계처럼 시스템의 상태를 보여주지만, 온도가 높을 때 사용자가 느끼는 실제 주행 경험(지연 시간의 체감 등)이나 구체적인 결과까지는 설명해주지 못합니다. * **길드 타이밍(Guild Timings) 도구:** 길드별 작업 소요 시간을 분 단위로 메모리에 기록하는 커스텀 도구를 사용해왔으나, 데이터 양이 너무 방대하여 대형 길드를 제외하고는 데이터를 빠르게 순환(Rotation)시켜야 하므로 과거 이력 분석이 어렵습니다. * **다운스트림 효과 파악 불가:** 기존 도구들은 개별 작업의 소요 시간은 보여주지만, 해당 작업이 연쇄적으로 일으키는 다운스트림 서비스의 영향과 전체적인 실행 흐름을 시각화하지 못하는 단점이 있었습니다. **Elixir 환경에서의 분산 추적 도입 과정** * **분산 추적(APM)의 필요성:** 작업의 구성 요소별 소요 시간을 한눈에 파악할 수 있는 분산 추적 기술을 통해 시스템 내부의 복잡한 상호작용을 투명하게 확인하고자 했습니다. * **기술적 난관:** 일반적인 추적 도구는 HTTP 헤더와 같은 메타데이터 레이어를 통해 추적 정보를 전달하지만, Elixir의 기본 통신 도구들에는 이러한 메타데이터 레이어가 내장되어 있지 않았습니다. * **커스텀 메타데이터 레이어 구축:** 서비스 간 통신 방식에 추적 정보를 함께 전달할 수 있는 자체 메타데이터 전달 메커니즘을 설계하여 문제를 해결했습니다. * **무중단 통합:** 서비스 간의 통신 방식을 근본적으로 변경하는 작업임에도 불구하고, 철저한 설계를 통해 시스템 가동 중단(Downtime) 없이 새로운 추적 시스템을 성공적으로 통합했습니다. 복잡한 분산 시스템에서 단순한 성능 지표만으로는 문제의 근본 원인을 파악하기 어렵습니다. 특히 Elixir와 같이 특수한 통신 구조를 가진 환경에서는 표준적인 APM 도구를 그대로 적용하기보다, 시스템의 특성에 맞춰 메타데이터 전달 계층을 직접 구현함으로써 인프라 전반의 흐름을 명확히 파악할 수 있는 분석 환경을 구축하는 것이 중요합니다.