Discord UI 갱신이 왜 장애가 되었나: 응답 제약과 Rate Limit를 동시에 다루는 설계
Discord 기반 상호작용 시스템에서 장애는 보통 단일 API 실패로 시작되지 않는다. 응답 속도를 높이기 위해 이벤트를 잘게 쪼개고 UI를 자주 갱신하는 설계가 누적되면, 호출 총량과 상태 전이 복잡도가 함께 증가한다. 이 글은 그 누적이 어떻게 Rate Limit 리스크로 수렴했는지, 그리고 이를 제어하기 위해 어떤 구조적 분리를 적용했는지 기술적으로 정리한다.
장애 신호를 하나의 문제로 묶기
초기에는 아래 현상이 서로 다른 버그처럼 보였다.
- 첫 메시지 구간 응답 누락
- 다중 메시지 출력 시 일부 메시지 손실
- 유휴 이후 첫 이벤트 실패(콜드 스타트 구간)
하지만 운영 로그를 시간축으로 정렬하면 공통 축이 명확하다.
- 상호작용 이벤트 1건당 메시지 발행 횟수 증가
- 발행 경로 내부에서 조회/검증/렌더링 로직이 중첩
- ACK 처리와 후속 작업이 같은 임계 구간에서 경쟁
결론적으로 문제의 본질은 “응답 경로의 과부하 + 상태 전이 결합"이다.
실패 메커니즘
1) 전송 경로 결합
기존 경로는 다음과 같은 형태였다.
interaction -> validation -> read(DB/KV) -> render -> send -> follow-up
핵심 문제는 send 이전에 변동성이 큰 작업이 과도하게 들어간다는 점이다.
이 구조에서는 한 단계 지연이 전체 응답 지연으로 전파되고, 재시도 시 동일 경로가 중복 실행된다.
2) 메시지 발행 정책 부재
요청 단위를 정의하지 않고 “보여줄 수 있는 상태를 즉시 노출"하는 정책을 택하면, 동일한 도메인 이벤트가 다수의 UI 메시지로 분해된다.
- 이벤트 폭증 시점: 상태 변화 > 메시지 집계 능력
- 결과: 호출량 증가, 중복 발행, 순서 역전
3) 재시도 비결정성
Idempotency 키 없이 재시도하면 다음 문제가 발생한다.
- 동일 이벤트의 중복 송신
- 동일 송신의 중복 후처리
- 관측 지표와 실제 사용자 체감의 불일치
구조 개편: 응답 경로를 2단계로 분리
핵심 변경은 “빠른 ACK 경로"와 “비동기 후속 경로"를 분리하는 것이다.
1) Fast Path (동기)
interaction -> auth/shape validation -> ack
- 목표: 가변 작업 최소화
- 금지: DB 다중 조회, 무거운 렌더링, 외부 I/O 체인
2) Async Path (비동기)
event queue -> state projection -> render -> follow-up send
- 역할: 상세 결과, 부가 설명, 장시간 작업
- 제약: 재시도 가능해야 하며 순서 보장이 필요할 때만 직렬화
전송 정책 설계
메시지 발행을 기능 단위가 아닌 정책 단위로 제한했다.
max_messages_per_interaction: 상호작용당 허용 메시지 상한coalesce_window_ms: 짧은 시간 내 상태 변경을 단일 메시지로 집계priority_lane: 오류/차단/필수 알림 우선 채널drop_policy: 저우선 상태 갱신은 샘플링 또는 생략
핵심은 “표현 가능한 변화"와 “전송 가능한 변화"를 분리하는 것이다.
상태/재시도 안정화
Idempotency 키
아래 조합으로 키를 고정했다.
<platform_event_id>:<route_version>:<intent_type>
재시도 시 동일 키가 감지되면 전송/후처리를 스킵하거나 병합한다.
상태 전이 규칙
- UI 상태는 도메인 상태의 파생값으로만 생성
- 파생 규칙 버전(
route_version)을 명시해 롤백 가능성 확보 - 전이 실패는 사용자 메시지 실패와 분리해 기록
관측 지표
리스크를 제어하려면 호출량보다 경계 지표를 먼저 본다.
ack_latency_p95events_per_interactionmessages_per_interactiondedupe_hit_ratetimeout_ratefirst_event_fail_rate
특히 events_per_interaction과 messages_per_interaction의 괴리가 커지면,
설계상 불필요한 표현 계층이 증가한 신호다.
트레이드오프
- 장점: 장애 전파 범위 축소, 재시도 결정성 확보, 디버깅 단위 명확화
- 단점: 큐/중복제거/상태 파생 버전 관리 비용 증가
- 감수한 비용: 구현 복잡도 증가
- 회피한 비용: 전체 경로 재시도에 따른 호출 폭증
다음 단계
- 인터랙션 타입별 전송 예산(메시지/조회 횟수) 강제
- 비동기 경로의 순서 보장 필요 구간만 직렬화
- 지표 기반 자동 완화(발행 상한 초과 시 강제 집계/샘플링)
참고 및 인용
참고: Discord는 글로벌/라우트 단위 Rate Limit를 문서화하고, 헤더 기반 제어를 권장한다. Rate Limits
참고: Interaction 응답 제약(초기 응답 + 후속 응답)을 분리하는 구조는 타임아웃 리스크를 줄인다. Receiving and Responding
참고: 재시도 폭주를 줄이기 위한 jitter/backoff 패턴은 대규모 API 운영의 기본 원칙이다. Timeouts, retries, and backoff with jitter