관리 메뉴

Mini

24.11.14. 발표자료 // n+1문제해결, 변경감지, redis 설계 본문

개발일지

24.11.14. 발표자료 // n+1문제해결, 변경감지, redis 설계

Mini_96 2024. 11. 14. 22:13

백엔드 경험


  • Redis관련 흐름 정리
flowchart TB
    Client["Client (Socket.IO)"]
    GameService["GameService"]
    Redis["Redis"]
    HTTP["HTTP Service"]

    Client <--> |Socket Events| GameService
    GameService <--> |Data Storage/Pub-Sub| Redis
    GameService --> |Quiz Data| HTTP
flowchart TB
    Room["Room:{gameId}
    - host
    - status
    - quizSetId
    - quizCount"]
    
    Player["Player:{clientId}
    - gameId
    - playerName
    - position
    - score"]
    
    RoomQuiz["RoomQuiz:{gameId}:{quizId}
    - quiz
    - answer
    - limitTime"]
    
    Leaderboard["Leaderboard:{gameId}
    Sorted Set of players"]

    Room --> Player
    Room --> RoomQuiz
    Room --> Leaderboard
flowchart LR
    Waiting["대기중"] --> Playing["게임중"]
    Playing --> QuizStart["퀴즈 시작"]
    QuizStart --> QuizEnd["퀴즈 종료"]
    QuizEnd --> |다음 퀴즈| QuizStart
    QuizEnd --> |마지막 퀴즈| GameEnd["게임 종료"]
  • 주요 이벤트 흐름도
sequenceDiagram
    participant C as Client
    participant GS as GameService
    participant R as Redis
    participant H as HTTP Service

    C->>GS: updatePosition
    GS->>R: Set Player Position
    R-->>C: Broadcast Position Update

    C->>GS: startGame
    GS->>H: Get Quiz Data
    GS->>R: Initialize Game State
    R-->>C: Broadcast Game Start

    Note over GS,R: Redis Pub/Sub Events
    R->>GS: Timer Event
    GS->>C: START_QUIZ_TIME

    R->>GS: Scoring Event
    GS->>C: END_QUIZ_TIME

    C->>GS: disconnect
    GS->>R: Update Room State
    R-->>C: Broadcast Player Exit
  • 구체적인 예시
sequenceDiagram
    participant Player1 as 방장(홍길동)
    participant Player2 as 참가자(김철수)
    participant Game as GameService
    participant Redis as Redis
    participant HTTP as HTTP API

    Note over Player1,Player2: 게임 시작 전
    Player1->>Game: 방 생성 (게임 ID: room123)
    Game->>Redis: 방 정보 저장<br/>Room:room123 = {host: "홍길동"}

    Player2->>Game: 방 입장
    Game->>Redis: 플레이어 정보 저장<br/>Player:김철수 = {gameId: "room123"}
    Redis-->>Player1: 플레이어 입장 알림

    Note over Player1,Player2: 게임 시작
    Player1->>Game: 게임 시작 요청
    Game->>HTTP: 퀴즈 데이터 요청
    HTTP-->>Game: 퀴즈 데이터 응답<br/>(예: "자바스크립트 기초" 퀴즈셋)

    Note over Player1,Player2: 첫 번째 퀴즈
    Game->>Redis: 퀴즈 데이터 저장
    Redis-->>Player1: 퀴즈 시작<br/>"var와 let의 차이점은?"
    Redis-->>Player2: 퀴즈 시작<br/>"var와 let의 차이점은?"

    Note over Player1,Player2: 답변 제출 (위치 기반)
    Player1->>Game: 위치 이동 (1번 영역)
    Player2->>Game: 위치 이동 (3번 영역)
    Game->>Redis: 위치 정보 저장

    Note over Player1,Player2: 채점
    Game->>Redis: 채점 결과 저장<br/>Player1: 정답(+1000점)<br/>Player2: 오답(+0점)
    Redis-->>Player1: 결과 발표
    Redis-->>Player2: 결과 발표
// 1. 방 생성 시점의 Redis 데이터
{
  "Room:room123": {
    "host": "player1",
    "status": "waiting",
    "quizSetId": "js-basic-101",
    "maxPlayerCount": "4",
    "quizCount": "5"
  },

  // 플레이어 입장
  "Player:player1": {
    "gameId": "room123",
    "playerName": "홍길동",
    "positionX": "0.5",
    "positionY": "0.5",
    "isAnswerCorrect": "0"
  },

  "Player:player2": {
    "gameId": "room123",
    "playerName": "김철수",
    "positionX": "0.5",
    "positionY": "0.5",
    "isAnswerCorrect": "0"
  },

  // 게임 시작 후 퀴즈 데이터
  "RoomQuiz:room123:quiz1": {
    "quiz": "var와 let의 차이점은?",
    "answer": "2",  // 2번이 정답
    "limitTime": "30",
    "choiceCount": "4"
  },

  "RoomQuizChoices:room123:quiz1": {
    "1": "아무 차이 없다",
    "2": "let은 블록 스코프, var는 함수 스코프",
    "3": "let만 재선언 가능",
    "4": "var만 호이스팅 된다"
  },

  // 리더보드
  "Room:room123:leaderboard": {
    "player1": "1000",
    "player2": "0"
  }
}

  • 1:n:n:n 관계 조회, 페이징
flowchart TB
    subgraph "QuizSet 조회"
        A[Start] --> B[QuizSet Repository]
        B --> C[QuizSets]
        C -->|Empty| D[Return Empty Result]
        C -->|Not Empty| E[Extract QuizSet IDs]
    end

    subgraph "Quiz 조회"
        E --> F[Quiz Repository]
        F -->|WHERE IN quizSetIds| G[Quizzes]
        G --> H[Extract Quiz IDs]
    end

    subgraph "Choice 조회"
        H --> I[Choice Repository]
        I -->|WHERE IN quizIds| J[Choices]
    end

    subgraph "메모리 매핑"
        J --> K[Group Choices by QuizId]
        G --> L[Group Quizzes by QuizSetId]
        C --> M[Map to DTOs]
        K --> M
        L --> M
    end

    M --> N[Return Result]

1: N : N: N의 조회가 쿼리 단 3번으로 끝난다! 페이징도 가능하다!

  • 1:N:N:N 에서 변경감지를 통한 수정
flowchart TB
    Start[시작] --> Transaction[트랜잭션 시작]
    Transaction --> FindQuizSet[퀴즈셋 조회<br/>with Relations]
    
    FindQuizSet --> |Not Found| Error[NotFoundException]
    FindQuizSet --> |Found| BasicUpdate[기본 필드 업데이트]
    
    subgraph "기본 필드 업데이트"
        BasicUpdate --> |title 존재| UpdateTitle[제목 업데이트]
        BasicUpdate --> |category 존재| UpdateCategory[카테고리 업데이트]
    end
    
    BasicUpdate --> CheckQuizList{퀴즈 리스트<br/>존재?}
    CheckQuizList --> |No| SaveChanges[변경사항 저장]
    CheckQuizList --> |Yes| UpdateQuizzes[퀴즈 업데이트]
    
    subgraph "퀴즈/선택지 업데이트"
        UpdateQuizzes --> ProcessQuiz[각 퀴즈 처리]
        ProcessQuiz --> |신규| CreateQuiz[새 퀴즈 생성]
        ProcessQuiz --> |기존| UpdateQuiz[기존 퀴즈 업데이트]
        
        UpdateQuiz --> CheckChoices{선택지<br/>존재?}
        CreateQuiz --> CheckChoices
        
        CheckChoices --> |Yes| UpdateChoices[선택지 업데이트]
        CheckChoices --> |No| SaveQuiz[퀴즈 저장]
        UpdateChoices --> SaveQuiz
    end
    
    UpdateQuizzes --> SaveChanges
    SaveChanges --> Return[결과 반환]
sequenceDiagram
    participant C as Controller
    participant S as Service
    participant T as Transaction
    participant M as Entity Manager
    participant E as Entities
    
    C->>S: update(id, dto)
    S->>T: transaction 시작
    T->>M: findOne with relations
    M->>E: Load QuizSet + Relations
    
    Note over E: 변경 감지 시작
    
    E->>E: 기본 필드 업데이트
    loop 각 퀴즈에 대해
        E->>E: 퀴즈 필드 업데이트
        loop 각 선택지에 대해
            E->>E: 선택지 필드 업데이트
        end
    end
    
    E->>M: 변경사항 저장
    M->>T: Commit
    T->>S: 결과 반환
    S->>C: 응답