| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 문서번호
- font-size
- 0.5px border
- angular
- Websocket
- TypeScript
- github
- entity
- 클론코딩
- 전역변수
- npm
- es6
- Props
- ES5
- TS
- 으
- 데이터베이스 #try #이중
- 서버리스 #
- literal
- Strict
- 0.75px border
- 당근마켓
- 0.25px border
- jwt
- &연산
- 1px border
- ZOOM
- 타입스크립트
- 컴포넌튼
- 10px
- Today
- Total
복잡한뇌구조마냥
[Spring] SSE는 WebFlux, WebSocket은 MVC를 선택한 이유 본문

✅ 배경 – “실시간”이라는 요구는 하나가 아니었다
이번 프로젝트에서는 실시간 통신이 필요한 화면이 여러 개 존재했다.
하지만 구현을 진행하면서, 이 실시간 요구는 하나의 문제로 묶기 어렵다는 점이 드러났다.
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는 혼용 시 경계를 명확히 해야 한다
- 기술 선택의 기준은 유행이 아니라 요구사항의 성격이다
같은 실시간이라도
문제의 성격이 다르면 선택도 달라져야 했다.
참고자료:
[Spring boot] Spring WebMVC(Servlet) 와 Spring Webflux의 차이
오늘은 Spring MVC(Sevlet)과 Webflux의 차이에 대해서 간략하게 정리해 보고자 한다. 최근 API Gateway를 만들기 위해 Spring Cloud Gateway를 사용하였는데, 이 환경이 Webflux로 작성되어 있어서 기존에 내가 사
corono.tistory.com
'BE > Spring' 카테고리의 다른 글
| [Java] TTL 캐시를 직접 구현하며 이해한 Redis 내부 구조 (0) | 2025.12.26 |
|---|---|
| [Spring] Flyway DB 스키마 관리 (0) | 2025.12.07 |
| [Spring] SMTP 기반 메일 인증 ( + 비동기 처리 ) (0) | 2025.11.30 |
| [Spring] Spring Batch + Scheduler 정리 (0) | 2025.11.27 |
| [JPA] OSIV ( Open Session In View ) (0) | 2025.11.25 |