Today I Learned (TIL) - 2025.03.07
1. 게임 종료 시 중복 실행 방지
isGameEnding Set을 사용하지 않고 Redis를 활용하여 중복 실행을 방지하는 방법을 적용했다.
NX 옵션을 사용하지 않고 set 명령어를 실행하여 Lock을 설정했다.
- 게임 종료 로직 실행이 끝난 후 Redis에서 Lock을 삭제하여 다음 실행을 허용하도록 했다.
적용한 코드
const gameLockKey = `lock:endGame:${roomId}`;
const lockExists = await this.redisClient.exists(gameLockKey);
if (lockExists) {
console.warn(`게임 종료가 이미 진행 중 (roomId: ${roomId}), 중복 실행 방지.`);
return;
}
await this.redisClient.set(gameLockKey, 'locked', 'EX', 10);
2. 게임 결과 및 업적 저장 시 중복 방지
- Redis에
gameResultKey와 gameAchievementsKey가 존재하는지 확인 후 중복 저장을 방지하는 로직을 추가했다.
exists 메서드를 사용하여 기존 데이터가 있는 경우 저장하지 않도록 했다.
적용한 코드
const isAlreadyStored = await this.redisClient.exists(gameResultKey);
if (isAlreadyStored) {
console.warn(`이미 저장된 게임 결과 (gameId: ${gameId}), 저장 안 함.`);
return;
}
3. Redis Pub/Sub 구독 중복 문제 해결
GameResultsSubscriber가 여러 번 실행되면서 Redis 구독이 중복으로 이루어지는 문제를 해결했다.
- 구독이 중복으로 실행되지 않도록 서버 실행 시
onModuleInit 내부에서 로그를 추가하고, 기존 연결이 있는 경우 새로운 구독을 생성하지 않도록 했다.
적용한 코드
if (this.redisSubscriber.status !== 'ready') {
console.log('게임 결과 및 게임 업적 Redis 구독 시작');
this.redisSubscriber.subscribe('gameResults', 'gameAchievements');
}
4. 게임 결과 및 업적 데이터베이스 저장 로직 수정
GameResultsSubscriber에서 게임 결과를 저장할 때 AchievementsService를 호출하여 업적 데이터를 함께 저장하도록 변경했다.
AchievementsService에서 playerAchievements 데이터를 기반으로 UserAchievementsRepository를 통해 데이터를 저장하도록 했다.
적용한 코드
await this.achievementsService.processGameAchievements(gameId, playerAchievements);
5. UserAchievementsRepository 예외 처리 개선
userId 또는 achieveId가 존재하지 않을 경우 더 명확한 예외 메시지를 출력하도록 변경했다.
- 기존
throw new Error(...) 대신 NotFoundException을 사용하여 예외를 관리했다.
적용한 코드
if (!user) {
throw new NotFoundException(`유저(${userId})를 찾을 수 없습니다. 회원가입을 확인하세요.`);
}
if (!achieve) {
throw new NotFoundException(`업적(${achieveId})을 찾을 수 없습니다. 업적 데이터를 로드하세요.`);
}
6. 업적 데이터 저장 방식 개선
- 게임 업적(
gameAchievements)과 일반 업적(achievements)이 올바르게 저장되도록 수정했다.
achievements.list.json에서 업적 데이터를 불러올 때 existsByConditionAndCount를 활용하여 중복 저장을 방지했다.
적용한 코드
const exists = await this.achieveRepository.existsByConditionAndCount(
achievement.condition,
achievement.conditionCount,
);
if (!exists) {
await this.achieveRepository.createAchieve(achievement);
}
오늘의 문제 해결 포인트
- 중복 실행 방지: Redis Lock을 사용하여
endGame이 여러 번 실행되는 것을 방지.
- 게임 결과 및 업적 중복 저장 방지: Redis의
exists 메서드를 활용하여 동일한 데이터가 여러 번 저장되지 않도록 처리.
- 구독 중복 실행 방지:
GameResultsSubscriber에서 기존 구독이 활성화된 경우 새로운 구독을 만들지 않도록 변경.
- 업적 데이터 정확성 개선:
UserAchievementsRepository에서 업적을 저장할 때 정확한 데이터 검증을 추가.
- 명확한 예외 메시지 추가:
NotFoundException을 활용하여 디버깅 시 원인을 쉽게 파악할 수 있도록 개선.
다음 개선할 점
- 게임 종료 후 Redis에서 불필요한 데이터 정리 로직 추가.
- 업적 데이터 저장 시 성능 최적화 (Batch Insert 적용 가능성 검토).
- 게임 서버의 로깅 방식 개선하여 중복 로그 발생 방지.