Netty | 반복된 연결 실패와 java.lang.OutOfMemoryError 문제

주싱·2022년 4월 15일
0

Netty

목록 보기
7/9

01. 문제 현상

테스트 삼아 AWS EC2 인스턴스 (1G 메모리) 에 올린 통신 서버(Netty 기반)에서 java.lang.OutOfMemoryError 예외가 발생합니다. 간단히 jconsole 을 사용해 문제를 모니터링해 보니 쓰레드 개수가 선형적으로 증가하고 있었습니다.

02. 문제 코드

예외가 발생한 시점의 콜스택을 확인해 보니 통신 파이프라인에 특정 핸들러를 추가하며 문제가 발생했습니다. 그리고 해당 핸들러는 Blocking 이 발생하는 특성을 가지고 있어서 채널의 공유 I/O 쓰레드가 아닌 전용 쓰레드를 할당하고 있었습니다.

03. 원인

Netty 프레임워크는 TCP 연결을 시도하기 전에 연결 후 메시지 처리를 담당하는 파이프라인을 먼저 구성합니다. 이 후에 만약 TCP 연결이 실패했다면 사용자가 등록한 전용 쓰레드는 비관리자원(Unmanaged Resource)이기 때문에 사용자가 직접 해제해 주어야 할 책임이 있습니다. 우리 시스템에서는 연결 실패 시에 문제가 발생한 핸들러에 할당한 전용 쓰레드 자원을 해제해 주지 않고 있었습니다. 그리고 타겟 시스템이 연결 가능한 상태가 되는 즉시 연결을 이어가기 위해 약 3초에 한 번씩 재연결 시도를 함으로 3초 마다 자원 해제는 없이 새로운 쓰레드가 생성되고 있었습니다.

04. 수정하기

Netty 프레임워크는 TCP 연결 실패 시 핸들러에 할당된 비관리 자원(쓰레드, 파일 등)을 해제해 줄 수 있는 매커니즘을 제공합니다. 핸들러에서 ChannelHandler.handlerRemoved() 인터페이스를 재정의하면 연결 실패 시 해당 이벤트 메서드가 호출되고 메서드 내에서 비관리 자원을 해제해 줄 수 있습니다. 아래와 같이 간단히 수정하였습니다.

위 수정 시 주의할 점이 있는데 shutdownGracefully() 호출을 통해 쓰레드 종료를 요청한 후 sync() 를 통해 완료를 대기해서는 안된다는 것입니다. 생각해보면 현재 이벤트 핸들러를 실행하고 있는 전용 쓰레드 A 가 A 에게 실행을 종료하라고 요청하고 있는 샘인데, A 가 A 스스로 종료를 대기 하고 있으니 데드락 상황이 발생하게 되기 때문입니다. 중요한 내용이라 주석을 달아두었습니다.

05. 결과확인

수정 이후 결과를 비교해보면 실행 초반에 쓰레드가 증가하다가 이후부터는 안정화되는 것을 확인할 수 있습니다.

profile
소프트웨어 엔지니어, 일상

0개의 댓글