Skip to content

Commit d344a84

Browse files
authored
Merge pull request #40 from unohee/fix/rate-limiter-safety-margin
fix(rate_limiter): enforce official KIS limit, lock-free sleep, safer defaults
2 parents f4ae370 + efa73b1 commit d344a84

5 files changed

Lines changed: 504 additions & 231 deletions

File tree

docs/RATE_LIMITER_GUIDE.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
# Rate Limiter 설정 가이드
22

3-
## 📊 기본 설정 (2025.09.21 기준)
3+
## 📊 기본 설정 (v1.7.x 기준)
44

5-
PyKIS의 Rate Limiter는 실제 테스트를 바탕으로 안정성을 우선시하여 설정되었습니다.
5+
kis-agent의 Rate Limiter는 KIS 공식 한도(20 RPS / 1000 RPM) 대비 충분한 안전
6+
마진을 두고, 동시 호출 시에도 sliding window 위반이 발생하지 않도록 설계되어
7+
있습니다.
68

79
### 기본값
810
```python
9-
# pykis/core/rate_limiter.py 기본 설정
11+
# kis_agent/core/rate_limiter.py의 DEFAULT_* 상수
1012
{
11-
'requests_per_second': 18, # API 스펙: 20 (안정성을 위해 90% 수준)
12-
'requests_per_minute': 900, # API 스펙: 1000 (안정성을 위해 90% 수준)
13-
'min_interval_ms': 50, # API 권장: 50ms
14-
'burst_size': 10, # 순간 버스트 허용량
15-
'enable_adaptive': True # 적응형 백오프 활성화
13+
'requests_per_second': 15, # 공식 한도 20의 75% (5회 여유)
14+
'requests_per_minute': 800, # 공식 한도 1000의 80% (200회 여유)
15+
'min_interval_ms': 70, # 15 RPS의 이론적 67ms + 3ms jitter
16+
'burst_size': 3, # priority>=1 시 effective RPS = 18
17+
'enable_adaptive': True # 적응형 백오프 활성화
1618
}
1719
```
1820

21+
### 안전 보장
22+
23+
- **공식 한도 자동 클램프**: `RateLimiter(requests_per_second=25)`처럼 공식 한도를
24+
초과하는 값을 주면 자동으로 20으로 clamp됩니다 (RPM도 동일).
25+
- **동시 호출 안전**: 다수의 스레드가 동시에 acquire()를 호출해도 sliding window
26+
내 요청 수가 한도를 넘지 않습니다 (1ms safety padding + slot reservation).
27+
- **Lock-free sleep**: `acquire()``time.sleep()`이 lock 바깥에서 실행되어 한
28+
스레드의 대기가 다른 스레드를 블록하지 않습니다.
29+
- **전역 싱글턴**: 모든 KISClient/Agent가 동일한 RateLimiter 인스턴스를 공유합니다.
30+
1931
## 🎯 사용 시나리오별 권장 설정
2032

2133
### 1. 안정성 최우선 (Production)

kis_agent/core/agent.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -177,28 +177,17 @@ def __init__(
177177
)
178178

179179
# Rate Limiter 설정 (전역 싱글턴 패턴)
180-
# 모든 Agent와 KISClient가 동일한 Rate Limiter 인스턴스를 공유하여
181-
# API 호출 제한을 전역적으로 관리합니다.
180+
# 기본값은 kis_agent.core.rate_limiter의 DEFAULT_* 상수를 사용한다
181+
# (공식 20 RPS / 1000 RPM 대비 75% / 80% 안전 마진).
182+
# 사용자가 rate_limiter_config로 일부 키를 override할 수 있다.
182183
if enable_rate_limiter:
183184
if rate_limiter:
184-
# 명시적으로 전달된 rate_limiter 사용 (테스트 등 특수 목적)
185185
self.rate_limiter = rate_limiter
186+
elif rate_limiter_config:
187+
# 사용자 override는 첫 호출에서만 적용됨 (싱글턴이므로)
188+
self.rate_limiter = get_global_rate_limiter(**rate_limiter_config)
186189
else:
187-
# 전역 싱글턴 Rate Limiter 사용 (2025.09.21 실측 기반)
188-
# 공식 스펙: 초당 20회 / 분당 1000회
189-
# 안정 운영: 초당 18회 / 분당 900회 (실측 기반 권장)
190-
default_config = {
191-
"requests_per_second": 18, # 실측 기반 안정 한계
192-
"requests_per_minute": 900, # 실측 기반 안정 한계
193-
"min_interval_ms": 55, # 최소 55ms 간격 (18 RPS 기준)
194-
"burst_size": 10, # 순간 처리량 허용
195-
"enable_adaptive": True,
196-
}
197-
if rate_limiter_config:
198-
default_config.update(rate_limiter_config)
199-
200-
# 전역 싱글턴 Rate Limiter 획득
201-
self.rate_limiter = get_global_rate_limiter(**default_config)
190+
self.rate_limiter = get_global_rate_limiter()
202191
else:
203192
self.rate_limiter = None
204193

kis_agent/core/client.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,13 @@ def __init__(
8989
self.rate_limit_lock = threading.Lock() # 인스턴스별 rate limit lock
9090
self.token_refresh_lock = threading.Lock() # 토큰 재생성 동기화용 락
9191

92-
# Rate Limiter 설정 (2025.09.21 실측 기반)
93-
# 공식 스펙: 초당 20회 / 분당 1000회
94-
# 안정 운영: 초당 18회 / 분당 900회 (실측 기반 권장)
95-
# 전역 싱글턴 사용: 모든 KISClient/Agent가 동일한 Rate Limiter 공유
92+
# Rate Limiter 설정 — 기본값은 kis_agent.core.rate_limiter의 DEFAULT_*
93+
# (공식 스펙 20 RPS / 1000 RPM 대비 75% / 80% 안전 마진).
94+
# 전역 싱글턴 사용: 모든 KISClient/Agent가 동일한 Rate Limiter 공유.
95+
# 명시적으로 전달된 rate_limiter가 있으면 그것을 사용 (테스트 등 특수 목적).
9696
self.enable_rate_limiter = enable_rate_limiter
9797
if enable_rate_limiter:
98-
# 명시적으로 전달된 rate_limiter가 있으면 사용, 없으면 전역 싱글턴 사용
99-
self.rate_limiter = rate_limiter or get_global_rate_limiter(
100-
requests_per_second=18, # 실측 기반 안정 한계
101-
requests_per_minute=900, # 실측 기반 안정 한계
102-
min_interval_ms=55, # 최소 55ms 간격 (18 RPS 기준)
103-
burst_size=10, # 순간 처리량 허용
104-
enable_adaptive=True,
105-
)
98+
self.rate_limiter = rate_limiter or get_global_rate_limiter()
10699
else:
107100
self.rate_limiter = None
108101

0 commit comments

Comments
 (0)