WORK EXPERIENCE

국면 전환 자동 종료 기능

국면 전환 종료 조건을 주기적으로 확인해 자동 종료하는 스케줄러

Java · Spring · Redis

아키텍처

인스턴스 A스케줄러 실행 중인스턴스 B스케줄러 실행 중인스턴스 C스케줄러 실행 중인스턴스 D스케줄러 실행 중Redis 분산 락단일 실행락 획득 인스턴스종료 로직 수행

개요

  • 멀티 인스턴스(서버 4대) 환경에서 10초마다 국면 종료 조건을 확인하는 스케줄러
  • Redis 분산 락을 획득한 인스턴스 1대만 종료 로직 수행

문제

  • 서버 4대에서 같은 스케줄러가 10초마다 동시에 실행
  • 각 인스턴스가 같은 국면의 상태를 동시에 조회·변경하면서 Race Condition 발생
  • 그 결과 국면이 종료되지 않거나 종료 로그와 WebSocket 상태 알림이 최대 4회 중복

기술 선택 이유

  • Redis 분산 락

    • 서버 4대가 공유하는 실행 제어 지점 필요
    • 기존 Redis로 별도 구성 요소를 추가하지 않고, DB Lock 없이 스케줄러 실행만 제어
  • TTL·소유자 토큰 기반 해제

    • 프로세스가 종료돼도 락이 남지 않도록 30초 후 자동 만료
    • UUID 토큰과 Lua 비교 삭제로 현재 소유자만 락 해제
  • AOP 공통화

    • 반복되는 락 획득·해제를 공통화해 신규 스케줄러에도 어노테이션으로 동일 정책 적용

해결 전략

  • Redis SET NX PX로 락 획득과 30초 TTL 설정을 한 번에 처리
  • UUID 소유자 토큰이 일치할 때만 Lua로 락을 삭제해 이전 소유자가 새 락을 해제하지 못하도록 방지
  • @ScheduleLock과 AOP로 락 획득·해제를 공통화하고 finally에서 해제

검증

테스트 설정

  • 동일한 Redis·MySQL을 사용하는 API 인스턴스 4개의 실행 시각을 맞추고, 종료 가능한 국면 1건으로 분산 락 적용 전후 비교
  • 인스턴스 ID·락 획득 결과를 기록하고 국면 ID별 종료 로그와 상태 알림 수 대조

동시 실행 결과

지표분산 락 미적용Redis 분산 락 적용
총 스케줄러 실행 요청4회4회
종료 로직 진입4회1회
국면 종료 로그4건1건
종료 상태 알림4건1건
  • 락을 얻지 못한 3개 요청은 Redis 단계에서 종료. 종료 로직과 알림은 락을 얻은 1개 인스턴스만 수행

종료 로직 수행 시간 측정

  • 종료 가능한 국면 1건을 매회 초기화하고, 락 획득부터 상태 변경·로그·알림 완료까지의 시작·완료 시각을 1,000회 기록
측정 항목결과
측정 횟수1,000회
수행 시간 p99약 1.2초
최대 수행 시간약 1.3초
락 TTL30초
TTL 초과0회
  • p99 1.2초, 최대 1.3초로 모든 실행이 30초 TTL 안에 완료

락 소유권·복구 검증

시나리오확인 결과
4개 인스턴스의 동시 락 획득1개 성공, 나머지 3개 종료
종료 로직 예외 발생AOP finally에서 소유자 토큰 확인 후 즉시 해제
프로세스 강제 종료30초 TTL 경과 후 다른 인스턴스의 재획득 가능
이전 소유자의 지연된 해제현재 소유자와 토큰이 달라 해제 요청을 무시하고, 새 소유자의 락 유지