Let's Git It 프로젝트를 개발하면서 실시간 멀티플레이어 게임 서버를 설계하다 마주친 고민을 정리했다.
Let's Git It은 Git 명령어를 게임으로 학습하는 멀티플레이어 플랫폼이다.
Speed Run, Time Attack, 협력 모드 등 실시간 인터랙션이 핵심인 서비스다 보니,
대기실 입장부터 게임 종료까지 모든 흐름에서 "이 이벤트는 REST로 처리해야 할까, WebSocket으로 처리해야 할까?" 라는 질문이 계속 따라왔다.
처음에는 "실시간 게임이니까 WebSocket으로 다 하면 되지 않나?" 라고 생각했다.
그런데 팀 내 리뷰를 거치면서 생각보다 이 둘의 경계가 명확하다는 걸 알게 됐다.
먼저 두 기술의 본질적인 차이부터 짚고 가자.
| 항목 | REST API | WebSocket |
|---|---|---|
| 통신 방향 | 클라이언트 → 서버 (단방향 요청-응답) | 클라이언트 ↔ 서버 (양방향) |
| 연결 방식 | 요청마다 새로운 연결 | 한 번 연결 후 유지 |
| 응답 보장 | HTTP 상태코드로 명확히 보장 | 별도 ACK 없으면 보장 안 됨 |
| 적합한 상황 | 명확한 요청-응답, 상태 변경 | 실시간 이벤트, 브로드캐스트 |
"어차피 WebSocket 연결이 있으니까, LEAVE 메시지 하나 보내면 되지 않나?"
실제로 초기 명세는 WebSocket으로만 처리하도록 설계했다.
클라이언트 → STOMP SEND /app/room/{roomId}/leave
서버 → 상태 업데이트 + 나머지 플레이어에게 브로드캐스트
팀 리뷰에서 이런 피드백을 받았다.
"나가기는 REST로 하고, 이후 알림은 WebSocket으로 해야 해"
처음엔 이유를 몰랐다. WebSocket으로도 동일하게 동작하는 것 같았으니까.
결국 핵심은 서버가 연결 종료의 이유를 알 수 있느냐의 문제였다.
WebSocket 연결이 끊기는 경우는 두 가지다.
① 사용자가 나가기 버튼을 눌렀다 → 의도적 종료
② 사용자 네트워크가 갑자기 끊겼다 → 비정상 종료
WebSocket만 사용할 경우, 서버는 두 경우 모두 SessionDisconnectEvent로만 감지한다.
즉, 연결이 끊겼다는 사실은 알지만, 왜 끊겼는지는 모른다.
서버: WebSocket 연결이 끊겼네
서버: ...이게 나간 건지, 튕긴 건지?
REST API를 사용할 경우, 나가기 버튼을 누르면 HTTP 요청이 먼저 전송된다.
REST 호출 있음 → 자발적 나가기
REST 없이 WS만 끊김 → 비정상 종료 (튕김)
HTTP 요청 자체가 "이건 내가 의도적으로 나간 것" 이라는 증거가 된다.
나가기를 REST로 처리하는 이유는 응답을 받기 위해서가 아니라,
자발적 종료와 비정상 종료를 구분하기 위해서다.
강퇴는 즉시성과 브로드캐스트가 핵심이다.
방장 → KICK_REQUEST 전송
서버 → 강퇴 대상에게 /queue/private (KICKED)
서버 → 나머지 전체에게 /topic/room/{roomId} (PLAYER_KICKED)
강퇴는 세 가지 이유로 WebSocket이 적합하다.
REST로 처리하면 결국 알림은 WebSocket으로 따로 보내야 하므로, 굳이 두 채널을 섞을 이유가 없다.
방 입장은 REST로 처리하고, 입장 확정 이후 서버가 WebSocket으로 브로드캐스트하는 방식이다.
POST /rooms/{roomId}/join (REST)
↓
서버: 인원 초과 체크, 중복 입장 체크, 세션 등록
↓
서버 → /topic/room/{roomId} (PLAYER_JOINED) 브로드캐스트
방 입장을 REST로 처리하는 이유는 두 가지다.
WebSocket 연결은 방 입장 확정 후에 맺는 것이 자연스럽다.
확정되지도 않은 방에 WebSocket을 먼저 연결하는 건 리소스 낭비다.
고민을 거듭하면서 아래 기준으로 판단하게 됐다.
REST API를 쓰는 경우
├─ 요청에 대한 성공/실패 응답이 클라이언트에게 필요한 경우
├─ 의도적 액션임을 서버가 명시적으로 알아야 하는 경우
└─ WebSocket 연결 이전에 처리되어야 하는 경우
WebSocket을 쓰는 경우
├─ 여러 클라이언트에게 동시에 이벤트를 전달해야 하는 경우
├─ 서버가 클라이언트에게 먼저 메시지를 보내야 하는 경우
└─ 게임 내 실시간 이벤트 흐름과 일관성이 필요한 경우
| 기능 | 방식 | 이유 |
|---|---|---|
| 방 목록 조회 | REST | 단순 조회, 실시간 불필요 |
| 방 입장 | REST → WS 브로드캐스트 | 입장 가능 여부 검증 필요 |
| 자발적 나가기 | REST → WS 브로드캐스트 | 자발적/비정상 종료 구분 |
| 강퇴 | WebSocket | 즉시성 + 동시 브로드캐스트 |
| 준비 상태 변경 | WebSocket | 실시간 상태, 전체 공유 |
| 게임 시작 | WebSocket | 전체 동시 전달 필요 |
| 채팅 | WebSocket | 실시간 양방향 |
| 게임 내 입력 | WebSocket | 실시간 처리 |
"WebSocket이니까 전부 WebSocket으로"라는 접근이 아니라,
각 이벤트의 의도, 방향성, 보장 수준을 기준으로 판단하는 게 맞다는 걸 이번 설계를 통해 체감했다.
REST와 WebSocket은 경쟁 관계가 아니라 역할이 다른 도구다.
둘을 적절히 조합하는 것이 실시간 서비스 설계의 핵심이라고 생각한다.
이 글은 Let's Git It 프로젝트 개발 과정에서 팀 내 기술 토론을 바탕으로 정리한 내용입니다.