← 전체 글로 돌아가기

API

API rate limit에 자꾸 걸릴 때

Rate limit 에러는 응답 헤더에 힌트가 있다. Retry-After를 읽고, 요청 빈도를 조절하거나 캐시 전략을 바꾸자.

API 호출이 자꾸 429 Too Many Requests를 내보낸다. 또는 다른 API의 rate limit에 계속 걸린다. 문제를 좁혀보자.

첫 번째: 응답 헤더를 본다

# 응답 헤더 확인
curl -i https://api.example.com/data

# 관련 헤더
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 5
X-RateLimit-Reset: 1672502400
Retry-After: 60

Rate limit 관련 헤더를 보면, 초당 몇 개의 요청을 허용하는지, 남은 요청이 몇 개인지, 언제 리셋되는지 알 수 있다.

두 번째: 자신의 요청 빈도를 측정한다

# 최근 요청 로그를 본다
grep "GET /api" access.log | tail -20

# 또는 애플리케이션 로그
grep "API call" app.log | tail -50

실제로 몇 초에 몇 개의 요청을 보내고 있는지 확인한다. 특히 병렬 요청이나 배치 작업에서는 의도치 않게 limit을 초과할 수 있다.

세 번째: 캐시를 활용한다

// Node.js 예제: 메모리 캐시
const cache = new Map();

async function fetchData(url) {
  const cached = cache.get(url);
  if (cached && Date.now() - cached.timestamp < 60000) {
    return cached.data; // 1분 내 캐시는 재사용
  }

  const response = await fetch(url);
  const data = await response.json();
  cache.set(url, { data, timestamp: Date.now() });
  return data;
}

Rate limit은 보통 같은 요청을 반복 호출할 때 걸린다. 결과를 캐시해서 중복 요청을 줄인다.

네 번째: 요청을 큐에 넣고 지연 실행한다

// 간단한 큐 구현
class RequestQueue {
  constructor(maxPerSecond = 5) {
    this.queue = [];
    this.maxPerSecond = maxPerSecond;
  }

  async add(fn) {
    return new Promise((resolve) => {
      this.queue.push({ fn, resolve });
      this.process();
    });
  }

  async process() {
    while (this.queue.length > 0) {
      const batch = this.queue.splice(0, this.maxPerSecond);
      const results = await Promise.all(batch.map(item => item.fn()));
      results.forEach((result, i) => batch[i].resolve(result));
      await new Promise(r => setTimeout(r, 1000));
    }
  }
}

const queue = new RequestQueue(5); // 초당 5개
await queue.add(() => fetch(url1));
await queue.add(() => fetch(url2));

요청을 큐에 넣고, 시간 단위로 조절해서 limit을 초과하지 않게 한다.

다섯 번째: Retry-After를 존중한다

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      const delay = parseInt(retryAfter) * 1000 || 5000;
      console.log(`Rate limited. Retrying after ${delay}ms`);
      await new Promise(r => setTimeout(r, delay));
      continue;
    }

    return response;
  }
  throw new Error('Max retries exceeded');
}

429 응답을 받으면, 서버가 제시한 Retry-After 시간만큼 기다렸다가 다시 시도한다.

체크리스트

  1. 응답 헤더에서 X-RateLimit-* 값을 본다
  2. 실제 요청 빈도를 측정한다
  3. 캐시를 통해 중복 요청을 줄인다
  4. 요청을 큐에 넣고 시간당 개수를 제한한다
  5. 429 응답을 받으면 Retry-After를 존중한다
  6. 외부 API를 사용한다면, 상위 계획(higher tier)으로 업그레이드 고려

Rate limit은 서버 리소스 보호의 정당한 메커니즘이다. 최대한 친절하게 대응하자.