작성자: 권지원

멀티 인스턴스 환경에서 스케줄러 중복 실행 방지

<aside>


1. 배경

ForPets 백엔드는 수평 확장(scale-out)을 전제로 설계되어 있다. 트래픽이 늘어나면 동일한 애플리케이션을 N대 복제해서 로드밸런서 뒤에 배치한다.

문제는 @Scheduled 어노테이션이 JVM 단위로 동작한다는 점이다. 인스턴스가 3대라면 매 주기마다 스케줄러가 3번 실행된다.

현재 ForPets 에는 4개의 스케줄러가 존재한다:

스케줄러 주기 주요 작업 외부 I/O
ReservationExpireScheduler 1분 PENDING 예약 만료 + 환불 + 상태 복원 PortOne 환불 API
PostExpireScheduler 10분 OPEN 공고 만료 + Proposal cascade 만료 DB only
CareRequestExpireScheduler 10분 PENDING/ACCEPTED 돌봄 요청 만료 DB only
UnavoidableCancelAutoApproveScheduler 매일 00:00 UNAVOIDABLE 취소 자동 승인 + 알림 발송 Kafka

2. 멀티 인스턴스에서 예상되는 문제

실제 장애를 재현하기 전에 설계 단계에서 리스크를 먼저 분석했다. 아래는 인스턴스 3대(A, B, C)가 동시에 스케줄러를 실행할 때 발생 가능한 문제들이다.

2.1 PortOne 환불 API 중복 호출

ReservationExpireScheduler 는 만료 대상 예약에 대해 PortOne 환불 API 를 호출한다.

T=0    A, B, C 가 동시에 만료 대상 예약 조회
       → 동일한 reservation_id 목록을 각자 조회
T=0.1  A, B, C 가 동시에 PortOne 환불 API 호출
       → 동일한 건에 대해 환불 요청 3회 발생

PortOne 은 멱등성 키(paymentId)가 있어 실제 이중 환불은 막히지만, 의도치 않은 API 호출이 발생했다는 사실 자체가 시스템 이상이다. 환불 API 스펙이 바뀌거나, 멱등성 키 없는 다른 외부 API 로 교체될 경우 실제 이중 환불로 이어질 수 있다.

2.2 Kafka 알림 중복 발행

UnavoidableCancelAutoApproveScheduler 는 cron 으로 정확히 00:00 에 트리거된다. N대가 거의 동시에 같은 대상을 조회하고 Kafka 에 알림 이벤트를 발행하면, 사용자는 동일한 알림을 N번 받는다.

엔티티 락(lock:reservation:{id})이 있어 실제 상태 변경은 1회만 일어나지만, 락 획득 이전에 이미 알림 발행 코드가 실행되는 race window 가 존재한다. 이 경우 상태는 정합성을 유지하더라도 알림은 중복 발송된다.

2.3 DB 부하 N배 증가