3.9 KiB
3.9 KiB
Timeout 예시
좋은 예시
예시 1. 전역 기본값은 공통 설정으로 둔다
spring:
http:
clients:
connect-timeout: 500ms
read-timeout: 2s
좋은 이유:
- 서비스 전체 기본값이 한 곳에 있다
- 모든 client에 최소 timeout 정책이 적용된다.
예시 2. provider별 차이는 HTTP service group에서 override한다
spring:
http:
clients:
connect-timeout: 500ms
serviceclient:
keycloak:
base-url: https://keycloak.example.com
read-timeout: 3s
payment:
base-url: https://payment.example.com
read-timeout: 5s
좋은 이유:
- 공통 기본값과 provider별 차이가 함께 보인다
- Spring Boot가 제공하는 group-level connect/read timeout 구조와 맞는다.
예시 3. RestClient는 주입된 builder를 사용한다
@Service
class KeycloakTokenClient {
private final RestClient restClient;
KeycloakTokenClient(RestClient.Builder builder, KeycloakProperties properties) {
this.restClient = builder
.baseUrl(properties.baseUrl())
.build();
}
}
좋은 이유:
- Boot auto-configuration과 공통 timeout/customizer를 따른다
- RestClient.create()로 공통 구성을 우회하지 않는다.
예시 4. WebClient는 Reactor Netty timeout을 명시적으로 구성할 수 있다
@Bean
WebClient paymentWebClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(3))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000);
return builder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("https://payment.example.com")
.build();
}
좋은 이유:
- connect timeout과 response timeout을 분리한다
- Reactor Netty의 구체 timeout 지점을 활용한다.
예시 5. timeout 값은 operation별로 명시적 override만 허용한다
Mono<ResponseDto> callLongRunningOperation(RequestDto request) {
return webClient.post()
.uri("/reports")
.bodyValue(request)
.retrieve()
.bodyToMono(ResponseDto.class)
.timeout(Duration.ofSeconds(8));
}
좋은 이유:
- “이 operation만 더 길다”는 의도가 코드에 드러난다
- 기본값과 다른 이유를 문서화하기 쉽다
주의:
- reactive 전체 timeout()은 최후 수단에 가깝고, 가능하면 client-specific timeout이 더 우선이다.
나쁜 예시
예시 1. 외부 호출에 timeout이 없다
@Service
class BadExternalClient {
private final RestClient restClient = RestClient.create("https://example.com");
}
나쁜 이유:
- 공통 timeout/customizer/관측 구성을 우회한다
- 무제한 또는 의도 불명확한 대기에 빠질 수 있다.
예시 2. 너무 낮은 timeout을 근거 없이 하드코딩한다
webClient.get()
.uri("/token")
.retrieve()
.bodyToMono(TokenResponse.class)
.timeout(Duration.ofMillis(20));
나쁜 이유:
- TLS handshake, 새 연결, DNS 비용을 고려하지 않은 값일 수 있다
- 배포 직후/콜드 커넥션에서 false timeout을 유발하기 쉽다.
예시 3. timeout 값을 서비스 전체에 하나의 숫자로 강제한다
external:
timeout-ms: 1000
나쁜 이유:
- provider별 latency와 business 중요도가 다를 수 있다
- connect/read/response 구분도 사라진다
- operation별 차이를 담기 어렵다
예시 4. timeout 이후 side effect API를 무심코 재시도한다
try {
paymentClient.capture(request);
} catch (TimeoutException ex) {
paymentClient.capture(request);
}
나쁜 이유:
- timeout이 side effect 미발생을 보장하지 않는다
- idempotency 검토 없이 중복 실행 위험이 생긴다.