01 SOFTWARE
API

Rate Limiting

01 SOFTWARE API rate limit 정책 및 최적화 가이드

Rate Limiting

01 SOFTWARE API는 공정한 사용과 시스템 안정성을 위해 rate limit을 적용합니다.

Rate Limit 정책

플랜별 제한

플랜월 API 호출분당 요청시간당 요청
Free10,00010200
Starter100,000501,000
Pro1,000,0002005,000

Rate limit은 테넌트별로 적용됩니다.

Rate Limit Headers

API 응답 헤더에 rate limit 정보가 포함됩니다.

응답 헤더

HTTP/1.1 200 OK
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 195
X-RateLimit-Reset: 1736251200
헤더설명
X-RateLimit-Limit시간당 최대 요청 수
X-RateLimit-Remaining남은 요청 수
X-RateLimit-Reset리셋 시간 (Unix timestamp)

Rate Limit 초과

429 응답

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1736251200
Retry-After: 3600

에러 응답

{
  "errors": [
    {
      "message": "Rate limit exceeded",
      "name": "TooManyRequests",
      "extensions": {
        "code": "RATE_LIMIT_EXCEEDED",
        "retryAfter": 3600
      }
    }
  ]
}

Rate Limit 확인

SDK를 통한 확인

import { createBrowserClient } from '@01.software/sdk'

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY
})

const response = await client.from('products').find()

// Rate limit 정보 확인
console.log('Remaining:', response.rateLimit.remaining)
console.log('Reset at:', new Date(response.rateLimit.reset * 1000))

최적화 전략

1. 캐싱 활용

// React Query로 자동 캐싱
const { data } = client.query.useCollection('products', {
  limit: 10
}, {
  staleTime: 5 * 60 * 1000,    // 5분
  cacheTime: 10 * 60 * 1000    // 10분
})

2. 배치 요청

// ❌ 여러 번 요청
for (const id of productIds) {
  await client.from('products').findById(id)  // N회 요청
}

// ✅ 한 번에 요청
const { data } = await client.from('products').find({
  where: {
    id: { in: productIds }
  }
})

3. Pagination 최적화

// ✅ 적절한 limit 사용
const { data } = await client.from('products').find({
  limit: 20,  // 필요한 만큼만
  page: 1
})

// ❌ 과도한 limit
const { data } = await client.from('products').find({
  limit: 1000  // 불필요하게 많은 데이터
})

4. 조건부 요청

// If-None-Match 헤더 사용
const response = await fetch('https://api.01.software/api/products', {
  headers: {
    'If-None-Match': etag,
    'Authorization': `Bearer ${token}`
  }
})

if (response.status === 304) {
  // 캐시된 데이터 사용
}

Retry 전략

Exponential Backoff

SDK는 자동으로 exponential backoff를 적용합니다.

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY,
  retry: {
    maxRetries: 3,
    retryDelay: (attempt) => {
      // 1초 → 2초 → 4초
      return Math.min(1000 * 2 ** attempt, 10000)
    }
  }
})

수동 Retry

async function fetchWithRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      if (error.status === 429 && i < maxRetries - 1) {
        const retryAfter = error.headers['retry-after'] || (2 ** i)
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
        continue
      }
      throw error
    }
  }
}

모니터링

Rate Limit 사용량 조회

// 현재 테넌트의 API 사용량 조회
const { data: usage } = await client.from('api-usage').find({
  where: {
    tenant: { equals: tenantId },
    month: { equals: '2026-01' }
  }
})

console.log('Used:', usage.totalCalls)
console.log('Limit:', usage.monthlyLimit)
console.log('Remaining:', usage.monthlyLimit - usage.totalCalls)

알림 설정

// 80% 사용 시 알림
if (usage.totalCalls > usage.monthlyLimit * 0.8) {
  sendAlert('Rate limit 80% 도달')
}

플랜 업그레이드

Rate limit을 초과하는 경우 플랜 업그레이드를 고려하세요.

// 테넌트 플랜 업그레이드
await client.from('tenants').update(tenantId, {
  plan: 'pro'
})

예외 처리

SDK 에러 핸들링

import { isRateLimitError } from '@01.software/sdk'

try {
  const { data } = await client.from('products').find()
} catch (error) {
  if (isRateLimitError(error)) {
    const retryAfter = error.retryAfter
    console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`)

    // 사용자에게 안내
    showNotification(`잠시 후 다시 시도해주세요 (${retryAfter}초 후)`)
  }
}

모범 사례

1. 캐싱 우선

가능한 한 캐싱을 활용하여 API 호출을 줄이세요.

// React Query 자동 캐싱
const { data } = client.query.useCollection('products',
  { limit: 10 },
  { staleTime: 5 * 60 * 1000 }  // 5분간 캐시 사용
)

2. 필요한 필드만 요청

// GraphQL로 필요한 필드만 요청
const { data } = await client.graphql(`
  query {
    Products {
      docs {
        id
        name
        price
      }
    }
  }
`)

3. Webhook 활용

실시간 업데이트가 필요한 경우 polling 대신 webhook을 사용하세요.

// ❌ Polling (비효율적)
setInterval(async () => {
  await client.from('orders').find()
}, 5000)  // 5초마다 요청

// ✅ Webhook (효율적)
// app/api/webhook/route.ts
export async function POST(request: Request) {
  return handleWebhook(request, async (event) => {
    if (event.collection === 'orders') {
      // 주문 업데이트 처리
    }
  })
}

4. 사용량 모니터링

정기적으로 API 사용량을 확인하고 최적화하세요.

// 주간 리포트
const weeklyUsage = await client.from('api-usage').find({
  where: {
    createdAt: {
      greater_than: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
    }
  }
})

다음 단계

On this page