목차
1. 문제 상황 (배경)
청년 정책 데이터를 제공하는 Open API(https://www.youthcenter.go.kr/go/ythip/getPlcy)를 호출하여 데이터를 가져오는 과정에서 간헐적으로 500 Internal Server Error가 발생했다.
특정 요청에서는 정상적으로 응답을 받지만, 일부 요청에서는 오류가 발생하여 정책 데이터를 가져오지 못하는 문제가 있었다.
발생한 오류 로그
2025-03-05T23:14:06.307+09:00 ERROR 37872 --- [nio-8080-exec-2] c.e.w.service.YouthPolicyService : Failed to fetch policy data from Open API: 500 Internal Server Error from GET <https://www.youthcenter.go.kr/go/ythip/getPlcy>
org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from GET <https://www.youthcenter.go.kr/go/ythip/getPlcy>
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:318) ~[spring-webflux-6.1.3.jar!/:6.1.3]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ? 500 INTERNAL_SERVER_ERROR from GET <https://www.youthcenter.go.kr/go/ythip/getPlcy> [DefaultWebClient]
Original Stack Trace:
2. 원인 분석 - 가능한 원인들
500 오류는 서버 측 오류이므로, Open API 서버의 상태나 요청 방식이 문제일 가능성이 크다.
이런 원인이 있을 수 있다고 판단했다.
- API 서버의 과부하
- Open API 서버가 동시에 너무 많은 요청을 받을 경우, 일부 요청에서 500 오류를 반환할 가능성이 있음.
- 다시 요청하면 정상 응답이 오는 것을 보면, 서버 부하로 인해 일부 요청만 실패하는 것으로 보임.
- 너무 빠른 요청 속도
- Flux.range(1, totalPageCount)로 여러 페이지의 데이터를 병렬로 요청하면서, 서버가 순간적으로 많은 요청을 처리해야 함.
- 일부 요청이 실패할 확률이 높아짐.
- API 응답 JSON 구조 문제
- Open API 응답이 일관되지 않거나, 특정 필드가 없을 경우 데이터 매핑에서 예외가 발생할 가능성 있음.
- Optional.ofNullable(dto.result()).map(YouthPolicyResultDto::policyList).orElse(Collections.emptyList()) 로 대비.
- API Key 사용 제한
- Open API가 짧은 시간 내에 같은 API Key로 너무 많은 요청을 받을 경우, 일시적으로 제한할 가능성이 있음.
- 이 경우 500이 아니라 403 Forbidden이 뜰 가능성이 높지만, 확인할 필요가 있음.
3. 해결 방법
위 원인들을 고려하여 재시도(retry()), 요청 간 딜레이(delayElements()), 응답 검증(Optional) 을 추가하는 방식으로 해결했다.
적용한 코드
return Flux.range(1, totalPageCount)
.delayElements(Duration.ofMillis(500)) // 요청 간 500ms 딜레이 (서버 부하 방지)
.concatMap(pageNum ->
webClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("pageType", 2)
.queryParam("rtnType", "json")
.queryParam("apiKeyNm", apiKeyNm)
.queryParam("pageNum", pageNum)
.queryParam("pageSize", 60)
.build())
.retrieve()
.bodyToMono(NewYouthPolicyListResponseDto.class)
.retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(2))) // 최대 3번, 2초 간격으로 재시도
.flatMapIterable(dto ->
Optional.ofNullable(dto.result()) // 응답이 null일 경우 대비
.map(YouthPolicyResultDto::policyList)
.orElse(Collections.emptyList())
)
)
.map(dto -> dto.toEntity(legalDongCodeCache, sortOrder.getAndIncrement())) // DTO -> Entity 변환 + 순서 부여
.collectList() // List<YouthPolicy> 형태로 변환
.block(); // 동기적으로 실행하여 전체 데이터 획득
해결 방법 정리
- 재시도(Retry) 추가
- retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(2)))
- 500 오류 발생 시 2초 간격으로 최대 3번 재시도 → 일시적인 서버 오류 대응.
- 요청 속도 조절 (딜레이 추가)
- delayElements(Duration.ofMillis(500))
- 0.5초씩 간격을 두고 요청 → API 서버 부하 완화.
- API 응답 검증 (null 방어)
- Optional.ofNullable(dto.result()).map(YouthPolicyResultDto::policyList).orElse(Collections.emptyList())
- 응답이 null이거나 예상과 다를 경우 대비하여 빈 리스트 반환 → 예외 방지.
4. 결과
위 방식으로 수정 후 500 오류 없이 성공적으로 정책 데이터를 가져올 수 있었음
이제 API 호출이 안정적으로 동작하며, 서버 부하나 일시적인 장애가 발생해도 자동으로 재시도하여 데이터를 정상적으로 가져올 수 있음.
2025-03-06T22:29:11.706+09:00 INFO 45900 --- [nio-8080-exec-5] c.e.w.service.YouthPolicyService : Fetching youth policy data from external API...
2025-03-06T22:29:13.093+09:00 WARN 45900 --- [ctor-http-nio-3] c.e.w.service.YouthPolicyService : API request failed, retrying... pageNum=2, error: 500 Internal Server Error from GET <https://www.youthcenter.go.kr/go/ythip/getPlcy>
인사이트
- 외부 API는 항상 안정적인 응답을 보장하지 않으므로 재시도(retry())와 딜레이(delayElements())를 적용하는 것이 중요하다.
- 예상하지 못한 API 응답 구조 변화에 대비하여 Optional을 활용한 null 방어 코드를 작성해야 한다.
- API 서버 부하를 줄이기 위해 너무 많은 요청을 동시에 보내지 않는 것이 중요하다.
반응형
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] 청년정책 Open API 개편에 따른 데이터 비교 (기존 vs 개선후) (1) | 2025.05.15 |
---|---|
[청하] 밸런스 게임/토론 기능 - (4) 댓글 조회 기능 개선 (feat. N+1 문제 방지를 위한 설계와 구현) (0) | 2025.04.30 |
[청하] TimeFormatter 클래스 리팩터링 (feat. @UtilityClass) (0) | 2025.04.04 |
[청하] 청년 정책 검색 기능 - (2) 구현 (0) | 2025.04.04 |
[청하] Swagger UI 개선 - API 문서 가독성 향상 (0) | 2025.04.03 |
댓글