소켓 연결 및 매칭 요청 흐름
sequenceDiagram
participant Client as 클라이언트
participant Socket as Socket.IO
participant MatchingGW as MatchingGateway
participant MatchingCtrl as MatchingController
participant MatchingSvc as MatchingService
participant Redis as Redis
Note over Client: 메인 페이지 진입
Client->>Socket: connectBattleSocket()
Socket-->>Client: 소켓 연결 성공 (/game)
Client->>Client: registerMatchingListeners()
Note over Client: MATCH_SUCCESS, STATS_UPDATE<br/>이벤트 리스너 등록
Client->>MatchingCtrl: POST /matching/start
MatchingCtrl->>MatchingSvc: startMatching(userId, tier)
MatchingSvc->>Redis: ZADD matching:queue<br/>(score=timestamp)
MatchingSvc->>Redis: HSET matching:user:{userId}<br/>(status=WAITING, tier, socketId)
Redis-->>MatchingSvc: OK
MatchingSvc->>MatchingGW: registerUserSocket(userId, socketId)
MatchingGW-->>MatchingSvc: OK
MatchingSvc-->>MatchingCtrl: 매칭 대기 시작
MatchingCtrl-->>Client: 200 OK
Note over Client: 매칭 대기 중...
매칭 성공 및 방 입장 흐름
sequenceDiagram
participant Scheduler as MatchingScheduler<br/>(Cron 2초마다)
participant MatchingSvc as MatchingService
participant RoomSvc as RoomService
participant BattleSvc as BattleService
participant BattleRedisSvc as BattleRedisService
participant Redis as Redis
participant MatchingGW as MatchingGateway
participant Client1 as 클라이언트 A
participant Client2 as 클라이언트 B
participant RoomGW as RoomGateway
Scheduler->>MatchingSvc: matchUsers() (Cron)
MatchingSvc->>Redis: ZRANGE matching:queue<br/>(대기 유저 조회)
Redis-->>MatchingSvc: [user1, user2, ...]
Note over MatchingSvc: 레이팅 범위 내 페어링
MatchingSvc->>RoomSvc: createMatchedRoom(user1, user2, problem)
RoomSvc->>Redis: SET room:{roomId}:info<br/>(players=[], problem, status=waiting)
RoomSvc->>BattleSvc: createBattle(roomId, user1, user2, problem)
BattleSvc->>BattleRedisSvc: createBattle(battleId, roomId, users)
BattleRedisSvc->>Redis: SET battle:{battleId}
BattleRedisSvc->>Redis: SET battle:room:{roomId}
BattleRedisSvc->>Redis: SADD matching:active_battles
BattleRedisSvc-->>BattleSvc: OK
BattleSvc-->>RoomSvc: Battle 생성 완료
RoomSvc-->>MatchingSvc: Room 생성 완료
MatchingSvc->>Redis: ZREM matching:queue (user1, user2)
MatchingSvc->>Redis: HSET matching:user:{user1Id}<br/>(status=MATCHED, roomId, opponentId)
MatchingSvc->>Redis: HSET matching:user:{user2Id}<br/>(status=MATCHED, roomId, opponentId)
MatchingSvc->>Redis: LPUSH matching:recent_wait_times<br/>(대기 시간 기록)
MatchingSvc->>MatchingGW: emitMatchSuccess(user1, user2, roomId, battleId)
MatchingGW->>Client1: MATCH_SUCCESS<br/>(roomId, battleId, opponent, problem)
MatchingGW->>Client2: MATCH_SUCCESS<br/>(roomId, battleId, opponent, problem)
Note over Client1,Client2: 자동으로 방 페이지로 이동
Client1->>RoomGW: JOIN_ROOM<br/>(roomId, role=player)
Client2->>RoomGW: JOIN_ROOM<br/>(roomId, role=player)
RoomGW->>Redis: room:{roomId}:info 조회 및 업데이트<br/>(player 추가)
RoomGW->>BattleSvc: joinBattle(roomId, player)
BattleSvc->>Redis: battle:{battleId} 업데이트<br/>(user 추가)
RoomGW->>Redis: HSET matching:user:{userId}<br/>(status=IN_ROOM, joinedAt)
RoomGW->>Client1: ROOM_PLAYERS (참가자 목록)
RoomGW->>Client2: ROOM_PLAYERS (참가자 목록)
RoomGW->>Client1: ROOM_STATE_ROLE (본인 역할)
RoomGW->>Client1: PROBLEM_INFO (문제 정보)
RoomGW->>Client1: CODE_UPDATED (초기 코드)
RoomGW->>Client2: ROOM_STATE_ROLE
RoomGW->>Client2: PROBLEM_INFO
RoomGW->>Client2: CODE_UPDATED
Note over RoomGW: 2명 모두 입장 시
RoomGW->>Redis: room:{roomId}:info 업데이트<br/>(status=in-battle)
RoomGW->>Client1: ROOM_AVAILABILITY<br/>(status=in-battle)
RoomGW->>Client2: ROOM_AVAILABILITY<br/>(status=in-battle)
배틀 진행 (코드 작성, 테스트, 제출) 흐름
sequenceDiagram
participant Client as 클라이언트
participant BattleGW as BattleGateway
participant BattleSvc as BattleService
participant Redis as Redis
participant SubmissionCtrl as SubmissionController
participant SubmissionSvc as SubmissionService
participant DB as PostgreSQL
participant Queue as BullMQ
participant Judge as Judge Worker
participant PubSub as Redis PubSub
participant Subscriber as PubsubSubscriber
participant PubsubGW as PubsubGateway
Note over Client: === 코드 작성 중 ===
Client->>BattleGW: CODE_CHANGE<br/>(code, language)
BattleGW->>BattleSvc: updateUserCode(battleId, userId, code)
BattleSvc->>Redis: battle:{battleId} 업데이트<br/>(user.code)
BattleGW->>Client: CODE_UPDATED (전체 방)
BattleGW->>Client: CODE_METADATA (상대방에게)
Note over Client: === 테스트 실행 ===
Client->>SubmissionCtrl: POST /submissions/dry-run<br/>(Header: x-socket-id)
SubmissionCtrl->>SubmissionSvc: executeTest(code, problemId, socketId)
Note over SubmissionSvc: DB 저장 X, submissionId만 생성
SubmissionSvc->>Queue: test-job 큐에 작업 추가
SubmissionSvc-->>Client: 202 Accepted
Queue->>Judge: 테스트 실행
Judge->>Judge: 코드 채점
Judge->>PubSub: PUBLISH submission-result<br/>(type=TEST, submissionId, result)
PubSub->>Subscriber: handleFinalResult(message)
Subscriber->>Subscriber: getSubmissionType() → TEST
Subscriber->>PubsubGW: emitFinalResultToSocket(socketId)<br/>(본인에게만)
PubsubGW->>Client: submission-result<br/>(status, passed, total)
Subscriber->>PubsubGW: emitTestResult(roomId)<br/>(방 전체)
PubsubGW->>Client: test-result (전체 방)
Subscriber->>PubsubGW: emitSystemChat(roomId)
PubsubGW->>Client: receive-chat (시스템 메시지)
Note over Client: === 코드 제출 ===
Client->>SubmissionCtrl: POST /submissions<br/>(Header: x-socket-id)
SubmissionCtrl->>SubmissionSvc: submit(code, problemId, userId, battleId, socketId)
SubmissionSvc->>DB: Submission 엔티티 생성<br/>(status=PENDING)
DB-->>SubmissionSvc: submission.id
SubmissionSvc->>Queue: submission-job 큐에 작업 추가
SubmissionSvc-->>Client: 202 Accepted
Queue->>Judge: 채점 실행
Judge->>Judge: 전체 테스트 실행
Judge->>PubSub: PUBLISH submission-result<br/>(type=SUBMISSION, submissionId, result)
PubSub->>Subscriber: handleFinalResult(message)
Subscriber->>DB: Submission 조회
Subscriber->>DB: Submission 업데이트<br/>(status, passedTestCases, executionTime)
Subscriber->>PubsubGW: emitFinalResult(roomId)<br/>(방 전체)
PubsubGW->>Client: submission-result
Subscriber->>BattleGW: handleUserFinished(roomId, userId)
BattleGW->>Client: receive-chat (제출 완료 시스템 메시지)
alt status === 'ACCEPTED'
Note over Subscriber: 모든 테스트 통과!
Subscriber->>BattleSvc: markUserFinished(battleId, userId)
Note over BattleSvc: 즉시 승리 → endBattle() 호출
end
배틀 종료 흐름
sequenceDiagram
participant Client as 클라이언트
participant BattleGW as BattleGateway
participant BattleSvc as BattleService
participant SubmissionRepo as SubmissionRepository
participant UserSvc as UserService
participant BattleRepo as BattleRepository
participant Redis as Redis
participant MatchingSvc as MatchingService
participant RoomSvc as RoomService
participant MatchingGW as MatchingGateway
participant DB as PostgreSQL
alt 케이스 1: 한 명이 ACCEPTED
Note over BattleSvc: markUserFinished() → endBattle()
else 케이스 2: 타이머 종료
Client->>BattleGW: TIMER_END<br/>(battleId, roomId)
BattleGW->>BattleSvc: endBattleByTimeout(battleId)
end
Note over BattleSvc: === endBattle(battleId) 시작 ===
BattleSvc->>Redis: battle:{battleId} 조회
Redis-->>BattleSvc: battle 데이터
BattleSvc->>SubmissionRepo: 각 참가자의 마지막 Submission 조회
SubmissionRepo-->>BattleSvc: [submission1, submission2]
Note over BattleSvc: === 승자 결정 로직 ===<br/>1. ACCEPTED가 있으면 먼저 제출한 사람<br/>2. 없으면 passedTestCases가 많은 사람<br/>3. 동점이면 무승부 (winnerId=null)
BattleSvc->>UserSvc: updateRatings(winnerId, loserId, isDraw)
UserSvc->>DB: User 테이블 업데이트<br/>(wins, losses, rating)
UserSvc-->>BattleSvc: { winner: {ratingDelta}, loser: {ratingDelta} }
BattleSvc->>BattleRepo: Battle 엔티티 생성 및 저장
Note over BattleRepo: - winnerId, winnerSubmissionId<br/>- loserSubmissionId, playerIds<br/>- player1RatingChange<br/>- player2RatingChange
BattleRepo->>DB: INSERT Battle
DB-->>BattleRepo: savedBattle
BattleRepo-->>BattleSvc: Battle 엔티티
BattleSvc->>Redis: battle:{battleId} DEL
BattleSvc->>Redis: battle:room:{roomId} DEL
BattleSvc->>Redis: SREM matching:active_battles
BattleSvc->>MatchingSvc: clearUserMatchingStatus(user1, user2)
MatchingSvc->>Redis: DEL matching:user:{user1Id}
MatchingSvc->>Redis: DEL matching:user:{user2Id}
BattleSvc-->>BattleGW: Battle 엔티티 반환
BattleGW->>BattleGW: emitBattleEnd(roomId, battleId, winnerId)
BattleGW->>Client: BATTLE_ENDED<br/>(battleId, winnerId) (전체 방)
BattleGW->>Client: receive-chat (시스템 메시지)
BattleGW->>RoomSvc: completeBattleRoom(roomId)
RoomSvc->>Redis: room:{roomId}:info 업데이트<br/>(status=completed)
BattleGW->>MatchingGW: broadcastRoomList()
MatchingGW->>Client: ROOM_LIST (전체)
Note over Client: BATTLE_ENDED 이벤트 수신
Client->>Client: navigate(`/result/${battleId}`)
Note over Client: sessionStorage 정리
Client->>BattleSvc: GET /battles/{battleId}/result
BattleSvc->>DB: Battle 조회 (with submissions, users)
DB-->>BattleSvc: Battle + Players + Submissions
BattleSvc-->>Client: BattleResultResponse<br/>(players, winner, submissions, ratingChanges)