웹소켓 통신에서의 메모리 누수
현재 stomp 기반의 실시간 영상 스트리밍 기술을 개발하고 있다.
하지만 화면 새로고침이나 화면 이동 등의 상황에서 서버의 메모리가 급격하게 늘어나는 것을 확인했다.
이 부분을 해결하기 위해서 다음과 같은 방안들을 사용했다.
효과적인 소켓 연결 관리 방법
Stomp에서의 메모리 누수를 막기 위해서 서버에서만 동작이 필요한 것이 아니다.
프론트엔드와 백엔드 모두 적절한 처리를 해주어야 작업이 완료될 수 있다.
React에서 효과적으로 소켓 연결 관리하기
React에서 서버의 메모리 누수를 발생시키는 원인은 주로 불필요한 연결 유지나 중복 연결로 인해서다.
이를 위해서 적절한 시점에 적절한 방법으로 연결 및 연결 해제해야 한다
연결 해제 로직 관리하기
우선 이번 프로젝트에서는 다음과 같이 연결 해제용 함수를 만들어서 관리했다.
function disconnect(){
subscriptionRef.current.unsubscribe();
clientRef.current.deactivate();
}
이 코드에서는 useRef를 활용하여 연결정보와 구독정보를 관리하고 있다.
또한, 연결 해제 시 구독 해제 후 client 연결을 종료한다.
이를 통해서 중복과 실수를 줄일 수 있고, 공통으로 연결 해제 기능을 관리할 수 있기에 이점이 있다.
연결 해제 이벤트 관리
위 기능은 사실 매우 간단하고 누구나 할 수 있는 방법이다
하지만 위 함수를 적절한 때에 호출하지 않는다면 이는 중복 연결이나 메모리 누수로 이어질 수 있다.
연결 정보 변경 등의 당연한 상황을 제외하면 대표적으로 두 가지 이벤트에서 연결을 해제하는 것이 좋다.
- visibilityChange : 화면 활성화 / 비활성화에 대한 이벤트
- beforeunload : 현재 페이지를 떠나기 전에 발생
해당 이벤트에서 연결을 해제해야 정상적으로 작동하는 서비스를 만들 수 있고 메모리 누수를 줄일 수 있다.
Spring Boot에서 효과적으로 Stomp 연결 관리하기
다음으로는 서버에서 효과적으로 연결을 관리하고 자원을 관리하는 것이 중요하다.
실시간 스트리밍에서 다양한 방법으로 메모리 누수를 막을 수 있겠지만 이번에는 효과적인 웹소켓 연결 관리에 대해서만 알아보겠다.
Stomp Event 처리하기
첫 번째는 Stomp 연결 해제 및 구독 해제 이벤트 발생 시 필요 없는 자원을 제거하는 것이다.
대표적으로 아래 두 가지 이벤트에 대한 이벤트 핸들러를 만들어서 관리한다.
우선 아래 코드를 살펴보자.
@Component
@RequiredArgsConstructor
public class SocketEventHandler {
@EventListener
public void handleDisconnect(SessionDisconnectEvent event) {
// do something
}
@EventListener
public void handleDisconnect(SessionUnsubscribeEvent event) {
// do something
}
}
이 코드에서는 총 두 가지 이벤트에 대한 처리를 진행한다.
- SessionDisconnectEvent : Stomp 연결이 종료되었을 때 발생
- SessionUnsubscribeEvent : Stomp 구독 해제 시 발생
우선 이 정도면 정상적인 상황에 대해서는 연결을 효과적으로 관리할 수 있다.
하지만 예기치 않게 연결이 종료되는 경우에 대비해서 한 가지 더 설정할 수 있다.
Stomp HeartBeat
Stomp HeartBeat는 간단히 말해서 일정 간격으로 패킷을 전송하여 대상이 정상적으로 작동하는지 확인하는 것이다.
만약에 네트워크 장애 등으로 인해서 세션이 예기치 않게 종료되면 이는 메모리 누수로 이어질 수 있기에 중요한 작업이다.
우선 아래 코드를 살펴보자.
@Configuration
@RequiredArgsConstructor
public class SocketHeartbeatConfig implements WebSocketMessageBrokerConfigurer {
private final ThreadPoolTaskScheduler taskScheduler;
/**
* add stomp broker endpoint for subscription
* */
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/your/destination")
.setHeartbeatValue(new long[]{10000, 10000})
.setTaskScheduler(taskScheduler);
}
}
@Configuration
public class CustomTaskSchedular {
@Bean
public ThreadPoolTaskScheduler taskScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(50);
scheduler.setThreadNamePrefix("stomp-heartbeat-");
scheduler.setDaemon(true);
scheduler.initialize();
return scheduler;
}
}
이 코드에서는 구독 설정 중 HeartBeat 설정을 진행하고, 이 때 사용할 작업 스케줄러를 정의하고 있다.
- configureMessageBroker() : 구독 정보 설정
- setHeartbeatValue : 서버에서 클라이언트 / 클라이언트에서 서버로의 heart beat 시간을 의미한다.
- 해당 시간이 경과되면 server가 client의 / client가 server의 소켓 연결을 종료한다
- setTaskScheduler : heartbeat 처리용 스케줄러를 등록
- setHeartbeatValue : 서버에서 클라이언트 / 클라이언트에서 서버로의 heart beat 시간을 의미한다.
- taskScheduler() : 작업 스케줄러 설정
- setPoolSize : 사용할 쓰레드 수
- setTheradNamePrefix : 쓰레드 이름 앞에 붙는 접두사를 설정함
- setDaemon : 데몬 스레드로 설정하여 jvm이 종료되면 자동 종료됨
- initialize : 명시적으로 스레드 풀을 초기화함
위 코드 기준 10초 이내로 패킷이 오지 않으면 연결을 종료한다.
이걸 통해서 유령 세션으로 인한 메모리 누수를 어느 정도는 막을 수 있다.
마무리
이번에는 Stomp의 효과적인 연결 관리 방법에 대해서 알아보았다.
단순 채팅 기능을 만드는 것보다 더 고차원적인 연결 관리를 요구했고, 해당 부분을 해결하기 위해서 열심히 공부한 것 같다.
이를 통해서 기존에 약 1GB이었던 메모리 사용량을 약 500MB까지 줄일 수 있었다.
실시간 영상 스트리밍 기술이 많은 메모리를 필요로하는 만큼, 약 500MB의 메모리 누수가 있었다는 것을 의미한다.
다만 아직 해결되지 않은 메모리 누수가 있기 때문에 해당 부분도 보완해볼 예정이다.
'Spring' 카테고리의 다른 글
| [ Spring Boot ] JPA 연관 관계 매핑 (1) | 2025.08.26 |
|---|---|
| [ Spring Boot ] 스트리밍 서비스에서 Buffer Pool로 ByteBuffer 관리 (6) | 2025.07.29 |
| [ Spring ] Spring의 디자인 패턴과 아키텍처 (1) | 2025.07.14 |
| [Spring Boot] Spring Boot 설정 파일 분리 : submodule (0) | 2025.03.31 |