게임 실시간 입력처리할 때 rank를 알려주기 위해서 score를 기준으로 user_id를 정렬해서 보내줘야 함
{
"event": "game:player:realtime",
"data": {
"highest_score": "100",
"average_score": "32.56",
"ranks": ["uuid-01", "uuid-04", "uuid-03", "uuid-02"]
}
}
room:roomId:game:players 내에 각 플레이어들이 Hash로 저장되어 있고, 거기에 메타데이터로 score가 저장되어있는 형태
score 정렬을 위해 매 브로드캐스트마다 모든 키를 가져와 정렬해야 해서 I/O + CPU 비용이 증가

ZSET으로 점수만 분리하도록 결정메타데이터는 Hash에 남김
Redis에서 ZSet은 각 멤버(Member)마다 Score 라는 실수 값을 연결해둔 집합
userId).점수).점수 정렬을 Redis ZSET으로 옮겨서 실시간 랭킹을 따로 정렬 없이 바로 가져오도록 함
게임 시작 시 준비 완료한 참가자들을 ZSET(room:{roomId}:game:scores)에 초기 점수 0으로 등록
const scoreEntries: Array<{ score: number; value: string }> = [];
...
scoreEntries.push({ score: 0, value: userId });
...
await this.redisClient.zAdd(this.getScoreKey(roomId), scoreEntries);
score를 0으로 초기화하고 value에 userId를 넣었기 때문에, Redis 입장에서는 userId(멤버)의 현재 점수(스코어)는 0점이다라고 이해하고 저장
flowchart LR
%% 유저/클라이언트 영역
subgraph UserSide [Client Side]
A[User/Client]
end
%% 서버 영역
subgraph ServerSide [Application Layer]
B[NestJS Server]
end
%% 실시간 연산 영역 (Redis)
subgraph VolatileLayer [Real-time Engine]
C[(Redis ZSet)]
end
%% 영속성 저장 영역 (MySQL)
subgraph PersistentLayer [Database Layer]
D[(MySQL)]
end
%% 데이터 흐름 정의
A -- "1. 점수 획득 이벤트" --> B
B -- "2. zIncrBy (실시간 가산)" --> C
C -. "3. zRange (O(log N) 조회)" .-> B
B -- "4. 300ms 주기 랭킹 방송" --> A
B -- "5. 최종 결과 저장
(Post-game)" --> D
%% 스타일링
style C fill:#f96,stroke:#333,stroke-width:2px
style D fill:#69f,stroke:#333,stroke-width:2px
<aside>
node-redis는 zAdd 함수에 객체 배열을 전달할 때, score 필드를 점수로, value 필드를 **멤버(데이터)**로 인식하도록 설계되어 있음
명시적으로 점수순으로 정렬하라고 정해주지 않아도, Redis 엔진 자체가 ZSet에 들어오는 데이터를 항상 Score 기준으로 오름차순 정렬해서 보관하게 됨
</aside>