사내 관제 시스템에선 선박의 불안정한 위성 네트워크 환경 위에서도 작업의 안정적 전달을 위해 FSM을 활용하고 있습니다. 각 작업의 실행 상태에 따라 READY, RUNNING, SUCCESS, FAILED 4가지 상태로 나뉘며, 실행 중 발생한 예외에 따라 이후에 재시도할지, 혹은 실패 처리하여 더 이상 실행하지 않을지 결정합니다.
예외별 동작은 다음과 같았습니다.
ResourceAccessException → 네트워크 I/O 문제 → 재시도RestClientException → 응답 데이터나 코드의 문제이므로 재시도해도 같은 결과 → 실패 처리이후 전 선박을 대상으로 하는 작업을 실행 후 모니터링 하던 중, 아래와 같은 문제가 생겼습니다.

응답에서 결과를 추출하던 중에 문제가 발생하여 작업이 FAILED 처리되었단 내용인데, 해당 선박은 소프트웨어 업데이트가 최신버젼으로 이뤄졌음으로, 위와 같은 상황이 나와선 안됐습니다.
Postman으로 해당 선박의 API를 호출하여 결과를 검증한 결과, 정상응답을 확인했습니다.
이를 토해 API가 정상응답을 반환함에도, 작업이 FAILED 처리되는 심각한 문제임을 확인했습니다.
FAILED처리의 이유를 확인하기 위해 StackTrace를 확인했습니다.
org.springframework.web.client.RestClientException:
Error while extracting response for type [...CrewClientQueryResponse]
Caused by: org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error: Read timed out
Caused by: tools.jackson.core.exc.JacksonIOException: Read timed out
at (through reference chain: CrewClientQueryResponse["data"] -> java.util.ArrayList[36])
Caused by: java.net.SocketTimeoutException: Read timed out
SocketTimeOut이 ResourceAccessException이 아니라 RestClientException으로 발생한 것을 확인할 수 있었으며, Jackson이 응답 바디의 data 배열에서 37번째 원소를 파싱하던 도중 타임아웃이 발생했음을 알 수 있습니다. 이를 통해,
Connect timeout 아님)Read timeout 시간 내에 도착하지 않음ReadTimeOut이 RestClientException으로 나타남RestClient가 응답 바디를 전부 버퍼링한 뒤 파싱하는 것이 아니라, 소켓의 InputStream을 Jackson 파서에 그대로 전달해 네트워크 읽기와 JSON 파싱을 동시에 진행하기 때문에 발생한 것으로 확인했습니다.
결과적으로, ReadTimeOut이 ResourceAccessException이 아닌, RestClientException으로 발생함을 알 수 있습니다.
"바디 읽기 중 타임아웃도 I/O 에러이므로 ResourceAccessException이어야 하지 않는가"라는 의문이 남았습니다. 확인해 보니 동일한 문제 제기가 이미 Spring Framework 이슈 트래커에 올라와 있었습니다. (spring-projects/spring-framework#33464)
Spring 웹 모듈 리드의 답변을 요약하면 다음과 같습니다.
ResourceAccessException은 요청을 실행하는 과정에서 발생하는 초기 I/O 문제를 위한 것이며, 응답을 처리하는 과정의 I/O 에러는 포함하지 않는다.
즉 이 동작은 의도된 설계였으며. Javadoc의 I/O error라는 표현이 실제 적용 범위보다 넓게 읽히는 것이 혼란의 근원이었습니다.
따라서 예외 ResourceAccessException만으로는 네트워크 문제 여부를 판단할 수 없음을 확인했습니다.
기존 ResourceAccessException 대신, RestClientException 발생 시 재시도 하도록, HttpStatusException 발생 시 실패처리하도록 구조를 변경했습니다. 이를 기존 테스트 코드 통과를 확인해 기존 동작과 일치하는지 확인했으며, 동일한 타임아웃 상황에서 작업이 다음 스케줄링에서 정상 재시도됨을 확인했습니다.
긴 글 읽어주셔서 감사합니다.