이 프로젝트의 트러블슈팅 경험을 바탕으로 나올 만한 질문과 답변 요지입니다. 답변은 결론 → 근거 → 내가 한 일 순서로 정리했습니다.
네트워크 / 실시간성
Q1. 실시간 게임인데 왜 UDP가 아니라 TCP(WebSocket)를 썼나요?
결론: 브라우저 게임이라 UDP 소켓을 쓸 수 없었고, 장르상 TCP로도 충분하다고 판단했습니다.
근거: 브라우저 JavaScript에는 raw UDP API가 없습니다. UDP 성격의 통신을 하려면 WebRTC DataChannel이나 WebTransport가 필요한데, 시그널링·HTTP/3 인프라가 추가로 필요합니다. 또 이 게임은 협동 타이쿤이라 주문·서빙처럼 절대 유실되면 안 되는 이벤트가 위치보다 중요합니다.
내가 한 일: 프로토콜을 바꾸는 대신, TCP 안에서 체감 지연을 줄였습니다. 전송 주기를 약 60Hz에서 20Hz로 줄이고, 클라이언트 보간을 넣고, 서버의 Redis 쓰기를 전달 경로에서 뺐습니다.
Q2. TCP의 어떤 특성이 게임에 불리한가요?
Head-of-Line Blocking입니다. 패킷 하나가 유실되면 재전송될 때까지 그 뒤 패킷이 모두 대기합니다. 위치처럼 최신 값만 의미 있는 데이터에서는, 이미 의미 없어진 옛 위치 때문에 새 위치까지 늦어집니다.
체감상으로는 캐릭터가 멈췄다가 몰아서 움직이는 현상으로 나타납니다.
Q3. 다른 플레이어의 캐릭터를 부드럽게 보이게 한 방법은?
받은 좌표를 바로 대입하지 않고 목표 좌표로만 저장한 뒤, 매 프레임 목표의 15%씩 다가가는 선형 보간(Lerp) 을 적용했습니다.
트레이드오프: 60fps에서 계수 0.15로 남은 거리의 90%를 따라잡으려면 ln(0.1)/ln(0.85) ≈ 14프레임 ≈ 236ms가 걸립니다. 부드러운 대신 표시 지연이 늘어납니다.
개선 방향: 타임스탬프 기반 스냅샷 보간(약 100ms 과거를 두 점 사이로 보간)으로 바꾸면, 지연은 일정하게 유지하면서 프레임레이트와 무관해집니다.
Q4. 내 캐릭터는 어떻게 처리했나요?
서버 응답을 기다리지 않고 입력 즉시 로컬에서 움직입니다. 네트워크로는 50ms마다 현재 위치만 보냅니다. 내 조작 반응에는 네트워크 지연이 전혀 없습니다.
동시성 / 순서
Q5. TCP는 순서를 보장하는데, 순서 문제가 생길 수 있나요?
네, 실제로 겪었습니다. TCP는 네트워크 계층까지의 순서만 보장합니다. Spring STOMP는 받은 메시지를 clientInboundChannel의 스레드 풀에서 처리합니다. 그래서 같은 세션의 메시지도 병렬로 실행되어 순서가 바뀔 수 있습니다.