Toss Open API Rate Limiter 적용 PRD
배경 (Background)
백엔드 서버는 5개의 Toss Gateway(Account, Alert, Stock, Holding, Order)를 통해 Toss 증권 Open API를 호출한다. 현재 구현에는 429(Too Many Requests) 방어 로직이 없어, 폴링 주기 단축·동시 요청 증가 시 Toss API 제한에 그대로 노출된다. Toss Open API는 API 그룹별 TPS(초당 요청 수)를 명시하며, 초과 시 HTTP 429를 반환한다.
목표 (Goals)
| 번호 | 목표 | 측정 기준 |
|---|---|---|
| G1 | Toss API 그룹별 TPS 한도 내로 자동 조절 | 단위 테스트 시 RateLimiter 임계치 초과 요청이 RequestNotPermitted 를 throw |
| G2 | Toss API 연속 실패 시 Circuit 오픈으로 서버 부하 차단 | 실패율 50% 초과 시 CircuitBreaker OPEN 상태 전이 확인 |
| G3 | 임계치 직전까지의 정상 흐름 검증 | 통합 테스트 통과 |
사용자 시나리오
시나리오 1: 폴링 중 Rate Limit 초과
서비스 사용자가 관심 종목 알림을 다수 등록한 상태에서 폴링 주기가 단축되면, MARKET_DATA 그룹 TPS(10 req/s)가 초과된다. 기존에는 429 → 스택 트레이스 로그 → 정제되지 않은 5xx가 반환됐으나, 적용 후에는 TossRateLimitException으로 정제된 에러가 반환되고 운영자 로그에 [TOSS-429] 이벤트가 기록된다.
시나리오 2: Toss API 연속 장애 시 빠른 차단
Toss Open API가 연속으로 오류를 반환하면, CircuitBreaker가 실패율 50% 초과 시점에 OPEN으로 전이해 이후 30초간 Toss API 호출 자체를 차단한다. 운영자는 [CIRCUIT-OPEN] 로그로 즉시 인지하고, 30초 후 HALF_OPEN → 3건 PROBE → CLOSED 순서로 자동 복구된다.
요구사항
기능 요구사항
| ID | 요구사항 | 우선순위 |
|---|---|---|
| R-01 | API 그룹별 RateLimiter를 설정하고 각 Gateway 호출 시 적용한다 | P0 |
| R-02 | 한도 초과 시 TossRateLimitException 을 throw한다 (즉시 실패, 큐잉 없음) | P0 |
| R-03 | Toss API 실패율 50% 초과 시 CircuitBreaker를 OPEN으로 전이한다 | P0 |
| R-04 | OPEN 상태에서 30초 대기 후 HALF_OPEN → 3건 PROBE → 성공 시 CLOSED 전이 | P1 |
| R-05 | 429 응답 자체도 CircuitBreaker failure로 기록한다 | P1 |
API 그룹별 Rate Limit 설정 (Toss 공식 문서 기준)
| 그룹명 | 대상 Gateway / 메서드 | TPS 한도 |
|---|---|---|
MARKET_DATA | 가격 조회 (/api/v1/prices) | 10 req/s |
STOCK | 종목 검색 (/api/v1/stocks) | 5 req/s |
ACCOUNT | 계좌 조회 (/api/v1/accounts) | 1 req/s |
ASSET | 보유종목 (/api/v1/holdings) | 5 req/s |
ORDER | 주문 접수 (/api/v1/orders) | 6 req/s |
AUTH | 토큰 발급 (/oauth2/token) | 5 req/s |
비기능 요구사항
| ID | 요구사항 | 우선순위 |
|---|---|---|
| R-06 | RateLimiter 설정값을 application.yml 에서 관리 (코드 수정 없이 조정 가능) | P1 |
| R-07 | 각 Gateway 구현에 AOP 없이 명시적 래핑 방식으로 적용 | P1 |
| R-08 | 기존 401 재시도 로직과 호환 (RateLimiter → 401 재시도 순서 유지) | P1 |
사용자·운영자 영향
| 역할 | 변경 전 | 변경 후 |
|---|---|---|
| 서비스 사용자 | 429 → 스택 트레이스 로그 노출 | TossRateLimitException 으로 정제된 에러 반환 |
| 운영자 | 429 발생 추적 불가 | 로그에 429-RATELIMIT, CIRCUIT-OPEN 구조화 이벤트 기록 |
운영: 모니터링·알림 요구사항
- 429 수신 시
WARN레벨로[TOSS-429] group={group} url={url}로그 출력 - CircuitBreaker OPEN 전이 시
WARN레벨로[CIRCUIT-OPEN] group=toss로그 출력 - Actuator
/actuator/circuitbreakerevents엔드포인트 활성화로 상태 모니터링 가능
범위 외
- Toss API 미확정 엔드포인트(거래내역·주문 정정·취소) 구현
- 사용자 요청 레벨 Global Rate Limit (API Gateway 레이어 관심사)
- Resilience4j Retry 도입 — 멱등 보장 불가 주문 API 포함이므로 범위 제외
완료 기준 (Acceptance Criteria)
./gradlew test전체 통과 (기존 테스트 회귀 없음)- 통합 테스트에서 MARKET_DATA 그룹 10 req 정상 → 11번째
TossRateLimitExceptionthrow 확인 - CircuitBreaker 실패율 50% 초과 시 OPEN 전이 확인
application.yml에서 rate limit 설정값 변경 후 재시작 없이 반영 확인 (Spring Actuator refresh)