피드로 돌아가기
50,000 Goroutines Took Down Prod at 3 AM. The Fix Is One Line
Dev.toDev.to
Backend

Context leak 해결로 메모리 47GB에서 400MB로 급감 및 p99 80ms 복구

50,000 Goroutines Took Down Prod at 3 AM. The Fix Is One Line

Gabriel Anhaia2026년 4월 28일13intermediate

Context

WebSocket 기반 알림 서비스에서 클라이언트 연결 종료 시 Context Cancel 함수가 호출되지 않는 구조적 결함 존재. 이로 인해 종료되지 않은 Goroutine과 연관 리소스가 메모리에 누적되는 Memory Leak 발생.

Technical Solution

  • context.WithCancel로 생성한 cancel 함수를 Subscription 구조체에 캡처하여 관리하는 설계 채택
  • WSHandler 내 WebSocket 연결 종료 시 defer를 통해 Unsubscribe 함수를 강제 호출하는 로직 추가
  • Unsubscribe 과정에서 cancel() 실행을 통한 pumpEvents Goroutine의 ctx.Done() 시그널 트리거 및 종료 유도
  • 서비스 레벨 Map에서 구독 정보를 제거하여 GC가 도달 불가능한 상태로 만들어 메모리 회수 가능 구조 확보
  • Goroutine 자체의 경량성보다 해당 루틴이 점유한 buffered channel, user state 등 Closure 리소스의 누적 방지에 집중

- Prometheus 대시보드에 `go_goroutines` 메트릭을 추가하여 추세선이 평탄하거나 톱니 모양인지 상시 모니터링 - `context.WithCancel` 사용 시 생성 직후 동일 라인에서 `defer cancel()` 호출 습관화 - `goleak` 라이브러리를 CI 파이프라인에 통합하여 동시성 패키지의 리소스 누수 자동 검증 - PR 리뷰 시 "해당 Goroutine을 종료시키는 트리거가 무엇이며, 반드시 실행되는가?"를 핵심 검토 항목으로 설정

원문 읽기