Skip to content

실시간 200명 플레이어 렌더링을 위한 최적화 과정

Jungi-Kim edited this page Jan 1, 2025 · 5 revisions

📍 개요

  • 퀴즈 그라운드는 부스트캠프 인원 200명이 동시에 플레이하는 것이 목표입니다.
  • 이를 위해 플레이어가 실시간으로 만드는 움직임을 버벅임 없이 렌더링해야 합니다.
  • 이에 따라 성능 테스트를 계획했습니다.

image

📖 성능 테스트 계획

다음과 같은 성능 테스트를 계획했습니다.

  • 200명의 더미 플레이어 생성
  • 1인당 1초에 1번 움직임
  • 200 * 5번의 움직임을 생성(약 5초)
  • Performence API로 시작 시간과 종료 시간을 측정
  • 빠르게 테스트하기 위해 현재 잘 사용 중인 소켓 목을 이용

아래는 테스트를 위한 코드입니다.

export default class SocketMockLoadTestOnlyMove extends SocketMock {
  constructor() {
    super();
    // 200명의 더미 플레이어 생성
    this.createDummyPlayer(200);
    // 모든 더미 플레이어가 5초 동안 1초에 한번 씩 움직임
    this.performenceTest([this.moveRandom(5, 1)]);
  }
}

🐌 첫 번째 결과 - 무려 3배 느린

기존의 코드로 테스트를 한 결과 15초~17초 정도의 시간이 측정되었습니다.

5초 동안 아무것도 안 하는 것보다 3배의 시간이 걸린 것입니다.

image

🍭 최적화 과정

원인 발견

우린 이 원인을 찾게 되었고 다음과 같은 이유를 알 수 있었습니다.

한 명이 움직여도 전체 플레이어가 리렌더링 된다!

image

기존 구조

전체 플레이어가 매 움직임마다 리렌더링되는 이유는 아래와 같습니다. 플레이어 컴포넌트는 QuizOptionBoard에 포함되고, QuizOptionBoard는 players 전역 상태를 구독합니다.

graph TD
    subgraph GamePage
        Player1
        Player2
        Player3
        QuizOptionBoard --> Player1
        QuizOptionBoard --> Player2
        QuizOptionBoard --> Player3
    end

    state["players 전역 상태"]
    QuizOptionBoard --->|구독| state
Loading

여기서 플레이어가 움직이도록 리렌더링하기 위해선 다음의 과정이 필요합니다.

  1. players 배열에서 움직일 플레이어를 탐색해 위치를 바꾼다.
  2. 새로운 배열 객체를 생성한다.
  3. QuizOptionBoard는 players 배열이 바뀌었음을 알아채고 모든 플레이어를 리렌더링 한다.

이처럼 한 명의 플레이어만 움직여도 전체 플레이어가 리렌더링되는 것입니다.

players 자료구조 변경

우리는 이러한 문제가 배열이라는 자료구조의 한계에 의해 발생한다고 생각하였고, 배열을 대신해 Map 자료구조로 바꾸기로 결정했습니다.

// 기존 players
players: Player[];

// 새로운 players(key는 playerId)
players: Map<string, Player>;

이렇게 결정한 이유는 아래와 같습니다.

  1. 탐색 평균 O(1)
  2. 삽입, 삭제 평균 O(1)
  3. 삽입 순서 보장

새로운 구조

이제 컴포넌트가 상태를 구독하고 있는 구조를 바꿨습니다.

graph TD
    subgraph GamePage
        Player1
        Player2
        Player3
        QuizOptionBoard --> Player1
        QuizOptionBoard --> Player2
        QuizOptionBoard --> Player3
    end
    
    subgraph players
        PlayerState1
        PlayerStete2
        PlayerState3
    end

    QuizOptionBoard -->|구독| players
    Player1 -->|구독| PlayerState1
    Player2 -->|구독| PlayerStete2
    Player3 -->|구독| PlayerState3
Loading

여전히 QuizOptionBoard가 players를 구독하고 있지만, 이제는 각 Player 컴포넌트가 자신의 상태를 prop으로 전달 받지 않고 직접 구독합니다.

이에 따라 움직일 때 움직인 플레이어만 리렌더링 됩니다.

image

✨ 개선 후 성능 측정 결과

개선 후 성능 측정 결과, 6~7초로 측정이 되었습니다. 기존 코드에 비해 무려 2배 이상 시간이 단축되었습니다.

image

⭐️ 결론

  • players 상태를 배열에서 Map으로 바꾸었습니다.
  • 각 플레이어가 직접 자신의 상태를 구독하도록 했습니다.
  • 성능 측정 결과 16.71초 → 6.31초로 시간이 단축 되었습니다.

📂 레퍼런스