Incident Library

IL-0499 Discord · 2026-03-25kubernetesgcp

세션 서버가 대량으로 내려간 뒤 음성 통화 라우팅 서비스가 과부하로 멈춰 음성·영상 통화가 세 시간 넘게 연결되지 않았다

Discord 의 Realtime Infrastructure 팀은 Elixir 서비스를 쿠버네티스로 옮기는 중이었고, sessions 서비스의 이전을 마무리하고 있었다. sessions 서비스는 접속한 기기마다 세션 프로세스를 하나씩 두고, 클라이언트로 보내는 모든 것이 세션을 거친다. 호스트를 내릴 때는 그 호스트의 모든 프로세스가 다른 노드로 데이터를 넘긴 뒤에 파드를 종료해야 한다. 팀은 주말 CPU 사용률을 낮추려고 파드를 수직 확장하고 파드 수를 줄이는 변경을 2026년 3월 25일 12:13 PDT에 첫 번째 존에 배포했다. 쿠버네티스가 그 존의 파드 50% 를 종료했고, 핸드오프가 시작되기 전에 종료 유예 시간이 끝나 Discord 전체 세션의 17% 가 갑자기 멈췄다.

세션이 멈추자 process monitor 가 그 세션을 감시하던 모든 프로세스에 {:DOWN, …} 메시지를 보냈다. us-east1-b 의 gateway 는 영향을 받은 사용자에게 재접속을 지시했다. 세션 시작 속도 제한은 마이그레이션 뒤 다시 맞추지 않아 파드 수가 늘어난 만큼 더 많은 요청을 통과시켰고, us-east1-b 의 gateway 노드가 메모리 부족으로 재시작했다. 남은 사용자들의 gateway 연결도 끊겨 다른 존으로 다시 접속했다.

많은 세션이 음성 통화에서 빠졌다가 다시 들어오면서 voice syncers 서비스가 전 세계 25,000대가 넘는 SFU 인스턴스로 HTTPS 연결을 대량으로 만들었다. voice syncers 서비스는 활성 음성 상태를 보고 통화를 어느 SFU 에 둘지 정하고 관리한다. voice syncers 는 내부 라이브러리 Holster 로 Erlang HTTP 클라이언트 gun 의 연결을 풀링한다. 새 연결을 만들 때마다 두 GenServer 프로세스를 거치는데, 하나는 Holster.Pool 의 supervisor 이고 다른 하나는 모든 gun 연결을 맡는 supervisor 다. 이 supervisor 들은 자식 프로세스를 만들 때 selective receive 로 메일박스를 훑으며 응답을 기다리므로, 메일박스가 커질수록 자식 생성이 느려져 끝내 따라잡지 못한다. 장애 뒤 시험에서 메일박스 대기열이 약 10만일 때 프로세스 생성에 약 1ms 가 더 걸렸다. 분산 키-값 저장소인 etcd 는 Elixir 서비스의 서비스 디스커버리를 맡는다. etcd 연결도 같은 supervisor 를 거치므로, 등록 갱신이 막힌 인스턴스는 60초 TTL 이 지나 etcd 에서 빠졌다.

팀은 12:43에 인스턴스 2-13 의 voice syncers 애플리케이션을, 12:47에 인스턴스 2-1 의 Holster.Pool DynamicSupervisor 를 재시작했다. 두 인스턴스 모두 잠깐 회복했지만, 링 밖으로 빠진 비정상 인스턴스의 두 번째, 세 번째 장애 조치 대상으로 트래픽을 더 받으면서 메일박스가 다시 불어났다. 13:05에 클러스터 전체를 재시작했지만 수백만 통화를 한꺼번에 다시 만드는 부하 때문에 13:09에 모든 노드의 메일박스가 다시 늘었다. 통화를 소유하는 서비스의 syncer 생성 속도 제한은 낡았거나 설정돼 있지 않았다. guilds 의 제한은 길드마다 coordinator syncer 하나만 제한했고, 음성 채널마다 만드는 자식 syncer 는 제한하지 않았다. 13:43에 calls, streams, guilds 서비스에 낮은 syncer 생성 제한을 넣고 다시 재시작하자, 막히는 supervisor 가 gun supervisor 쪽으로 바뀌어 인스턴스들이 etcd 등록을 유지하고 일부 RPC 를 보냈다.

14:03에 인스턴스 2-3 재시작이 완전히 성공했고, 14:15까지 다섯 대를 재시작해 네 대가 완전히 회복했다. 같은 때 Salt 로 준비한 인스턴스 15대가 추가돼 용량이 두 배가 됐고 인스턴스당 syncer 수가 절반이 됐다. 14:26에 마지막으로 막혀 있던 인스턴스를 재시작해 두 배로 늘어난 클러스터가 완전히 정상이 됐다.

심각도
–
영향 범위
Voice and video calls on Discord: users mostly could not start or join calls; 17% of sessions stopped and gateway connections in us-east1-b were cut
영향 사용자
–
지역
us-east1-b for sessions and the gateway; voice syncer traffic globally
감지까지
–
복구까지
3h 17m

증상

2026년 3월 25일 12:13 PDT부터 15:30 PDT까지 Discord 의 음성·영상 기능이 크게 망가졌다. 사용자 대부분이 통화를 시작하거나 참여하지 못했고, 통화 상태에는 “Awaiting Endpoint” 메시지가 떴다.

12:13 PDT에 설정 변경이 첫 번째 존에 배포되자 쿠버네티스가 그 존의 sessions 서비스 파드 50% 를 종료했다. 프로세스 핸드오프가 시작되기 전에 쿠버네티스의 종료 유예 시간이 끝났고, 세 존에 고르게 나뉜 세션 가운데 17% 가 정상 종료 절차 없이 멈췄다.

세션이 멈추자 그 세션을 감시하던 프로세스마다 {:DOWN, …} 메시지가 쏟아졌다. us-east1-b 의 gateway 프로세스들이 이 메시지를 받고 클라이언트에 재접속을 지시했다. 세션 시작 속도 제한은 마이그레이션 뒤 다시 조정하지 않은 상태여서, 예전 VM 구성보다 늘어난 파드 수만큼 더 많은 세션 시작을 통과시켰다. us-east1-b 의 모든 gateway 노드에서 메모리 사용량이 치솟아 메모리가 바닥났고 노드가 재시작했다. 이 때문에 us-east1-b 에 남아 있던 사용자의 gateway 연결도 끊겼고, 사용자들은 us-east1-c 와 d 로 다시 접속했다.

같은 12:13부터 voice syncers 인스턴스 15대 전체의 프로세스 메일박스 길이 합계가 늘기 시작했고, 통화 참가자 사이의 종단 간 암호화 미디어를 중계하는 SFU 로 가는 RPC 가 급감했다. 인스턴스들은 etcd 기반의 일관성 해시 링에서 빠졌고, 새 syncer 를 받지 않았으며, RPC 요청을 보내지 못했다. etcd 를 조회하자 voice syncers 인스턴스 대부분이 등록하지 않은 채 링 밖에 있었다. Elixir 와 Erlang 운영 시스템의 문제를 진단하는 도구인 Recon 으로 실행 중인 Erlang 프로세스를 메일박스 대기열 길이 순으로 정렬하자 Holster.Pool 의 DynamicSupervisor 가 맨 위에 있었다.

인스턴스 2-8 하나만 메일박스를 비우고 etcd 에 다시 등록했고, 이 때문에 엔드포인트 선택과 RPC 생성 성공이 잠깐 치솟았다. 팀은 인스턴스를 하나씩 재시작하고, Holster.Pool DynamicSupervisor 만 따로 재시작하고, 클러스터 전체를 재시작해 봤다. 그때마다 인스턴스가 잠깐 회복했다가 메일박스가 다시 불어났다.

증상 서비스 중단

원인

촉발
Elixir 서비스를 쿠버네티스로 옮기던 중, 주말 CPU 사용률을 낮추려고 sessions 서비스 파드의 CPU 와 메모리를 늘리고 파드 수를 그만큼 줄이는 변경을 배포했다. 레플리카 수가 줄자 쿠버네티스가 첫 번째 존의 파드 50% 를 종료했고, 진행 중인 다른 작업을 기다리는 안전 검사 때문에 핸드오프가 시작되기 전에 종료 유예 시간이 끝났다.

감지

–

조치

후속 조치

교훈

충분히 큰 트래픽 급증은 시스템 어딘가의 병목을 찾아낸다. 이전의 큰 부하 장애에서는 여러 ScyllaDB 데이터베이스가 복구를 막았지만, 이번에는 데이터베이스가 잘 버텼고 병목이 음성 서비스로 옮겨 갔다. 용량 한계를 만나면 공급을 늘리거나 수요를 줄이는 두 길이 있고, Discord 는 장애 대응과 사후 조치를 정할 때 이 질문을 분명히 던진다. 장애 중에는 쓸 수 있는 도구를 알아야 한다. Elixir 와 BEAM 가상 머신에서는 어떤 프로세스의 상태든 꺼내 볼 수 있고 핫패치로 관측을 더할 수도 있다. 분산 시스템에서는 장애 모드를 따져야 한다. Elixir 프로세스는 메시지를 하나씩 처리하므로 바쁜 프로세스의 핵심 루프가 느려지면 서비스와 사용자 경험이 함께 나빠진다. Discord 는 사후 검토에서 장애를 어떻게 피할지뿐 아니라 일어난 뒤 어떻게 더 빨리 풀지도 물었고, 복구 시간을 줄이면 관계없는 장애에서도 더 빨리 회복할 수 있다고 봤다.

출처

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 파드 종료 처리, 코디네이션 키-값 저장소, 데이터베이스 프록시와 커넥션 풀러. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.