매일 아침 정산 메일을 보내는 스케줄러가 있었습니다. 서버를 두 대로 늘린 다음 날부터 사용자들이 같은 메일을 두 통씩 받기 시작했습니다. 코드는 그대로였는데 말이죠.
인스턴스마다 스케줄러가 돈다
@Scheduled는 그 애플리케이션 안에서 시간이 되면 실행됩니다. 문제는 서버가 두 대면 스케줄러도 두 개가 각자 돈다는 겁니다. 둘 다 아침 9시에 깨서 각자 메일을 보냈으니 사용자는 두 통을 받은 것이죠. 서버가 한 대일 땐 안 보이던 문제가 스케일아웃하는 순간 튀어나왔습니다.
한 번만 실행되도록 잠근다
분산 환경에서 특정 작업을 한 인스턴스만 실행하게 하려면 공유 저장소에 락을 걸어야 합니다. ShedLock을 붙여서 DB에 락 레코드를 두고, 락을 잡은 인스턴스만 실제 작업을 수행하게 했습니다.
@Scheduled(cron = "0 0 9 * * *")
@SchedulerLock(name = "dailySettlementMail", lockAtMostFor = "10m")
public void sendSettlementMail() { ... }
정리
로컬과 서버 한 대에선 절대 재현되지 않는 종류의 버그였습니다. 스케일아웃을 전제로 스케줄러를 짤 땐 이건 한 번만 돌아야 하는 작업인가부터 물어야 한다는 걸 배웠습니다.