사례2건
2건 중 1~2번째
- 증상
2026년 3월 25일 12:13 PDT부터 15:30 PDT까지 Discord 의 음성·영상 기능이 크게 망가졌다. 사용자 대부분이 통화를 시작하거나 참여하지 못했고, 통화 상태에는 “Awaiting Endpoint” 메시지가 떴다.
- 촉발
Elixir 서비스를 쿠버네티스로 옮기던 중, 주말 CPU 사용률을 낮추려고 sessions 서비스 파드의 CPU 와 메모리를 늘리고 파드 수를 그만큼 줄이는 변경을 배포했다. 레플리카 수가 줄자 쿠버네티스가 첫 번째 존의 파드 50% 를 종료했고, 진행 중인 다른 작업을 기다리는 안전 검사 때문에 핸드오프가 시작되기 전에 종료 유예 시간이 끝났다.
- 원인
- 구성 요소
- 기여 요인
- 조치
- 13:43 PDT까지 calls, streams, guilds 서비스에 낮은 syncer 생성 제한을 넣고 voice syncers 클러스터를 다시 재시작했다. 막히는 supervisor 가 gun supervisor 로 바뀌면서 인스턴스들이 etcd 등록을 유지했고 일부 syncer 의 RPC 를 SFU 로 보냈다.
- 인스턴스를 골라 하나씩 다시 재시작했고, 14:03에 재시작한 인스턴스 2-3 처럼 일부 인스턴스가 완전히 회복했다.
- 인스턴스 15대를 추가로 올려 voice syncers 클러스터의 용량을 두 배로 늘렸다. 새 인스턴스들은 메일박스 대기열 문제 없이 syncer 를 받았고, 인스턴스당 syncer 수가 절반이 됐다.
- 14:26에 마지막으로 막혀 있던 인스턴스를 재시작해 두 배로 늘어난 클러스터가 완전히 정상이 됐다.
- 후속 조치
- Elixir 워크로드용 validating admission webhook 을 쿠버네티스 클러스터에 넣어, 파드가 프로세스를 다 넘기기 전에는 워크로드를 줄이는 업데이트를 거부한다.
- 부하 테스트 뒤 Holster.Pool 의 supervisor 를 PartitionSupervisor 로 바꿔, 연결 풀 요청을 독립된 여러 supervisor 에 나누고 나가는 연결을 동시에 만들거나 꺼낼 수 있게 했다.
- gun 연결 프로세스의 생명주기 관리를 그 프로세스를 만드는 Holster.Pool 로 옮겨 gun 연결 supervisor 를 아예 쓰지 않게 했다.
- voice syncers 를 호출하는 상위 서비스의 속도 제한을 다시 조정해 더 정확히 걸리게 했다. 필요하면 로드 셰딩 도구로 이 제한을 강제로 적용해 voice syncer 를 다시 만들게 할 수도 있다.
- voice syncers 의 엔드포인트 선택에 새 속도 제한을 넣고 조정해, 나가는 연결 생성을 제한하는 수단을 하나 더 뒀다.
- HTTP 연결 풀링 메일박스에 감시를 더해 언제 문제가 되고 언제 아닌지 더 분명히 알 수 있게 했다.
- 서비스 디스커버리 등록과 voice syncer 에서 SFU 로 가는 RPC 트래픽의 계측과 로그를 전부 검토하고 넓혔다.
- 메일박스 상태 합계 같은 간접 지표 대신, 모든 서비스에서 가장 중요한 프로세스를 직접 감시하도록 넓혀 간다.
- 더 많은 워크로드를 쿠버네티스로 옮겨, VM 을 손으로 준비하는 대신 명령 하나로 파드를 늘릴 수 있게 한다.
- A/V 인프라의 컨트롤 플레인 서비스를 여러 리전에 수평 확장해 신뢰성과 리전 장애 내성을 얻고, 나가는 연결 급증을 전 세계에 고르게 흩는다.
- 교훈
충분히 큰 트래픽 급증은 시스템 어딘가의 병목을 찾아낸다. 이전의 큰 부하 장애에서는 여러 ScyllaDB 데이터베이스가 복구를 막았지만, 이번에는 데이터베이스가 잘 버텼고 병목이 음성 서비스로 옮겨 갔다. 용량 한계를 만나면 공급을 늘리거나 수요를 줄이는 두 길이 있고, Discord 는 장애 대응과 사후 조치를 정할 때 이 질문을 분명히 던진다. 장애 중에는 쓸 수 있는 도구를 알아야 한다. Elixir 와 BEAM 가상 머신에서는 어떤 프로세스의 상태든 꺼내 볼 수 있고 핫패치로 관측을 더할 수도 있다. 분산 시스템에서는 장애 모드를 따져야 한다. Elixir 프로세스는 메시지를 하나씩 처리하므로 바쁜 프로세스의 핵심 루프가 느려지면 서비스와 사용자 경험이 함께 나빠진다. Discord 는 사후 검토에서 장애를 어떻게 피할지뿐 아니라 일어난 뒤 어떻게 더 빨리 풀지도 물었고, 복구 시간을 줄이면 관계없는 장애에서도 더 빨리 회복할 수 있다고 봤다.
- 증상
2020년 3월 16일 14:19 PDT 에 실시간 연결과 채팅 메시지를 처리하는 Elixir 서비스 대부분이 죽어 용량이 한순간에 사라졌고, 여기에 의존하는 다른 시스템으로 장애가 번졌다. 14:21 에 핵심 지표가 떨어졌다는 알림과 클러스터 사용률 이상 알림이 여럿 울렸고 SEV1 이 선언됐다. 대부분의 사용자가 Discord 에 접속하지 못했다.
- 촉발
2020년 3월 16일 14:18 PDT 에 Google Cloud 관리형 인스턴스 그룹 오토스케일러가 API 클러스터에 새 노드를 넣었고, 이 노드가 etcd 에 자신을 등록하는 도중 연결이 끊겨 값이 빈 키가 서비스 디스커버리에 쓰였다.
- 원인
- 구성 요소
- 기여 요인
- 감지
14:21 PDT 에 핵심 지표가 떨어졌다는 알림과 클러스터 사용률 이상 알림이 여럿 울렸고, SEV1 이 선언됐다.
- 조치
- 잃은 실시간 용량을 다시 띄우고 장애가 난 노드를 고쳤다.
- 서비스 복구를 돕기 위해 Discord 로 들어오는 접속을 조절하기 시작했다.
- 모두가 다시 접속하는 동안 자원이 바닥나지 않도록 ScyllaDB 클러스터 두 곳의 데이터베이스 유지 작업인 anti-entropy repair 를 멈췄다.
- 서비스가 회복되는 대로 접속 허용량을 계속 늘려 더 많은 사용자가 다시 접속하게 했다.
- 후속 조치
- 값이 잘못된 키가 쓰일 가능성을 줄이려고, etcd 클라이언트가 자신을 등록하는 요청을 TCP 패킷 두 개가 아니라 하나로 보내게 바꿨다.
- 키 값이 깨진 서비스는 무시하도록 오류 처리를 더해, 같은 일이 다시 나도 그 서비스가 발견되지 않을 뿐 크래시 루프에 빠지지 않게 했다.
- Go 프로젝트에 이 문제를 버그로 보고한다.
- 교훈
서비스 디스커버리 시스템은 etcd 안의 여러 장애에는 견디도록 만들었지만, 일시적인 네트워크 오류로 값 없는 키가 쓰일 수 있다는 것은 예상하지 못했다. 이 경우 Go 의 net/http 는 HTTP 1.1 규격을 어겨, Content-Length 헤더가 있고 본문이 불완전한 요청을 거부하지 않는다. 이 때문에 예상하지 못한 장애 상황이 하나 더 생겼다.
이 2건에서 가장 많이 나오는 구성 요소: 코디네이션 키-값 저장소, 데이터베이스 프록시와 커넥션 풀러. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.