본문으로 건너뛰기

Rate Limiting

Rate Limiting 은 API 남용을 방지하고 서비스 안정성을 보장합니다.

응답 형식

플랜 한도를 초과하면 429 Too Many Requests 응답이 반환됩니다.

json
{
  "error": "Rate limit exceeded",
  "level": "app",
  "retry_after": "60",
  "message": "Your application has exceeded its rate limit. Please wait before making more requests."
}

대부분의 Connect Base 에러 응답은 {"error": "..."} 형태입니다. Rate limit 응답만 level / retry_after / message 추가 필드가 함께 반환되며, 일부 신규 엔드포인트(예: push notification 서비스 단계 에러)는 내부적으로 {"success": false, "error": {"code", "message", ...}} 봉투를 사용할 수 있습니다. ⚠️ SDK ApiError.message 는 단순 error 문자열을 기대하므로 봉투 응답에서는 [object Object] 가 될 수 있습니다 — statusCode 로 분기하거나 raw fetch 로 본문을 직접 읽으세요.

SDK 에서 처리

typescript
import { ApiError } from 'connectbase-client'

try {
    await cb.database.getData('tbl_xxx')
} catch (e) {
    if (e instanceof ApiError && e.statusCode === 429) {
        console.warn('Rate limit hit')
    }
}

Exponential Backoff 패턴

typescript
import { ApiError } from 'connectbase-client'

async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 5): Promise<T> {
    for (let i = 0; i < maxAttempts; i++) {
        try {
            return await fn()
        } catch (e) {
            if (e instanceof ApiError && e.statusCode === 429 && i < maxAttempts - 1) {
                // 1초, 2초, 4초, 8초, 16초 (최대 30초)
                const waitMs = Math.min(Math.pow(2, i) * 1000, 30_000)
                await new Promise((r) => setTimeout(r, waitMs))
                continue
            }
            throw e
        }
    }
    throw new Error('재시도 한도 초과')
}

// 사용
const result = await withRetry(() => cb.database.getData('tbl_xxx'))

Rate Limit 회피 패턴

1. 캐시 레이어 사용 (React Query / SWR)

typescript
import { useQuery } from '@tanstack/react-query'

function useTableData(tableId: string) {
    return useQuery({
        queryKey: ['table', tableId],
        queryFn: () => cb.database.getData(tableId),
        staleTime: 60_000  // 1분 동안 캐시 사용
    })
}

2. 페이지네이션 활용

큰 데이터셋을 한 번에 가져오는 대신 limit / offset 으로 분할:

typescript
// ❌ 비효율적: 매번 1000개 요청
const all = await cb.database.getData('tbl_xxx', { limit: 1000 })

// ✅ 효율적: 사용자가 보는 만큼만 요청
const page = await cb.database.getData('tbl_xxx', { limit: 20, offset: 0 })

3. 배치 / 트랜잭션 활용

여러 row 를 한 번에 처리:

typescript
// ❌ 비효율: 각각 호출 (5번의 요청)
for (const item of items) {
    await cb.database.createData('tbl_xxx', { data: item })
}

// ✅ 효율: 한 번에 (1번의 요청)
await cb.database.createMany('tbl_xxx', items.map((data) => ({ data })))

4. 요청 큐잉 (p-queue)

typescript
import PQueue from 'p-queue'

const queue = new PQueue({
    concurrency: 5,         // 동시 5개
    interval: 1000,         // 1초당
    intervalCap: 10         // 최대 10개
})

for (const item of items) {
    void queue.add(() => processItem(item))
}

자세한 패턴은 흔한 에러 & 해결책 #3 을 참고하세요.