본문 바로가기
Projects/청하-청년을 위한 커뮤니티 서비스

[청하] Open API 호출 시 500 오류 발생 및 해결 과정

by Lpromotion 2025. 5. 23.

목차
 

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>

 

인사이트

  1. 외부 API는 항상 안정적인 응답을 보장하지 않으므로 재시도(retry())와 딜레이(delayElements())를 적용하는 것이 중요하다.
  2. 예상하지 못한 API 응답 구조 변화에 대비하여 Optional을 활용한 null 방어 코드를 작성해야 한다.
  3. API 서버 부하를 줄이기 위해 너무 많은 요청을 동시에 보내지 않는 것이 중요하다.
반응형

댓글