2025. 5. 15. 17:18ㆍBackend/Trouble Shooting
RestTemplate로 API가 두 번 호출되는 이유, 실무에서 겪은 중복 처리 이슈 해결기
실무에서 한 번쯤은 이런 상황을 겪었을 것이다.
한 번만 호출한 API가 서버에서 두 번 처리된 경우.
얼핏 보면 코드에 버그가 있거나 스케줄러가 중복 실행된 것 같지만, 로그를 아무리 뜯어봐도 분명히 요청은 한 번뿐이다.
나 역시 비슷한 상황에 부딪혔고, 그 원인을 추적하면서 RestTemplate의 동작 방식과 로드밸런서의 재시도 설정에 대해 깊이 이해할 수 있었다.
관리자 서버는 한 번만 호출했다
문제는 아래처럼 생긴 단순한 API 호출 코드에서 시작됐다.
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.GET, requestEntity, String.class
);
이 코드는 관리자 서버의 크론탭에 등록된 스케줄러에서 실행되었고,
매주 정해진 시간에 외부 백엔드 서버(API 서버)로 요청을 보낸다.
그런데 이상한 일이 벌어졌다.
Datadog 로그를 통해 확인해보니,
API 서버 A, B에서 같은 요청을 각각 한 번씩 처리한 로그가 남아 있었던 것이다.
RestTemplate이 재시도한 걸까?
처음엔 RestTemplate가 내부적으로 재시도한 게 아닐까 의심했다.
하지만 공식 문서나 실제 코드 레벨에서 확인해보면,
RestTemplate은 기본적으로 재시도 기능을 제공하지 않는다.
예외가 발생하면 그냥 바로 예외를 던진다.
HttpClientErrorException, ResourceAccessException 같은 익숙한 예외가 떠오를 것이다.
즉, 클라이언트에서 동일한 요청이 두 번 전송된 건 아니라는 뜻이다.
진짜 원인은 로드밸런서?
그렇다면 왜 서버 A와 B에서 모두 요청이 처리되었을까?
여기서 의심하게 된 것이 로드밸런서의 재시도 설정이었다.
특히 IDC 환경에서 사용하는 L4 로드밸런서는 아래와 같은 방식으로 동작할 수 있다.
- 요청을 A 서버에 전달한다.
- 응답이 일정 시간 안에 오지 않으면, 장애로 간주하고 요청을 B 서버로 재전송한다.
이렇게 되면 클라이언트(RestTemplate)는 한 번만 요청했지만,
서버 입장에선 두 번 처리된 셈이 된다.
확인할 수 있는 로그 지표들
로드밸런서 설정을 직접 확인할 수 없는 환경이라면,
아래 지표들을 통해 우회적으로 상황을 분석할 수 있다.
- X-Request-ID, X-Forwarded-For 같은 헤더:
- 같은 요청 ID로 여러 서버에서 로그가 찍혔다면 의심할 수 있다.
- 타임스탬프 비교:
- 요청이 거의 동시에 두 서버에서 처리되었다면, 로드밸런서의 재시도를 의심할 수 있다.
실무에서 취할 수 있는 조치들
이 문제를 해결하기 위해 여러 방법을 검토했고, 아래 두 가지 방식이 가장 현실적이었다.
1. 스케줄러 중복 실행 방지
분산 환경에서는 하나의 스케줄러가 여러 서버에서 동시에 실행될 수 있다.
이럴 땐 ShedLock 같은 라이브러리를 써서 실행 락을 걸어주는 게 좋다.
@Scheduled(cron = "0 0 8 * * MON")
@SchedulerLock(name = "newsletterJob", lockAtLeastFor = "PT10M")
public void sendNewsletter() {
...
}
2. 중복 요청 필터링
RestTemplate로 요청을 보낼 때,
X-Request-ID나 taskId 같은 고유 키를 같이 넘겨서 서버에서 중복 요청을 걸러낼 수 있는 구조를 갖추는 것도 하나의 방법이다.
마무리하며
이 경험을 통해 하나 깨달은 게 있다.
실제 배포 환경을 이해하지 못하면, 로직을 잘 짜도 문제가 생긴다.
코드만 보면 아무 문제 없어 보이지만,
실제 운영 환경에서는 로드밸런서, 스케줄러, 네트워크 등 다양한 요소가 변수로 작용한다.
앞으로 RestTemplate을 쓸 때는 항상 한 가지 더 고민하게 될 것 같다.
이 요청, 정말 한 번만 처리되는 걸까?
조금 더 자세히
velog 에서 확인할 수 있다.
Spring RestTemplate 중복 호출 이슈 분석 – 재시도 정책과 로드밸런서 설정까지