PG 시뮬레이터를 로컬 PC(M1 Air)에서 실행하며, 실제 운영 환경에서 적용할 Failure Ready System을 어떻게 설계할지 수치 기반으로 정리했습니다.
| 구분 | 값 |
|---|---|
| 요청 성공 확률 | 60% |
| 요청 지연 | 100ms ~ 500ms |
| 처리 지연 | 1s ~ 5s |
| 처리 결과 성공률 | 성공 70% / 한도초과 20% / 카드오류 10% |
Feign timeout 외에 TimeLimiter는 전체 실행 시간을 제한한다.
특징:
PG 처리 지연 max = 5s
→ timeout-duration = 5.5s (~6s) 설정
PG 요청 성공률 = 60%
→ 즉, Retries가 너무 많으면 PG 자체 부하만 키움
| max-attempts | 성공률 | 부하 증가율 |
|---|---|---|
| 1 | 60% | 0.6 |
| 2 | 84% | 0.6 + 0.6 * 0.4 |
| 3 | 93.6% | 0.6 + 0.24 + 0.096 |
→ 2회 → +24%
→ 3회 → +9.6% (효용 급감)
재시도는 성공률 증가보다 부하 폭증이 더 크다.
| Retry 횟수 | 최대 시도 | 기대 시도 | 부하 증가율 |
|---|---|---|---|
| 0 | 1 | 1.0 | 1.0x |
| 1 | 2 | 1.4 | 1.4x |
| 2 | 3 | 1.56 | 1.56x |
| 3 | 4 | 1.624 | 1.62x |
| 5 | 6 | 1.781 | 1.78x |
➡ 재시도 1회만 해도 부하가 40% 증가
→ 실제로는 retry 를 하지 않는 것이 가장 현실적인 선택
PG 자체의 “정상적인 실패율”을 먼저 계산한다.
요청 성공률 60% × 처리 성공률 70% = 42%
즉, 실패율:
100% - 42% = 58%
따라서 CircuitBreaker의 failure-rate-threshold 는
→ 최소 65% 이상이 되어야 정상 실패를 차단하지 않음.
brew install wrk
wrk -t2 -c200 -d30s -s post.lua http://localhost:8082/api/v1/payments
wrk.method = "POST"
wrk.body = [[
{
"orderId": "1351039135",
"cardType": "SAMSUNG",
"cardNo": "1234-5678-9814-1451",
"amount": "5000",
"callbackUrl": "http://localhost:8080/api/v1/payments/callback"
}
]]
wrk.headers["Content-Type"] = "application/json"
wrk.headers["X-USER-ID"] = "135135"

2초 정도의 지연은 사용자가 느끼기에 긴 시간으로 느껴지지 않아, Slow-call 기준을 다음처럼 설정:
slow-call-duration-threshold: 2000ms
PG 자체가 감당 가능한 처리량 = 약 300 TPS
→ 이 이상의 호출이 오면 PG가 먼저 병목됨
Retry 여부를 검증하기 위해 동일 조건에서 테스트함.
wrk -t2 -c200 -d30s -s post.lua http://localhost:8080/api/v1/payments
| 지표 | Retry O | Retry X | 변화 |
|---|---|---|---|
| 요청 수 | 2854 | 2754 | +100 |
| 성공 TPS | 94.96 | 91.65 | +3.3% |
| Latency | 1.26s | 1.24s | 거의 동일 |
| Non-2xx | 109 | 189 | -42% ↓ |
| Timeout | 1236 | 1223 | 비슷 |
➡ 따라서 retry 1회(max-attempts:2) 가 최적
feign:
client:
config:
pgClient:
connect-timeout: 600 #PG 요청 지연 max = 500ms
read-timeout: 6000 #PG 처리 지연 max = 5s
resilience4j:
timelimiter:
instances:
pgTimeLimiter:
timeout-duration: 5s #PG 처리 지연 max = 5s, read-timeout 보다 작은값
cancel-running-future: true
retry:
instances:
pgRetry:
max-attempts: 2
wait-duration: 1s
retry-exceptions:
- feign.RetryableException
fail-after-max-attempts: true
circuitbreaker:
instances:
pgCircuit:
failure-rate-threshold: 65 # 요청 성공률 60% × 처리 성공률 70% = 42%, 실패율 58%
slow-call-rate-threshold: 50
slow-call-duration-threshold: 2s # Max latency = 2000ms
permitted-number-of-calls-in-half-open-state: 30 # TPS 300 의 10~20%
minimum-number-of-calls: 30 # 타임아웃 2s, TPS 300
sliding-window-type: TIME_BASED # 트래픽 차이가 클경우, TIME_BASED
sliding-window-size: 60 # 장애탐지가 중요하므로 작게
wait-duration-in-open-state: 5s #회복하는데 걸리는 시간 기본 5s 적용
automatic-transition-from-open-to-half-open-enabled: true
record-exceptions:
- feign.FeignException
- org.springframework.web.client.HttpServerErrorException
- java.io.IOException
- java.util.concurrent.TimeoutException
이번 글은 PG Simulator의 수치를 기반으로
Timeout, Retry, CircuitBreaker 값을 합리적으로 결정한 과정을 정리했습니다.
실제 운영 환경의 성능, 네트워크 품질에 따라 수치는 달라질 수 있어, 차후 실제 환경 부하 테스트를 통해 더 세밀한 수정이 필요합니다. 특히 위 방식이 로컬을 기준으로 측정된 값 + 제공된 PG 사양이 혼합되어 있지만, 수치를 기반으로 근거있는 결정을 해볼수 있었습니다.