대량 추적 시 API Rate Limiting 활용 가이드

하루에 수천 개의 택배를 추적해야 할 때, API Rate Limit을 이해하고 효율적으로 활용하는 것이 매우 중요합니다. 이 가이드에서는 Rate Limit 범위 내에서 처리량을 극대화하는 다섯 가지 전략을 소개합니다.

WhereParcel Rate Limit 구조

WhereParcel의 Rate Limit은 요금제에 따라 다르게 적용됩니다.

요금제분당 요청 수일일 요청 수배치 크기
Free105001
Starter6010,00010
Business300100,00050
Enterprise협의협의100

모든 API 응답 헤더에 현재 Rate Limit 상태가 포함됩니다.

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1706200000

X-RateLimit-Remaining 값을 확인하면 남은 요청 횟수를 실시간으로 파악할 수 있습니다.

전략 1: 배치 추적 활용

택배를 하나씩 조회하는 대신 배치 엔드포인트를 사용하면 API 호출 수를 획기적으로 줄일 수 있습니다.

// ❌ 비효율적: 50건을 개별 요청으로 처리
for (const parcel of parcels) {
  await fetch('/v2/track', {
    body: JSON.stringify({
      carrier: parcel.carrier,
      trackingNumber: parcel.trackingNumber,
    }),
  });
}

// ✅ 효율적: 1번의 배치 요청으로 처리
await fetch('/v2/track/batch', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.WHEREPARCEL_API_KEY}:${process.env.WHEREPARCEL_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    parcels: parcels.map(p => ({
      carrier: p.carrier,
      trackingNumber: p.trackingNumber,
    })),
  }),
});

배치 요청은 포함된 택배 수와 관계없이 1건의 요청으로 카운트됩니다 (요금제별 배치 크기 한도 내에서). 50건을 개별 요청하면 50회의 Rate Limit을 소모하지만, 배치로 보내면 단 1회만 소모됩니다.

전략 2: 스마트 캐싱

모든 추적 요청이 매번 API를 호출할 필요는 없습니다. 택배의 현재 상태에 따라 캐시 기간을 다르게 설정하세요.

function getCacheDuration(status) {
  switch (status) {
    case 'delivered':
      return 24 * 60 * 60; // 24시간 - 상태가 바뀌지 않음
    case 'out_for_delivery':
      return 5 * 60;       // 5분 - 곧 변경될 가능성 높음
    case 'in_transit':
      return 30 * 60;      // 30분
    case 'picked_up':
      return 60 * 60;      // 1시간
    default:
      return 15 * 60;      // 15분
  }
}

async function getTracking(carrier, trackingNumber) {
  const cacheKey = `tracking:${carrier}:${trackingNumber}`;
  const cached = await cache.get(cacheKey);

  if (cached) return cached;

  const result = await whereparcel.track(carrier, trackingNumber);
  const ttl = getCacheDuration(result.status);
  await cache.set(cacheKey, result, ttl);

  return result;
}

핵심은 상태별로 캐시 기간을 차등 적용하는 것입니다. 이미 배송 완료된 택배는 24시간 캐싱해도 문제 없지만, 배송 출발 상태인 택배는 5분마다 갱신해야 고객에게 정확한 정보를 제공할 수 있습니다.

전략 3: Exponential Backoff 적용

Rate Limit에 도달하여 HTTP 429 응답을 받으면, Exponential Backoff를 적용하여 점진적으로 재시도합니다.

async function trackWithRetry(carrier, trackingNumber, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('/v2/track', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.WHEREPARCEL_API_KEY}:${process.env.WHEREPARCEL_SECRET_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ trackingItems: [{ carrier, trackingNumber }] }),
      });

      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 60;
        const delay = Math.min(retryAfter * 1000, 2 ** attempt * 1000);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      return await response.json();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise(resolve =>
        setTimeout(resolve, 2 ** attempt * 1000)
      );
    }
  }
}

429 응답의 Retry-After 헤더를 우선 참고하되, 없는 경우 2의 거듭제곱으로 대기 시간을 늘려갑니다. 이렇게 하면 서버에 부담을 주지 않으면서 안정적으로 재시도할 수 있습니다.

전략 4: 폴링 대신 웹훅 사용

API 호출을 줄이는 가장 효과적인 방법은 폴링을 완전히 중단하는 것입니다. 웹훅을 등록하면 상태가 변경될 때 WhereParcel이 직접 알려줍니다.

// ❌ 비효율적: 1,000개 택배를 30분마다 폴링 = 일일 48,000건 요청
setInterval(async () => {
  for (const parcel of activeParcels) {
    await trackParcel(parcel);
  }
}, 30 * 60 * 1000);

// ✅ 효율적: 웹훅 1회 등록 후 업데이트를 자동 수신
await fetch('/v2/webhooks', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.WHEREPARCEL_API_KEY}:${process.env.WHEREPARCEL_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://yourapp.com/webhooks/tracking',
    events: ['status_changed'],
  }),
});
// 결과: 택배 하나당 배송 기간 동안 약 3~5건의 웹훅 호출

활성 택배 1,000건 기준으로 일일 API 사용량이 48,000건에서 약 500건으로 대폭 감소합니다. Rate Limit 걱정 없이 대규모 추적이 가능해집니다.

전략 5: 활성 택배 우선순위 관리

모든 택배에 동일한 폴링 주기를 적용할 필요가 없습니다. 발송 후 경과 일수와 현재 상태에 따라 조회 빈도를 차등 적용하세요.

function getPollingInterval(parcel) {
  const daysSinceShipped = getDaysSince(parcel.shippedDate);

  if (parcel.status === 'delivered') return null;     // 폴링 중지
  if (parcel.status === 'out_for_delivery') return 5;  // 5분
  if (daysSinceShipped <= 1) return 30;                // 30분
  if (daysSinceShipped <= 5) return 60;                // 1시간
  if (daysSinceShipped <= 14) return 180;              // 3시간
  return 720;                                           // 12시간
}

배송 완료된 택배는 더 이상 조회하지 않고, 배송 출발 상태의 택배는 5분마다 조회하는 식으로 자원을 집중 배분합니다. 발송 후 오래된 택배일수록 상태 변경 빈도가 낮으므로, 폴링 간격을 늘려도 무방합니다.

API 사용량 모니터링

예상치 못한 Rate Limit 초과를 방지하려면, API 사용량을 실시간으로 모니터링하세요.

class RateLimitMonitor {
  constructor() {
    this.remaining = Infinity;
    this.resetTime = 0;
  }

  updateFromResponse(headers) {
    this.remaining = parseInt(headers['x-ratelimit-remaining']);
    this.resetTime = parseInt(headers['x-ratelimit-reset']);

    if (this.remaining < 10) {
      console.warn(`Rate limit warning: ${this.remaining} requests remaining`);
    }
  }

  canMakeRequest() {
    if (this.remaining <= 0) {
      const now = Math.floor(Date.now() / 1000);
      return now >= this.resetTime;
    }
    return true;
  }
}

잔여 요청 수가 임계값 이하로 떨어지면 경고를 발생시키고, 새로운 요청 전에 canMakeRequest()로 가능 여부를 확인하는 구조입니다.

전략별 절감 효과 요약

전략API 호출 절감률
배치 추적최대 50배
스마트 캐싱40~70%
웹훅 (폴링 대체)95% 이상
우선순위 기반 폴링50~80%

이 전략들을 조합하면, 수만 건의 택배를 추적하면서도 Rate Limit 범위 내에서 충분히 운영할 수 있습니다. 대규모 물량을 처리해야 하는 경우에는 문의하기를 통해 Enterprise 요금제의 커스텀 한도를 상담 받으실 수 있습니다.