Retry 및 Timeout
Retry와 Timeout은 마이크로서비스의 복원력을 높이는 핵심 메커니즘입니다. Istio를 사용하면 애플리케이션 코드 변경 없이 이러한 정책을 설정할 수 있습니다.
목차
개요
Timeout과 Retry의 필요성
Timeout 설정
기본 Timeout
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-timeout
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
timeout: 10s # 10초 후 타임아웃경로별 Timeout
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-timeouts
spec:
hosts:
- api.example.com
http:
# 빠른 응답이 필요한 API - 짧은 timeout
- match:
- uri:
prefix: "/api/quick"
route:
- destination:
host: api-service
timeout: 1s
# 일반 API
- match:
- uri:
prefix: "/api/standard"
route:
- destination:
host: api-service
timeout: 5s
# 무거운 작업 - 긴 timeout
- match:
- uri:
prefix: "/api/batch"
route:
- destination:
host: api-service
timeout: 30sRetry 설정
기본 Retry
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-retry
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
retries:
attempts: 3 # 최대 3번 재시도
perTryTimeout: 2s # 각 시도마다 2초 timeout
retryOn: 5xx,reset,connect-failure,refused-stream # 재시도 조건Retry 조건
| 조건 | 설명 |
|---|---|
5xx | HTTP 5xx 에러 |
gateway-error | 502, 503, 504 에러 |
reset | 연결 리셋 |
connect-failure | 연결 실패 |
refused-stream | HTTP/2 REFUSED_STREAM |
retriable-4xx | 409 Conflict |
retriable-status-codes | 사용자 정의 상태 코드 |
고급 Retry 설정
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: advanced-retry
spec:
hosts:
- payment-service
http:
- route:
- destination:
host: payment-service
retries:
attempts: 5
perTryTimeout: 3s
retryOn: 5xx,reset,connect-failure
retryRemoteLocalities: true # 다른 지역으로도 재시도Retry와 Timeout 조합
계층별 Timeout
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: layered-timeouts
spec:
hosts:
- frontend
http:
- route:
- destination:
host: frontend
timeout: 10s # 전체 timeout
retries:
attempts: 3
perTryTimeout: 3s # 각 재시도 timeout계산: 최대 대기 시간 = min(전체 timeout, attempts × perTryTimeout) = min(10s, 3 × 3s) = 9s
멱등성 보장이 필요한 경우
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: idempotent-retry
spec:
hosts:
- order-service
http:
# GET 요청 - 안전하게 재시도 가능
- match:
- method:
exact: GET
route:
- destination:
host: order-service
timeout: 5s
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx,reset
# POST 요청 - 재시도 제한적
- match:
- method:
exact: POST
route:
- destination:
host: order-service
timeout: 10s
retries:
attempts: 1 # 멱등성 보장 안 되면 재시도 최소화
perTryTimeout: 5s
retryOn: connect-failure,reset # 네트워크 문제만 재시도실전 예제
예제 1: 마이크로서비스 체인
yaml
# Frontend → Backend → Database
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: frontend
spec:
hosts:
- frontend
http:
- route:
- destination:
host: frontend
timeout: 15s # 전체 체인 고려
retries:
attempts: 2
perTryTimeout: 7s
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: backend
spec:
hosts:
- backend
http:
- route:
- destination:
host: backend
timeout: 10s # Database 호출 고려
retries:
attempts: 3
perTryTimeout: 3s
retryOn: 5xx,reset
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: database
spec:
hosts:
- database
http:
- route:
- destination:
host: database
timeout: 5s
retries:
attempts: 2
perTryTimeout: 2s
retryOn: connect-failure,refused-stream예제 2: 외부 API 호출
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: external-api
spec:
hosts:
- api.external.com
http:
- route:
- destination:
host: api.external.com
timeout: 30s # 외부 API는 느릴 수 있음
retries:
attempts: 5 # 외부 API는 일시적 실패 많음
perTryTimeout: 5s
retryOn: 5xx,reset,connect-failure,gateway-error
---
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-api
spec:
hosts:
- api.external.com
ports:
- number: 443
name: https
protocol: HTTPS
location: MESH_EXTERNAL
resolution: DNS예제 3: Circuit Breaker와 함께 사용
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: resilient-service
spec:
hosts:
- payment
http:
- route:
- destination:
host: payment
timeout: 10s
retries:
attempts: 3
perTryTimeout: 3s
retryOn: 5xx,reset,connect-failure
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: payment-circuit-breaker
spec:
host: payment
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50중요 주의사항
⚠️ 비멱등성 요청(Non-Idempotent Requests)에 대한 Retry 위험
핵심 원칙: POST, PUT, DELETE 등 상태를 변경하는 요청은 Istio Proxy에서 자동 retry를 사용하면 데이터 정합성 문제가 발생할 수 있습니다.
문제 상황
왜 위험한가?
- 중복 생성: POST 요청이 실제로는 성공했지만 네트워크 문제로 응답이 손실되면, Proxy가 재시도하여 중복 레코드가 생성됩니다.
- 잘못된 상태 변경: 결제, 재고 차감 등 비즈니스 크리티컬한 작업이 중복 실행될 수 있습니다.
- 검증 불가능: Istio Proxy는 요청이 성공했는지 확인할 방법이 없습니다.
안전한 Retry 전략
✅ 권장: 애플리케이션 레벨 Retry
yaml
# Istio: 네트워크 문제만 재시도
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- match:
- method:
exact: POST
route:
- destination:
host: order-service
timeout: 10s
retries:
attempts: 1 # Retry 비활성화
perTryTimeout: 5s
retryOn: connect-failure,refused-stream # 연결 실패만python
# 애플리케이션: Idempotency Key 사용
import uuid
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_order_with_idempotency(order_data):
# 고유한 Idempotency Key 생성
idempotency_key = str(uuid.uuid4())
session = requests.Session()
retry_strategy = Retry(
total=3,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["POST"], # POST도 재시도
backoff_factor=1
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
headers = {
"X-Idempotency-Key": idempotency_key # 중복 방지
}
response = session.post(
"http://order-service/orders",
json=order_data,
headers=headers
)
return response
# 서버측: Idempotency Key 검증
@app.route('/orders', methods=['POST'])
def create_order():
idempotency_key = request.headers.get('X-Idempotency-Key')
# Redis/DB에서 이미 처리된 요청인지 확인
if redis.exists(f"order:idempotency:{idempotency_key}"):
# 이미 처리된 요청 - 저장된 결과 반환
cached_result = redis.get(f"order:result:{idempotency_key}")
return jsonify(json.loads(cached_result)), 200
# 새 주문 생성
order = create_order_in_db(request.json)
# Idempotency Key와 결과 저장 (24시간 TTL)
redis.setex(f"order:idempotency:{idempotency_key}", 86400, "1")
redis.setex(f"order:result:{idempotency_key}", 86400, json.dumps(order))
return jsonify(order), 201HTTP 메소드별 Retry 안전성
| 메소드 | 멱등성 | Istio Retry 안전성 | 권장 설정 |
|---|---|---|---|
| GET | ✅ 멱등 | ✅ 안전 | attempts: 3, retryOn: 5xx,reset |
| HEAD | ✅ 멱등 | ✅ 안전 | attempts: 3, retryOn: 5xx,reset |
| OPTIONS | ✅ 멱등 | ✅ 안전 | attempts: 3, retryOn: 5xx,reset |
| PUT | ⚠️ 조건부 멱등 | ⚠️ 주의 | Idempotency Key 필요 |
| DELETE | ⚠️ 조건부 멱등 | ⚠️ 주의 | Idempotency Key 필요 |
| POST | ❌ 비멱등 | ❌ 위험 | attempts: 1, 애플리케이션 retry |
| PATCH | ❌ 비멱등 | ❌ 위험 | attempts: 1, 애플리케이션 retry |
안전하게 Retry 가능한 경우
yaml
# 읽기 전용 요청 - 안전
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service-reads
spec:
hosts:
- api-service
http:
- match:
- method:
regex: "GET|HEAD|OPTIONS"
route:
- destination:
host: api-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,reset,connect-failureyaml
# 멱등성이 보장된 쓰기 요청
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: idempotent-writes
spec:
hosts:
- api-service
http:
- match:
- method:
exact: PUT
- headers:
x-idempotency-key:
regex: ".+" # Idempotency Key 있을 때만
route:
- destination:
host: api-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,resetCircuit Breaker와 함께 사용 시 주의사항
Circuit Breaker는 장애 격리에는 효과적이지만, 비멱등성 요청의 중복 실행은 막지 못합니다.
yaml
# ❌ 잘못된 예: POST + Circuit Breaker + Retry
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: payment-service
spec:
hosts:
- payment-service
http:
- route:
- destination:
host: payment-service
retries:
attempts: 3 # ❌ POST에 대해 3번 재시도
retryOn: 5xx
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: payment-circuit-breaker
spec:
host: payment-service
trafficPolicy:
outlierDetection:
consecutiveErrors: 5
baseEjectionTime: 30s
# 결과: Circuit Breaker가 열리기 전에
# 중복 결제가 3번 발생할 수 있음!yaml
# ✅ 올바른 예: Circuit Breaker만 사용, Retry는 애플리케이션에서
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: payment-service
spec:
hosts:
- payment-service
http:
- route:
- destination:
host: payment-service
timeout: 10s
retries:
attempts: 0 # Retry 완전 비활성화
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: payment-circuit-breaker
spec:
host: payment-service
trafficPolicy:
outlierDetection:
consecutiveErrors: 5
baseEjectionTime: 30s실전 가이드라인
- GET/HEAD/OPTIONS: Istio Proxy Retry 사용 가능 ✅
- POST/PATCH: Istio Retry 비활성화, 애플리케이션 레벨 Retry + Idempotency Key ✅
- PUT/DELETE: Idempotency 보장 시에만 Istio Retry 사용 ⚠️
- 결제/재고/포인트 등 크리티컬: 반드시 애플리케이션 레벨 검증 + Idempotency Key 🔴
모범 사례
1. Timeout 설정 가이드
yaml
# ✅ 좋은 예: 계층별 적절한 timeout
# Frontend: 15s
# API Gateway: 10s
# Backend Service: 5s
# Database: 3s
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-gateway
spec:
hosts:
- api-gateway
http:
- route:
- destination:
host: api-gateway
timeout: 10s
retries:
attempts: 2
perTryTimeout: 4syaml
# ❌ 나쁜 예: 너무 긴 timeout
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-gateway
spec:
hosts:
- api-gateway
http:
- route:
- destination:
host: api-gateway
timeout: 300s # 5분은 너무 김2. Retry 전략
yaml
# ✅ 좋은 예: 멱등성 고려
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service
spec:
hosts:
- api-service
http:
# GET - 안전하게 재시도
- match:
- method:
exact: GET
route:
- destination:
host: api-service
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx,reset,connect-failure
# POST - 신중하게 재시도
- match:
- method:
exact: POST
route:
- destination:
host: api-service
retries:
attempts: 1
perTryTimeout: 5s
retryOn: connect-failure # 네트워크 문제만3. 지수 백오프 (Exponential Backoff)
Istio는 기본적으로 25ms 간격으로 재시도하지만, 커스텀 백오프가 필요한 경우:
yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: backoff-retry
spec:
hosts:
- payment
http:
- route:
- destination:
host: payment
retries:
attempts: 5
perTryTimeout: 2s
retryOn: 5xx,reset
# Istio는 자동으로 재시도 간격 증가
# 25ms, 50ms, 100ms, 200ms, 400ms4. 전체 시스템 Timeout 계산
yaml
# Frontend → API Gateway → Backend → Database
# Frontend: 20s
# API Gateway: 15s (Frontend보다 작아야 함)
# Backend: 10s (API Gateway보다 작아야 함)
# Database: 5s (Backend보다 작아야 함)
# 각 레이어는 하위 레이어 timeout + overhead를 고려문제 해결
Timeout이 작동하지 않음
bash
# 1. VirtualService 확인
kubectl get virtualservice -n <namespace>
kubectl describe virtualservice <name> -n <namespace>
# 2. Envoy 구성 확인
istioctl proxy-config routes <pod-name> -n <namespace> -o json | grep timeout
# 3. 실제 timeout 테스트
kubectl exec -it <pod-name> -n <namespace> -c istio-proxy -- \
curl -v --max-time 5 http://backend-serviceRetry가 너무 많이 발생
bash
# Retry 메트릭 확인
kubectl exec -n <namespace> <pod-name> -c istio-proxy -- \
curl -s localhost:15000/stats/prometheus | grep retry
# 특정 서비스로의 retry 확인
istio_requests_total{destination_service="backend.default.svc.cluster.local",response_flags="UR"}Retry Storm 방지
yaml
# Circuit Breaker와 함께 사용
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: prevent-retry-storm
spec:
host: backend
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 10 # 대기 요청 제한
http2MaxRequests: 100
maxRequestsPerConnection: 1
outlierDetection:
consecutiveErrors: 3 # 빠른 차단
interval: 10s
baseEjectionTime: 30s