2025년 3월 7일

김동환·2025년 3월 7일

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;
}

// Lock 설정
await this.redisClient.set(gameLockKey, 'locked', 'EX', 10);

2. 게임 결과 및 업적 저장 시 중복 방지

  • Redis에 gameResultKeygameAchievementsKey가 존재하는지 확인 후 중복 저장을 방지하는 로직을 추가했다.
  • 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 적용 가능성 검토).
  • 게임 서버의 로깅 방식 개선하여 중복 로그 발생 방지.
profile
Node.js 7기

0개의 댓글