프로젝트: Snowpeak Game — 실시간 멀티플레이 협동 타이쿤 게임 스택: Java 17, Spring Boot 3.5, Spring WebSocket(STOMP + SockJS), Spring Data Redis(Lettuce), Redis 7, JUnit 5 관련 문서: 이동 동기화 트러블슈팅
플레이어 상태를 Redis Hash(room:{roomId})에 저장하는 구조에서 유령 플레이어, 같은 닉네임 덮어쓰기, room:null 키 생성, 이동마다 Redis 동기 쓰기, 이동 순서 역전을 찾아냈습니다.
HSETNX / Lua Script / 쓰기 지연(Write-Behind) / 세션 기반 식별 / STOMP 수신 순서 보장으로 해결하고, 통합 테스트 8개와 실제 Redis 테스트 3개로 검증했습니다.
Key : room:{roomId} (Redis Hash)
Field : nickname (= playerId)
Value : PlayerState JSON (GenericJackson2JsonRedisSerializer)
| 시점 | 기존 동작 |
|---|---|
입장 /app/join |
HSET room:1 nickname state 후 /topic/room.1로 브로드캐스트 |
이동 /app/update |
이동 메시지마다 HSET room:{클라이언트가 보낸 roomId} 후 브로드캐스트 |
| 퇴장 (연결 끊김) | HEXISTS → HDEL room:1 nickname → LEAVE 브로드캐스트 |
| 늦은 입장자 동기화 | HVALS room:1 결과를 1명씩 convertAndSendToUser(sessionId, ...)로 전송 |
SessionDisconnectEvent에만 의존합니다. 서버 프로세스가 죽으면 이 이벤트가 발생하지 않아 Redis의 데이터가 정리되지 않습니다. 키에 TTL도 없어서 영구히 남습니다.alice로 접속한 상태에서 B도 alice로 접속하면, A의 상태가 B로 바뀝니다. 그 뒤 B가 나가면 A까지 같이 사라집니다.HSET으로 무조건 덮어썼습니다. 존재 여부를 먼저 확인(HEXISTS)한 뒤 저장해도, 두 요청이 동시에 오면 둘 다 "없음"으로 판단하는 경쟁 조건(Race Condition) 이 남습니다.room:null 키가 생기고, 퇴장이 반영되지 않는다room:null 키가 생기고, 브로드캐스트가 /topic/room.null로 나갑니다.