복잡한뇌구조마냥

[Spring] SSE는 WebFlux, WebSocket은 MVC를 선택한 이유 본문

BE/Spring

[Spring] SSE는 WebFlux, WebSocket은 MVC를 선택한 이유

지금해냥 2026. 1. 15. 20:20


✅ 배경 – “실시간”이라는 요구는 하나가 아니었다

이번 프로젝트에서는 실시간 통신이 필요한 화면이 여러 개 존재했다.
하지만 구현을 진행하면서, 이 실시간 요구는 하나의 문제로 묶기 어렵다는 점이 드러났다.

1️⃣ 로비 / 경기 대기실

  • 방 목록 변경
  • 참가자 입장 / 퇴장
  • 준비 상태 변경
  • 강퇴 처리
  • 다른 사용자가 어떤 행동을 했는지 즉시 반영

2️⃣ 게임 진행 중

  • 사용자 입력 전송
  • 상대방 행동의 즉각적인 반영
  • 짧은 주기의 반복적인 상호작용

처음에는 두 영역 모두
“실시간 통신”이라는 하나의 문제로 접근했지만,
통신의 방향·빈도·지연 민감도가 서로 다르다는 점을 무시할 수 없었다.


✅ 실시간 통신 요구사항을 분리해보면

🔹 로비 / 대기실의 성격

  • 서버 → 클라이언트 단방향
  • 상태 변경 알림 중심
  • 이벤트 빈도는 높지 않음
  • 연결은 비교적 오래 유지됨
  • 재연결 안정성이 중요

🔹 게임 진행의 성격

  • 클라이언트 ↔ 서버 양방향
  • 입력과 결과가 즉시 연결됨
  • 지연이 체감 품질에 직접적인 영향

이 시점에서 중요한 결론은 다음이었다.

같은 “실시간”이라도
통신 성격이 다르면 기술 선택도 달라져야 한다


✅ 로비 / 대기실에는 SSE가 적합했다

로비와 경기 대기실의 요구는
양방향 통신보다는 상태 변경 알림에 가까웠다.

이 영역에는 SSE(Server-Sent Events) 가 적합했다.

SSE 선택 이유

  • 서버 → 클라이언트 단방향 구조
  • HTTP 기반으로 구현 단순
  • 브라우저 기본 지원
  • 자동 재연결 지원
  • 상태 이벤트 전달에 충분한 성능

또한 SSE는 Spring WebFlux와 잘 맞는 기능이기 때문에
로비 / 대기실 상태 동기화는
SSE + WebFlux 조합으로 구현했다.


✅ 자연스럽게 생긴 가정 – “그럼 WebSocket도 WebFlux로?”

게임이 시작되면 요구사항은 달라진다.

  • 사용자 입력을 서버로 전송
  • 상대방 행동을 즉시 반영
  • 양방향, 저지연 통신 필요

이 영역에는 WebSocket이 필요했다.

이미 SSE를 WebFlux로 구현하고 있었기 때문에
다음과 같은 가정을 하게 되었다.

“SSE를 WebFlux로 쓰고 있으니
WebSocket도 WebFlux로 구현하는 게 맞지 않을까?”

즉, 실시간 통신 스택을 WebFlux로 통일하려는 시도였다.


✅ 문제 발생 – WebSocket이 아예 연결되지 않음

ws://localhost:8080/ws/battles/{id} 로 연결을 시도했지만
다음과 같은 에러가 발생했다.

No static resource ws/battles/6 for request '/ws/battles/6' DispatcherServlet → ResourceHttpRequestHandler

관찰된 현상

  • WebSocket 요청인데 DispatcherServlet이 처리
  • WebSocketHandler로 요청이 도달하지 않음
  • 정적 리소스 처리 단계까지 내려가며 실패

즉, 웹소켓 로직 이전에 라우팅 단계에서 이미 요청이 탈락하고 있었다.


✅ 원인 – MVC와 WebFlux 혼용의 구조적 한계

프로젝트에는 다음 의존성이 함께 존재했다.

  • spring-boot-starter-webmvc
  • spring-boot-starter-websocket
  • spring-boot-starter-webflux

이 구성에서 Spring Boot는 기본적으로

  • Servlet 기반 MVC 스택을 우선 선택
  • Tomcat + DispatcherServlet 기반으로 실행

그 결과,

WebFlux의 WebSocketHandler는
MVC 요청 파이프라인에 자연스럽게 연결되지 않는다

또한 WebFlux WebSocket 라우팅에서 사용한
SimpleUrlHandlerMapping은 {id} 형태의 PathVariable을 인식하지 않아
요청이 매칭되지 않고 MVC 기본 체인으로 떨어졌다.


✅ 선택지 정리

이 문제를 해결하기 위한 선택지는 다음과 같았다.

1️⃣ REST까지 포함해 WebFlux로 전체 전환

  • 변경 범위 큼
  • 현재 단계에서는 과도

2️⃣ WebSocket 서버 분리

  • Redis Pub/Sub 등 추가 인프라 필요
  • 운영 복잡도 증가

3️⃣ WebSocket만 MVC로 전환

  • 기존 REST API와 동일한 스택
  • 변경 범위 최소
  • 디버깅과 안정성 측면에서 유리

✅MVC와 WebFlux를 WebSocket 관점에서 비교

구분 Spring MVC Spring WebFlux
기본 런타임 Servlet (Tomcat) Reactive (Netty)
처리 모델 Thread-per-request Event-loop
WebSocket 지원 spring-websocket Reactive WebSocketHandler
라우팅 Ant 패턴(/ws/*) HandlerMapping
MVC와 혼용 자연스러움 주의 필요
디버깅 난이도 낮음 상대적으로 높음
운영 복잡도 낮음 높음

✅ 최종 선택 – 역할 기준으로 기술을 나눴다

결론적으로 다음과 같이 정리했다.

🔹 로비 / 경기 대기실

  • 방 목록, 준비, 강퇴, 퇴장
  • 단방향 상태 이벤트
  • SSE + WebFlux

🔹 게임 진행 중

  • 사용자 입력, 상호작용
  • 양방향 통신
  • WebSocket + MVC

기술을 통일하는 대신,
통신의 성격에 따라 역할을 분리했다.


✔️ 정리

  • “실시간”이라고 해서 모든 통신이 같은 문제는 아니다
  • SSE와 WebSocket은 용도가 다르다
  • WebFlux와 MVC는 혼용 시 경계를 명확히 해야 한다
  • 기술 선택의 기준은 유행이 아니라 요구사항의 성격이다

같은 실시간이라도
문제의 성격이 다르면 선택도 달라져야 했다.

 

참고자료:

https://corono.tistory.com/33

 

[Spring boot] Spring WebMVC(Servlet) 와 Spring Webflux의 차이

오늘은 Spring MVC(Sevlet)과 Webflux의 차이에 대해서 간략하게 정리해 보고자 한다. 최근 API Gateway를 만들기 위해 Spring Cloud Gateway를 사용하였는데, 이 환경이 Webflux로 작성되어 있어서 기존에 내가 사

corono.tistory.com

 

LIST