실시간 멀티플레이어 게임 서버는 플레이어의 접속 상태를 관리하고, 매칭을 처리하며, 게임 진행 중 발생하는 임시 데이터를 저장하고, 세션을 유지하고, 랭킹을 실시간으로 갱신해야 한다.
이러한 기능은 공통적으로 읽기와 쓰기가 빈번하고, 여러 서버가 동일한 데이터를 공유해야 하며, 빠른 응답 속도와 장애 대응이 중요하다는 특징을 가진다.
Redis는 메모리 기반 데이터 저장소로, String, Hash, Set, Sorted Set, List 등의 자료구조와 TTL, 원자적 연산을 제공한다. 따라서 온라인 상태, 매칭 대기열, 임시 게임 상태, 세션 데이터, 랭킹처럼 접근 빈도가 높은 데이터를 처리하는 데 적합하다.
다만 모든 게임 데이터를 Redis에 저장해야 하는 것은 아니다. 계정 정보, 결제 내역, 재화 원장, 경기 결과처럼 영구적인 보존이 필요한 데이터는 일반적으로 관계형 데이터베이스를 기준 저장소로 사용한다. Redis는 빠른 조회와 임시 상태 관리, 여러 서버 간 데이터 공유에 활용하는 것이 일반적이다.
온라인 상태 관리
온라인 상태란?
온라인 상태 관리는 플레이어가 현재 게임에 접속해 있는지, 마지막으로 언제 활동했는지, 어떤 서버에 연결되어 있는지를 관리하는 기능이다.
대표적인 상태는 다음과 같다.
ONLINE: 정상적으로 접속 중IN_GAME: 게임 플레이 중MATCHING: 매칭 대기 중OFFLINE: 접속 종료RECONNECTING: 연결 복구 처리 중
온라인 상태는 단순한 로그인 여부보다 복잡하다. 플레이어가 게임을 종료하지 않았더라도 네트워크 연결이 끊길 수 있고, 서버가 비정상적으로 종료되면 로그아웃 이벤트를 처리하지 못할 수도 있기 때문이다.
따라서 연결 종료 이벤트만으로 온라인 상태를 판단해서는 안 된다.
Redis를 이용한 온라인 상태 저장
플레이어의 마지막 활동 시각과 접속 서버를 Redis에 저장할 수 있다.
player:{playerId}:presence
Hash 자료구조를 사용하면 여러 상태 정보를 하나의 키에 저장할 수 있다.
HSET player:1001:presence status ONLINE serverId game-server-1 lastSeen 1792840000
위 데이터는 플레이어의 접속 상태, 연결된 서버, 마지막 활동 시각을 저장하는 예시다.
TTL을 이용한 연결 상태 관리
클라이언트가 일정 간격으로 Heartbeat를 전송하도록 구성할 수 있다.
예를 들어 클라이언트가 주기적으로 Heartbeat를 보내고, 서버가 해당 플레이어의 온라인 상태 키에 TTL을 설정한다고 가정하자.
SET player:1001:online 1 EX 30
Heartbeat를 수신할 때마다 TTL을 갱신한다. 설정된 시간 동안 Heartbeat가 도착하지 않으면 키가 만료된다. 이를 통해 연결이 끊겼거나 서버와 통신할 수 없는 플레이어를 탐지할 수 있다.
다만 TTL 만료가 곧바로 실제 연결 종료를 의미하는 것은 아니다. 네트워크 지연이나 일시적인 패킷 손실일 수 있으므로, 게임 정책에 따라 유예 시간이나 재접속 상태를 둘 필요가 있다.
Redis의 만료 이벤트 역시 정확한 시각에 반드시 전달되는 이벤트 스케줄러가 아니다. 따라서 만료 이벤트만으로 모든 정리 작업을 보장해서는 안 된다.
온라인 상태 설계 시 주의점
연결 상태와 게임 상태를 분리한다.
플레이어가 서버와 연결되어 있다는 사실과 게임에 참가 중이라는 사실은 다르다. 연결이 살아 있어도 로비에 있을 수 있고, 게임 도중 연결이 끊겼더라도 일정 시간 동안 게임 참가자로 유지해야 할 수 있다.
중복 접속을 제어한다.
동일 계정이 여러 서버에 접속할 수 있다면 세션 소유권을 관리해야 한다. 단순히 상태 키를 덮어쓰면 이전 연결이 새로운 연결의 상태를 변경하는 문제가 발생할 수 있다.
온라인 상태를 영구 데이터로 취급하지 않는다.
온라인 상태는 일시적인 운영 정보다. 서버 장애나 Redis 재시작 시 복구 전략을 마련하고, 실제 연결 상태와 주기적으로 대조해야 한다.
매칭 대기열
매칭 대기열이란?
매칭 대기열은 게임을 시작하려는 플레이어를 등록하고, 조건에 맞는 플레이어들을 그룹으로 묶는 시스템이다.
일반적인 매칭 흐름은 다음과 같다.
- 플레이어가 매칭을 요청한다.
- 서버가 요청을 검증한다.
- 플레이어를 대기열에 등록한다.
- 매칭 엔진이 조건에 맞는 플레이어를 탐색한다.
- 매칭이 성립하면 대기열에서 플레이어를 제거한다.
- 게임 Room 또는 서버 인스턴스에 참가시킨다.
매칭 시스템에서는 빠른 삽입과 삭제, 대기 시간에 따른 탐색, 중복 등록 방지가 중요하다.
Sorted Set을 이용한 대기열
Redis의 Sorted Set은 각 멤버에 점수(Score)를 부여하고 점수순으로 정렬하는 자료구조다.
이를 활용하면 대기열에 등록된 시각이나 매칭 우선순위를 기준으로 플레이어를 정렬할 수 있다.
ZADD matchmaking:ranked 1792840000 player:1001
ZADD matchmaking:ranked 1792840005 player:1002
ZADD matchmaking:ranked 1792840010 player:1003
위 예제에서는 등록 시각을 점수로 사용한다. 점수가 낮은 플레이어부터 조회하면 먼저 대기열에 진입한 플레이어를 우선적으로 탐색할 수 있다.
ZRANGE matchmaking:ranked 0 9
이 명령은 점수가 낮은 순서로 지정한 범위의 플레이어를 조회한다.
플레이어가 매칭을 취소하거나 매칭이 성립하면 다음과 같이 제거할 수 있다.
ZREM matchmaking:ranked player:1001
단, 이 구조는 대기 순서를 관리하는 예시다. 실제 경쟁 매칭에서는 MMR, 지역, 게임 모드, 파티 규모, 대기 시간 등을 함께 고려해야 한다.
MMR 기반 매칭
경쟁 게임에서는 단순히 먼저 들어온 플레이어를 묶는 것보다 실력 수준이 비슷한 플레이어를 매칭하는 것이 중요하다.
플레이어의 MMR을 점수로 사용하면 다음과 같이 저장할 수 있다.
ZADD matchmaking:mmr 1500 player:1001
ZADD matchmaking:mmr 1520 player:1002
ZADD matchmaking:mmr 1490 player:1003
하지만 MMR 자체를 점수로 사용한다고 해서 자동으로 적절한 매칭이 완성되는 것은 아니다. 매칭 엔진은 허용 MMR 차이, 대기 시간, 지역, 게임 모드 등의 조건을 검사해야 한다.
대기 시간이 길어질수록 허용 MMR 차이를 점진적으로 넓히는 정책도 적용할 수 있다. 실제 허용 범위는 게임의 플레이어 수와 경쟁 설계에 따라 달라진다.
매칭 대기열의 동시성 문제
매칭 시스템에서 중요한 문제는 여러 매칭 작업이 동시에 실행될 수 있다는 점이다.
두 매칭 작업이 같은 플레이어를 동시에 조회하면 플레이어가 중복으로 배정될 수 있다.
이를 방지하기 위해 다음과 같은 방법을 고려할 수 있다.
- 매칭 작업을 단일 조정자에서 직렬화한다.
- Redis Lua 스크립트로 조회, 검증, 제거 작업을 원자적으로 처리한다.
- 플레이어별 매칭 상태와 소유권을 검증한다.
- 매칭 요청에 고유한 요청 ID를 부여해 중복 요청을 방지한다.
- 매칭 결과를 확정하기 전에 플레이어의 상태를 다시 확인한다.
단순히 ZRANGE로 플레이어를 조회한 다음 ZREM으로 제거하는 두 작업만 수행하면 그 사이에 다른 작업이 개입할 수 있다. 따라서 경쟁 조건이 발생하는 구간을 원자적으로 처리해야 한다.
또한 대기열에서 플레이어를 제거한 뒤 게임 서버 배정에 실패할 수 있다. 이를 고려해 매칭 예약, 확정, 취소 상태를 구분하고 실패 시 복구할 수 있도록 설계해야 한다.
임시 게임 상태
임시 게임 상태란?
임시 게임 상태는 게임 진행 중에만 필요하거나, 짧은 시간 동안 유지하면 되는 데이터를 의미한다.
대표적인 예시는 다음과 같다.
- 로비의 임시 참가자 목록
- 게임 시작 전 준비 상태
- 방 생성 및 참가 예약 정보
- 일회성 초대 코드
- 제한 시간 내에 완료해야 하는 이벤트 상태
- 재접속을 위한 임시 복구 데이터
- 중복 요청을 방지하기 위한 임시 처리 기록
이러한 데이터는 영구적으로 보관할 필요가 없는 경우가 많다. Redis의 TTL을 이용하면 일정 시간이 지난 데이터를 자동으로 만료시킬 수 있다.
임시 Room 데이터 저장
게임 Room이 시작되기 전까지 참가자들의 준비 상태를 관리한다고 가정하자.
HSET room:5001:ready player:1001 true
HSET room:5001:ready player:1002 true
HSET room:5001:ready player:1003 false
각 플레이어의 준비 여부를 Hash로 관리할 수 있다.
Room 자체에 제한 시간을 두고 싶다면 별도의 TTL 키를 사용할 수 있다.
SET room:5001:lobby active EX 600
이 키는 설정한 시간 동안 유지된다. TTL은 임시 Room의 만료를 보조하는 수단이며, 실제 Room 종료와 관련된 자원 해제까지 자동으로 처리해 주지는 않는다.
따라서 Room 종료 시에는 참가자 목록, 매칭 정보, 서버 내부 객체, 타이머 등 관련 자원을 명시적으로 정리해야 한다.
임시 데이터와 권위 있는 게임 상태
여기서 구분해야 할 개념이 있다.
임시 저장소와 게임의 권위 있는 상태는 반드시 같지 않다.
실시간 액션 게임에서 플레이어의 위치, 속도, 충돌, 투사체 이동 등을 Redis에 매 프레임 저장하는 것은 일반적으로 비효율적이다.
이러한 상태는 보통 실제 게임 Room을 실행하는 서버의 메모리에서 관리하고, 필요한 경우 클라이언트에 동기화한다.
Redis는 다음과 같은 용도로 사용하는 것이 적절하다.
- 서버 간 공유가 필요한 임시 상태
- 재접속 시 복구할 데이터
- 매칭 및 Room 예약 정보
- 제한 시간이나 만료 정책이 있는 데이터
- 실시간 게임 로직에서 분리된 공용 상태
반면 프레임 단위로 갱신되는 물리 상태는 게임 서버 메모리에서 처리하고, 지속적으로 보존해야 하는 결과와 보상은 별도의 영속 저장 구조에 기록하는 방식이 일반적이다.
TTL과 데이터 정합성
TTL을 설정했다고 해서 데이터가 자동으로 안전해지는 것은 아니다.
Room 상태 키가 만료되었지만 게임 서버에서는 여전히 해당 Room을 실행하고 있을 수 있다. 반대로 Room은 종료되었지만 Redis의 관련 데이터가 남아 있을 수도 있다.
따라서 다음을 함께 설계해야 한다.
- 데이터 만료 시간
- 정상 종료 시 명시적인 정리
- 만료된 Room의 재사용 방지
- 장애 발생 시 상태 복구
- Room ID 또는 세대 번호를 이용한 오래된 데이터 식별
TTL은 정리 작업을 보조하는 기능이다. 게임의 상태 전이와 자원 해제를 대신하는 기능은 아니다.
세션 관련 데이터
세션 데이터란?
세션 데이터는 사용자가 접속한 이후 연결을 유지하고 요청을 처리하는 동안 필요한 상태 정보다.
게임 서버에서는 다음과 같은 정보를 관리할 수 있다.
- 로그인된 사용자 ID
- 인증 및 접속 상태
- 현재 접속 서버
- 현재 Room ID
- 마지막 활동 시각
- 재접속을 위한 임시 정보
- 중복 로그인 방지를 위한 세션 식별자
Access Token이나 Refresh Token 같은 인증 토큰과 게임 서버의 세션 상태는 구분해야 한다. 토큰은 인증에 사용되는 자격 증명이며, 세션 데이터는 현재 접속과 게임 상태를 관리하기 위한 정보다.
Redis를 이용한 세션 저장
세션 정보를 다음과 같이 저장할 수 있다.
HSET session:abc123 userId 1001 serverId game-server-1 roomId 5001
위 데이터는 세션 ID를 기준으로 사용자와 연결 서버, Room 정보를 관리하는 예시다.
세션에 만료 시간을 설정하려면 별도로 TTL을 지정한다.
EXPIRE session:abc123 3600
이 경우 세션 키는 설정한 시간이 지나면 만료된다. 세션 갱신 정책에 따라 만료 시간을 갱신해야 하며, 인증 토큰의 만료 시간과 반드시 동일하게 설정할 필요는 없다.
실제 구현에서는 HSET과 EXPIRE 사이에 장애가 발생해 TTL이 누락되는 상황도 고려해야 한다. 단일 Redis 명령이나 Lua 스크립트 등으로 저장과 만료 설정을 원자적으로 처리할 수 있다.
중복 로그인과 세션 소유권
동일 계정의 중복 접속을 허용하지 않는 게임이라면 사용자 ID를 기준으로 현재 유효한 세션을 추적할 수 있다.
SET user:1001:session abc123 EX 3600
새로운 세션이 생성되면 기존 세션을 무효화하거나 기존 접속을 종료하도록 처리할 수 있다.
하지만 단순히 사용자 ID에 세션 ID를 저장하는 것만으로는 모든 경쟁 조건을 해결할 수 없다.
예를 들어 기존 세션 A가 접속 중인 상태에서 새 세션 B가 로그인하고, B를 최신 세션으로 등록했다고 가정하자. 이후 늦게 도착한 A의 종료 이벤트가 사용자 상태를 OFFLINE으로 변경하면 실제로는 B가 접속 중인데도 사용자가 오프라인으로 표시될 수 있다.
이를 방지하려면 종료 이벤트를 처리할 때 해당 세션이 여전히 현재 소유 세션인지 확인해야 한다. 세션 ID 또는 세대 번호를 비교하고, 검증과 상태 변경을 원자적으로 수행하는 것이 안전하다.
재접속 처리
온라인 게임에서는 네트워크가 잠시 끊겼다고 해서 즉시 게임에서 제외하면 안 되는 경우가 있다.
재접속 흐름은 다음과 같이 구성할 수 있다.
- 연결이 끊기면 세션을 즉시 삭제하지 않는다.
- 일정 시간 동안 재접속을 허용한다.
- 플레이어의 Room ID와 복구에 필요한 정보를 유지한다.
- 재접속 요청에서 사용자와 세션의 유효성을 검증한다.
- 재접속이 성공하면 기존 게임 상태를 복원한다.
- 제한 시간이 지나면 세션과 관련된 임시 상태를 정리한다.
중요한 점은 세션 데이터만 저장한다고 게임 상태 전체가 복원되는 것은 아니라는 사실이다.
게임 진행 상태가 서버 메모리에만 존재한다면 해당 게임 서버가 종료되었을 때 복구하지 못할 수 있다. 따라서 재접속과 서버 장애 복구는 서로 다른 문제로 취급해야 한다.
랭킹 시스템
랭킹 시스템이란?
랭킹 시스템은 플레이어의 점수, 승리 횟수, MMR, 시즌 포인트 등을 기준으로 순위를 제공하는 기능이다.
랭킹은 점수가 자주 갱신될 수 있고, 상위 플레이어 조회가 빈번하며, 특정 플레이어의 현재 순위도 조회해야 한다. 동점자 처리와 시즌 종료 후 결과 보존 정책도 필요하다.
Redis의 Sorted Set은 멤버마다 점수를 저장하고 정렬된 상태를 유지하므로 랭킹 구현에 적합하다.
Sorted Set을 이용한 랭킹 구현
플레이어의 점수를 다음과 같이 저장할 수 있다.
ZADD ranking:season:current 1500 player:1001
ZADD ranking:season:current 1800 player:1002
ZADD ranking:season:current 1650 player:1003
키는 시즌 랭킹이고, 멤버는 플레이어 ID, 점수는 랭킹 점수다.
점수가 높은 플레이어부터 조회하려면 다음 명령을 사용한다.
ZREVRANGE ranking:season:current 0 9 WITHSCORES
이 명령은 상위 플레이어와 점수를 반환한다.
Redis의 최신 버전에서는 ZRANGE의 REV 옵션을 사용할 수도 있다.
ZRANGE ranking:season:current 0 9 REV WITHSCORES
플레이어의 순위 조회
특정 플레이어의 순위를 조회할 수도 있다.
ZREVRANK ranking:season:current player:1001
이 명령은 점수가 높은 순서에서 해당 플레이어의 순위를 영부터 반환한다. 따라서 화면에 표시할 순위는 결과에 하나를 더해 계산한다.
플레이어가 랭킹에 없다면 결과는 nil이므로 별도로 처리해야 한다.
플레이어의 점수는 다음 명령으로 조회할 수 있다.
ZSCORE ranking:season:current player:1001
Sorted Set을 활용하면 전체 플레이어를 매번 직접 정렬하지 않고도 상위 랭킹과 개별 순위를 효율적으로 조회할 수 있다.
점수 갱신과 중복 지급
플레이어의 점수를 갱신할 때는 ZINCRBY를 사용할 수 있다.
ZINCRBY ranking:season:current 25 player:1001
이 명령은 플레이어의 점수를 지정한 값만큼 증가시킨다.
다만 ZINCRBY 자체가 원자적이라고 해서 보상 시스템 전체가 중복 처리에 안전해지는 것은 아니다.
게임 결과 처리 요청이 재시도되어 같은 승리 보상이 두 번 반영되면 점수도 두 번 증가할 수 있다.
따라서 다음과 같은 구조를 고려해야 한다.
- 경기 결과에 고유한
matchId를 부여한다. - 해당 경기 결과가 이미 처리되었는지 확인한다.
- 결과 처리와 점수 반영이 중복되지 않도록 설계한다.
- 영구적인 랭킹 결과와 보상 내역을 데이터베이스에 기록한다.
- Redis 랭킹은 필요에 따라 영속 데이터에서 재구축할 수 있도록 한다.
Redis의 원자적 명령은 개별 명령의 동시성 문제를 줄여 주지만, 데이터베이스 기록과 Redis 갱신을 하나의 트랜잭션으로 묶어 주지는 않는다.
따라서 경기 결과와 보상 지급은 별도의 멱등성 및 복구 전략을 갖춰야 한다.
동점자와 랭킹 정책
Sorted Set은 점수가 같은 멤버에 대해 멤버 값의 사전식 순서를 적용한다. 따라서 점수만으로 게임의 동점자 정책을 모두 표현할 수 있는 것은 아니다.
게임 정책에 따라 다음과 같은 기준을 적용할 수 있다.
- 점수가 같으면 공동 순위로 처리한다.
- 점수가 같으면 먼저 달성한 플레이어를 우선한다.
- 점수가 같으면 승리 횟수가 많은 플레이어를 우선한다.
- 점수가 같으면 마지막 갱신 시각이나 별도 보조 지표를 사용한다.
정책에 따라 별도의 보조 데이터나 복합 점수 구조가 필요할 수 있다. 여러 기준을 하나의 숫자로 결합한다면 정밀도와 충돌 가능성을 검토해야 한다.
시즌 랭킹과 영속성
시즌 랭킹은 시즌별 키를 분리해 관리할 수 있다.
ranking:season:current
ranking:season:previous
시즌이 종료되면 해당 랭킹을 최종 확정하고 보상 지급 및 기록 보존 작업을 수행한다.
Redis는 캐시나 실시간 랭킹 저장소로 사용할 수 있지만, 운영 설정에 따라 데이터가 유실될 수 있다. 중요한 시즌 결과를 Redis에만 보관하는 것은 위험하다.
일반적으로 시즌 최종 결과는 관계형 데이터베이스 등 영속 저장소에 보관하고, Redis 랭킹은 실시간 조회를 위한 파생 데이터로 취급하는 편이 안전하다.
Redis와 관계형 데이터베이스의 역할 분리
Redis를 활용할 때는 저장 속도만 비교할 것이 아니라 데이터의 성격에 따라 저장소를 구분해야 한다.
| 데이터 | Redis 활용 | 영속 데이터베이스 활용 |
|---|---|---|
| 온라인 상태 | 적합 | 필요 시 접속 이력 저장 |
| 매칭 대기열 | 적합 | 매칭 이력과 결과 저장 |
| 임시 Room 상태 | 공유 및 복구용 상태에 적합 | 중요한 결과와 기록 저장 |
| 세션 정보 | 적합 | 계정 및 인증 관련 영속 정보 저장 |
| 실시간 랭킹 | 적합 | 시즌 결과 및 공식 기록 저장 |
| 재화 잔액과 원장 | 보조 캐시로 활용 가능 | 기준 데이터로 사용 |
| 결제와 보상 기록 | 중복 처리 보조에 활용 가능 | 영속 기록의 기준으로 사용 |
이 표는 일반적인 설계 방향이다. 실제 구조는 데이터의 유실 허용 범위, 복구 시간 목표, 트래픽, 운영 비용에 따라 달라진다.
핵심은 Redis에 저장되어 있다는 이유만으로 데이터가 안전하거나 영구적으로 보존된다고 가정하지 않는 것이다.
실전 아키텍처 예시
Node.js 기반의 실시간 멀티플레이어 서버를 구성한다고 가정하자.
Client
|
v
Game API / WebSocket Gateway
|
+----------------------+
| |
v v
Authentication Matchmaking Service
| |
v v
Session Service Redis Queue
| |
+----------+-----------+
|
v
Game Server
|
+--------------------+
| |
v v
In-Memory State Redis
(Room / Tick) (Shared State)
|
+-------------+-------------+
| | |
v v v
Presence Session Ranking
|
v
Relational Database
(Persistent Records)
각 구성 요소의 역할은 다음과 같다.
- Game API / WebSocket Gateway: 클라이언트 요청과 실시간 연결을 처리한다.
- Authentication: 로그인과 인증 정보를 검증한다.
- Matchmaking Service: 매칭 조건을 평가하고 대기열을 관리한다.
- Game Server: Room의 게임 로직과 프레임 단위 상태를 처리한다.
- Redis: 여러 서버가 공유하는 온라인 상태, 세션, 대기열, 랭킹 등을 관리한다.
- Relational Database: 계정, 경기 결과, 보상 내역, 시즌 기록 등 영속성이 필요한 데이터를 보관한다.
이 구조에서 Redis는 모든 게임 로직을 대신하는 서버가 아니다. 여러 서버가 공유해야 하거나 빠르게 접근해야 하는 데이터를 제공하는 역할을 담당한다.
운영 환경에서 고려해야 할 사항
Redis 장애 대응
Redis가 중단되면 온라인 상태 조회, 매칭, 세션 검증, 랭킹 조회 등의 기능이 영향을 받을 수 있다.
기능별로 장애 대응 정책을 정해야 한다.
- 매칭: 신규 매칭을 일시 중단하거나 대기열을 복구한다.
- 온라인 상태: 실제 연결 상태를 기준으로 재동기화한다.
- 세션: 검증할 수 없는 요청을 무조건 허용하지 않는다.
- 랭킹: 영속 데이터나 복구 가능한 기록을 기준으로 재구축한다.
- 임시 상태: 유실 시 재생성 가능한 데이터와 반드시 복구해야 하는 데이터를 구분한다.
데이터 만료와 메모리 관리
온라인 상태와 세션처럼 만료가 필요한 데이터는 TTL을 설정해 메모리를 관리할 수 있다.
그러나 모든 키에 무조건 TTL을 설정하는 것은 적절하지 않다. 랭킹처럼 시즌 동안 유지해야 하는 데이터는 별도의 보존 정책이 필요하다.
TTL이 설정된 키라도 즉시 메모리에서 제거된다고 가정해서는 안 된다. Redis는 만료 처리를 수행하지만 실제 메모리 회수 시점은 구현과 실행 상황에 따라 달라질 수 있다.
원자성과 분산 환경
Redis 명령 하나의 원자성과 여러 서비스에 걸친 작업의 원자성은 서로 다른 문제다.
예를 들어 대기열에서 플레이어 제거, Room 참가 예약, 경기 시작 상태 기록, 데이터베이스에 경기 정보 저장 등의 작업이 연속으로 수행될 수 있다. 중간 단계에서 서버가 종료되면 일부 작업만 완료될 수 있다.
이를 해결하기 위해서는 작업 상태를 명시적으로 관리하고, 멱등성 키, 재시도, 보상 작업, 주기적인 정합성 검증 등을 적용해야 한다.
관측 가능성
실시간 게임 서버에서는 Redis 응답 시간이 빠른지만 확인해서는 부족하다.
다음과 같은 지표를 함께 관찰하는 것이 좋다.
- Redis 명령의 응답 시간
- 연결 수와 Connection Pool 사용량
- 메모리 사용량과 만료되는 키 수
- 매칭 대기열의 크기와 평균 대기 시간
- 온라인 상태와 실제 연결 상태의 불일치
- 세션 검증 실패 횟수
- 랭킹 갱신 실패 및 재처리 횟수
이러한 지표를 수집하면 성능 문제와 데이터 정합성 문제를 구분해 분석할 수 있다.