Rate Limit이란?
Rate Limit(속도 제한)은 일정 시간 동안 API에 보낼 수 있는 요청 수를 제한하는 메커니즘입니다. API 서버를 보호하고, 모든 사용자에게 공정한 서비스를 제공하기 위해 거의 모든 API 서비스에서 적용합니다.
- 서버 보호: 과도한 요청으로 인한 서버 과부하 방지
- 공정한 사용: 한 사용자가 리소스를 독점하는 것 방지
- 비용 관리: 요금제별 사용량 제어
- 보안: 무차별 대입 공격(Brute Force) 방어
Rate Limit의 종류
1. 시간 단위별 제한
가장 일반적인 방식으로, 다양한 시간 단위로 요청 수를 제한합니다.
| 시간 단위 | 일반적인 제한 | 용도 |
|---|---|---|
| 분당 (RPM) | 60~500회 | 버스트 트래픽 제어 |
| 시간당 (RPH) | 1,000~10,000회 | 지속적인 사용량 제어 |
| 일일 (RPD) | 5,000~50,000회 | 일일 할당량 관리 |
| 월간 (RPM) | 10,000~500,000회 | 요금제 기반 제한 |
Bank API의 요금제별 제한은 다음과 같습니다:
| 플랜 | 분당 | 시간당 | 일일 | 월간 |
|---|---|---|---|---|
| Free | 60 | 1,000 | 5,000 | 10,000 |
| Basic | 100 | 3,000 | 15,000 | 100,000 |
| Premium | 500 | 10,000 | 50,000 | 500,000 |
2. 동시 연결 제한
동시에 열 수 있는 연결 수를 제한합니다. WebSocket이나 장시간 연결에서 주로 사용됩니다.
3. 대역폭 제한
전송할 수 있는 데이터 양을 제한합니다. 파일 다운로드나 대용량 응답에서 사용됩니다.
Rate Limit 응답 이해하기
429 Too Many Requests
Rate Limit에 도달하면 서버는 HTTP 429 상태 코드를 반환합니다.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1736323200
Retry-After: 45
{
"error": "rate_limit_exceeded",
"message": "분당 요청 한도를 초과했습니다.",
"limit": 60,
"reset_at": "2026-01-08T10:00:00Z"
}
Rate Limit 관련 헤더
| 헤더 | 설명 |
|---|---|
X-RateLimit-Limit |
현재 시간 창에서의 최대 요청 수 |
X-RateLimit-Remaining |
남은 요청 수 |
X-RateLimit-Reset |
제한이 리셋되는 Unix 타임스탬프 |
Retry-After |
다음 요청까지 기다려야 할 초 |
Rate Limit 대응 전략
1. 지수 백오프 (Exponential Backoff)
재시도 간격을 지수적으로 늘려가는 방식입니다. 첫 번째 재시도는 1초 후, 두 번째는 2초 후, 세 번째는 4초 후... 이런 식으로 대기 시간을 늘립니다.
async function fetchWithRetry(url, options, maxRetries = 5) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Retry-After 헤더 확인
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter
? parseInt(retryAfter) * 1000
: Math.pow(2, attempt) * 1000; // 지수 백오프
console.log(`Rate limited. ${waitTime}ms 후 재시도... (${attempt + 1}/${maxRetries})`);
await sleep(waitTime);
continue;
}
return response;
} catch (error) {
lastError = error;
const waitTime = Math.pow(2, attempt) * 1000;
await sleep(waitTime);
}
}
throw lastError || new Error('Max retries exceeded');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
2. 지터 (Jitter) 추가
여러 클라이언트가 동시에 재시도하면 "thundering herd" 문제가 발생할 수 있습니다. 랜덤한 지연(지터)을 추가하면 요청이 분산됩니다.
function getBackoffWithJitter(attempt, baseMs = 1000) {
const exponentialWait = Math.pow(2, attempt) * baseMs;
const jitter = Math.random() * baseMs; // 0~1000ms 랜덤 추가
return exponentialWait + jitter;
}
// 사용 예
// attempt 0: 1000~2000ms
// attempt 1: 2000~3000ms
// attempt 2: 4000~5000ms
// attempt 3: 8000~9000ms
3. 요청 큐잉 (Request Queue)
요청을 큐에 넣고 Rate Limit에 맞춰 순차적으로 처리합니다.
class RateLimitedQueue {
constructor(requestsPerMinute) {
this.queue = [];
this.processing = false;
this.intervalMs = (60 * 1000) / requestsPerMinute;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
// 다음 요청까지 대기
await new Promise(r => setTimeout(r, this.intervalMs));
}
this.processing = false;
}
}
// 사용 예: 분당 60회 제한
const queue = new RateLimitedQueue(60);
// 100개 요청을 큐에 추가 (자동으로 1초 간격으로 처리)
const results = await Promise.all(
accountIds.map(id =>
queue.add(() => fetchTransactions(id))
)
);
4. 사전 Rate Limit 체크
응답 헤더를 확인해서 Rate Limit에 가까워지면 미리 속도를 줄입니다.
class SmartApiClient {
constructor() {
this.remaining = Infinity;
this.resetTime = 0;
}
async request(url, options) {
// 남은 요청이 적으면 대기
if (this.remaining < 5) {
const waitTime = this.resetTime - Date.now();
if (waitTime > 0) {
console.log(`Rate limit 임박. ${waitTime}ms 대기...`);
await new Promise(r => setTimeout(r, waitTime));
}
}
const response = await fetch(url, options);
// 헤더에서 Rate Limit 정보 업데이트
this.remaining = parseInt(
response.headers.get('X-RateLimit-Remaining') || '60'
);
this.resetTime = parseInt(
response.headers.get('X-RateLimit-Reset') || '0'
) * 1000;
return response;
}
}
Rate Limit 최적화 Best Practices
배치 요청 활용
여러 계좌를 조회할 때 개별 요청 대신 배치 API를 사용하면 요청 수를 줄일 수 있습니다.
캐싱 적용
자주 요청하는 데이터는 캐시하세요. 잔액 조회는 1분, 거래내역은 5분 정도 캐싱이 적절합니다.
요청 분산
모든 요청을 한 번에 보내지 말고, 시간대별로 분산하세요. 특히 배치 작업은 새벽 시간에 실행을 권장합니다.
사용량 모니터링
Rate Limit 헤더를 로깅하고 대시보드로 모니터링하면 패턴을 파악하고 최적화할 수 있습니다.
피해야 할 패턴
- 무한 재시도: 최대 재시도 횟수를 설정하세요
- 즉시 재시도: 항상 백오프를 적용하세요
- Rate Limit 무시: 429 응답을 에러로만 처리하지 마세요
- 헤더 미확인: 응답 헤더에서 Rate Limit 정보를 활용하세요
완성된 Rate Limit 처리 클래스
지금까지 배운 모든 기법을 통합한 완성된 API 클라이언트입니다.
class BankApiClient {
constructor(apiKey, secretKey, options = {}) {
this.apiKey = apiKey;
this.secretKey = secretKey;
this.baseUrl = options.baseUrl || 'https://api.bankapi.co.kr';
this.maxRetries = options.maxRetries || 5;
// Rate Limit 상태
this.remaining = Infinity;
this.resetTime = 0;
// 요청 큐
this.queue = [];
this.processing = false;
this.minInterval = options.minInterval || 100; // 최소 100ms 간격
}
async request(endpoint, options = {}) {
return new Promise((resolve, reject) => {
this.queue.push({
endpoint,
options,
resolve,
reject,
retries: 0
});
this.processQueue();
});
}
async processQueue() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
// Rate Limit 임박 시 대기
if (this.remaining < 3 && this.resetTime > Date.now()) {
const waitTime = this.resetTime - Date.now() + 100;
await this.sleep(waitTime);
}
const item = this.queue[0];
try {
const result = await this.executeRequest(item);
this.queue.shift();
item.resolve(result);
} catch (error) {
if (error.status === 429 && item.retries < this.maxRetries) {
// Rate Limit: 지수 백오프로 재시도
item.retries++;
const backoff = this.getBackoffWithJitter(item.retries);
console.log(`Rate limited. ${backoff}ms 후 재시도 (${item.retries}/${this.maxRetries})`);
await this.sleep(backoff);
// 큐의 맨 앞에 유지 (다음 루프에서 재시도)
} else {
this.queue.shift();
item.reject(error);
}
}
// 최소 간격 유지
await this.sleep(this.minInterval);
}
this.processing = false;
}
async executeRequest({ endpoint, options }) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
'Authorization': `Bearer ${this.apiKey}:${this.secretKey}`,
'Content-Type': 'application/json',
...options.headers
};
const response = await fetch(url, {
...options,
headers
});
// Rate Limit 헤더 업데이트
this.remaining = parseInt(
response.headers.get('X-RateLimit-Remaining') || '60'
);
const resetHeader = response.headers.get('X-RateLimit-Reset');
if (resetHeader) {
this.resetTime = parseInt(resetHeader) * 1000;
}
if (!response.ok) {
const error = new Error(`API Error: ${response.status}`);
error.status = response.status;
error.retryAfter = response.headers.get('Retry-After');
throw error;
}
return response.json();
}
getBackoffWithJitter(attempt) {
const base = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * 1000;
return base + jitter;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 편의 메서드
async getTransactions(bankCode, accountNumber, startDate, endDate) {
return this.request('/v1/transactions', {
method: 'POST',
body: JSON.stringify({
bankCode,
accountNumber,
startDate,
endDate
})
});
}
}
// 사용 예
const client = new BankApiClient('pk_live_xxx', 'sk_live_xxx');
// 여러 계좌 조회 (자동으로 Rate Limit 처리)
const accounts = [
{ bank: 'NH', account: '123-456-789' },
{ bank: 'NH', account: '987-654-321' },
// ... 더 많은 계좌
];
const results = await Promise.all(
accounts.map(acc =>
client.getTransactions(acc.bank, acc.account, '2026-01-01', '2026-01-08')
)
);
마무리
Rate Limit은 API를 안정적으로 운영하기 위한 필수 요소입니다. 클라이언트 입장에서는 불편해 보일 수 있지만, 적절한 대응 전략을 구현하면 오히려 더 안정적인 애플리케이션을 만들 수 있습니다.
핵심 포인트 요약
- 429 응답은 에러가 아닌 신호입니다. 적절히 대응하세요.
- 지수 백오프 + 지터는 필수입니다.
- 응답 헤더를 활용해 Rate Limit 상태를 추적하세요.
- 요청 큐잉으로 자동화된 Rate Limit 관리를 구현하세요.
- 캐싱으로 불필요한 요청을 줄이세요.
Bank API는 모든 응답에 Rate Limit 관련 헤더를 포함하고 있어, 위 전략들을 바로 적용할 수 있습니다. 더 궁금한 점이 있다면 FAQ를 확인하거나 문의해 주세요.
Bank API로 시작해보세요
무료 플랜으로 시작해서 Rate Limit 처리를 직접 테스트해보세요.
무료로 시작하기 API 문서 보기