국면 전환 자동 종료 기능
국면 전환 종료 조건을 주기적으로 확인해 자동 종료하는 스케줄러
아키텍처
개요
- 멀티 인스턴스(서버 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초 |
| 락 TTL | 30초 |
| TTL 초과 | 0회 |
- p99 1.2초, 최대 1.3초로 모든 실행이 30초 TTL 안에 완료
락 소유권·복구 검증
| 시나리오 | 확인 결과 |
|---|---|
| 4개 인스턴스의 동시 락 획득 | 1개 성공, 나머지 3개 종료 |
| 종료 로직 예외 발생 | AOP finally에서 소유자 토큰 확인 후 즉시 해제 |
| 프로세스 강제 종료 | 30초 TTL 경과 후 다른 인스턴스의 재획득 가능 |
| 이전 소유자의 지연된 해제 | 현재 소유자와 토큰이 달라 해제 요청을 무시하고, 새 소유자의 락 유지 |