SSE 좀비 소켓 누적으로 인한 OOM — 3단 방어로 해결
1. 상황 및 문제
운영진 대시보드가 SSE로 실시간 인증 현황을 받는데, 브라우저 새로고침이 반복되면 서버에 FIN_WAIT2·CLOSE_WAIT 상태의 좀비 소켓이 누적되었고, 결국 메모리 부족(OOM)으로 이어졌습니다.
2. 원인 분석
클라이언트가 끊겨도 서버의 SseEmitter와 그 뒤의 커넥션이 즉시 정리되지 않는 것이 원인이었습니다. Nginx의 proxy_read_timeout이 3600초로 설정되어 있어, 죽은 커넥션이 한 시간 동안 유지되는 구조였습니다.
3. 해결 단계
Step 1. completeWithError()로 끊긴 연결 즉시 강제 종료
전송 실패가 감지되면 completeWithError()로 emitter를 즉시 종료해, 끊긴 연결이 서버에 남지 않게 했습니다.
Step 2. 45초 주기 Heartbeat로 죽은 연결 조기 감지
주기적으로 heartbeat 이벤트를 보내, 이미 끊긴 연결에서 전송 예외가 빨리 발생하도록 만들었습니다. 감지가 빨라지면 정리도 빨라집니다.
Step 3. Nginx proxy_read_timeout 3600s → 80s
Heartbeat 주기(45초)보다 여유 있게 80초로 줄여, 정상 연결은 유지되면서 죽은 커넥션은 Nginx 레벨에서도 빠르게 회수되게 했습니다.
location /sse { proxy_pass http://app; proxy_read_timeout 80s; # heartbeat 45s + 여유 proxy_set_header Connection ''; proxy_http_version 1.1; }heartbeat 주기에 맞춘 타임아웃 — 죽은 커넥션을 Nginx가 회수
4. 검증 및 결과
새로고침을 반복해도 FIN_WAIT2·CLOSE_WAIT 소켓이 누적되지 않는 것을 확인했고, OOM이 재발하지 않았습니다. 애플리케이션·프록시 양쪽에서 커넥션 수명이 관리되는 구조가 되었습니다.