본문으로 건너뛰기

Preact 요청률 제한 가이드

요청률 제한은 시간 창 안에서 설정된 횟수만큼 실행을 허용합니다. 용량이 남아 있는 동안 호출은 즉시 실행됩니다. 제한에 도달하면 용량이 다시 생길 때까지 이후 호출이 거부됩니다.

TanStack Pacer는 주로 클라이언트 측 작업을 위한 인메모리 요청률 제한기를 제공합니다. 서버 측 JavaScript에서도 실행할 수 있지만 분산 할당량이나 강제 적용 시스템은 아닙니다.

요청률 제한의 작동 방식​

이 예제는 시간 창당 세 번의 실행을 허용합니다.

Rate Limiting (limit: 3 calls per window)
Timeline: [1 second per tick]
Window 1 | Window 2
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Executed: ✅ ✅ ✅ ❌ ❌ ✅ ✅
[=== 3 allowed ===][=== blocked until reset ===][=== new window ===]

요청률 제한은 버스트를 허용합니다. 수락된 호출의 간격을 균등하게 조정하지는 않습니다.

요청률 제한을 사용해야 하는 경우​

다음과 같은 경우 요청률 제한을 선택합니다.

  • 클라이언트 작업이 고정 할당량을 따라야 하는 경우
  • 할당량을 소진할 때까지 호출을 즉시 실행할 수 있는 경우
  • 거부된 호출을 버리거나 별도로 처리할 수 있는 경우
  • 남은 용량이나 용량이 돌아올 때까지의 시간을 측정해야 하는 경우

다음과 같은 경우 다른 유틸리티를 선택합니다.

  • 실행 간격을 균등하게 유지해야 하는 경우 스로틀링을 사용합니다.
  • 마지막 호출만 중요한 경우 디바운싱을 사용합니다.
  • 모든 작업을 결국 실행해야 하는 경우 큐잉을 사용합니다.
  • 여러 항목을 함께 실행해야 하는 경우 배칭을 사용합니다.
  • Promise 결과, 재시도, 중단 지원이 필요한 경우 비동기 요청률 제한을 사용합니다.

시간 창 타입​

windowType 옵션은 용량이 돌아오는 시점을 제어합니다.

고정 시간 창​

고정 시간 창은 첫 실행이 수락될 때 시작됩니다. 수락된 모든 실행은 해당 시간 창이 끝날 때까지 횟수에 포함됩니다. 그다음 용량이 한꺼번에 재설정됩니다.

const limiter = useRateLimiter(sendEvent, {
limit: 3,
window: 1000,
windowType: 'fixed',
})

고정 시간 창은 재설정될 때 전체 할당량을 사용할 수 있게 되므로 경계 부근에서 버스트가 발생할 수 있습니다.

슬라이딩 시간 창​

슬라이딩 시간 창은 수락된 각 실행을 별도로 추적합니다. 오래된 타임스탬프가 시간 창에서 빠져나갈 때마다 실행 한 번만큼의 용량이 돌아옵니다.

Sliding Window (limit: 3 calls per window)
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Executed: ✅ ✅ ✅ ❌ ✅
[=== full ===][oldest execution expires][=== one available ===]
const limiter = useRateLimiter(sendEvent, {
limit: 3,
window: 1000,
windowType: 'sliding',
})

용량을 한꺼번에 복원하지 않고 점진적으로 복원해야 한다면 슬라이딩 시간 창을 사용합니다.

API 선택하기​

  • 할당량이 제어되는 이벤트 핸들러가 필요하면 useRateLimitedCallback
  • Preact 상태가 필요하면 useRateLimitedState 또는 useRateLimitedValue
  • 용량 헬퍼와 선택한 상태가 필요하면 useRateLimiter

작업에는 콜백 API를, 할당량이 제어되는 UI 업데이트에는 상태 또는 값 API를 사용합니다. 용량 헬퍼나 거부 상태가 필요하면 인스턴스 API를 사용합니다.

Preact 예제​

import { useRateLimiter } from '@tanstack/preact-pacer'

function SendButton() {
const limiter = useRateLimiter(
sendEvent,
{ limit: 3, window: 10_000 },
(state) => ({
rejectionCount: state.rejectionCount,
}),
)

return (
<button onClick={() => limiter.maybeExecute('clicked')}>
Send ({limiter.state.rejectionCount} rejected)
</button>
)
}

이 가이드에서 이후에 다루는 핵심 코드 조각은 useRateLimiter를 사용하며 컴포넌트나 다른 훅 내부에서 실행한다고 가정합니다.

거부된 호출 처리하기​

거부된 호출은 나중에도 실행되지 않습니다. 불리언 반환 값이나 onReject를 사용해 피드백을 제공하거나 다른 곳에서 재시도하거나 작업을 큐에 넣습니다.

const limiter = useRateLimiter(sendEvent, {
limit: 2,
window: 1000,
onReject: (limiter) => {
console.log('Rejected calls:', limiter.store.state.rejectionCount)
},
})

거부된 작업을 결국 실행해야 한다면 일반적으로 큐어가 더 적합합니다.

용량 확인하기​

인스턴스 API는 두 가지 계산 헬퍼를 제공합니다.

limiter.getRemainingInWindow() // Accepted executions still available.
limiter.getMsUntilNextWindow() // Time until at least one execution is available.

두 헬퍼 모두 현재 limit, window, windowType, 실행 기록을 사용합니다.

제한기 재설정 및 설정하기​

reset()은 실행 타임스탬프, 카운터, 정리 타이머를 지웁니다. 다음 호출은 전체 용량으로 시작합니다.

limiter.reset()

설정을 업데이트하려면 setOptions()를 사용합니다.

limiter.setOptions({
limit: 10,
window: 30_000,
})

옵션을 변경해도 기존 실행 기록은 지워지지 않습니다. 새 설정을 새로운 시간 창으로 시작해야 한다면 reset()을 호출합니다.

enabled, limit, window 옵션에는 제한기 인스턴스를 받는 함수를 사용할 수 있습니다.

const limiter = useRateLimiter(sendEvent, {
enabled: (limiter) => limiter.store.state.executionCount < 100,
limit: (limiter) => (limiter.store.state.rejectionCount > 10 ? 2 : 5),
window: 60_000,
})

제한기를 비활성화하면 래핑된 함수가 실행되지 않습니다. 기존 실행 기록은 삭제하지 않습니다.

실행 관찰하기​

onExecute는 실행된 인수와 제한기 인스턴스를 받습니다. onReject는 제한기 인스턴스를 받습니다.

const limiter = useRateLimiter(sendEvent, {
limit: 5,
window: 1000,
onExecute: (args, limiter) => {
console.log('Sent:', args)
console.log('Remaining:', limiter.getRemainingInWindow())
},
onReject: (limiter) => {
console.log('Rejected:', limiter.store.state.rejectionCount)
},
})

Preact 수명 주기​

동기 제한기에는 대기 중이거나 활성 상태인 작업이 없으므로 어댑터에는 기본 작업 정리가 없습니다. 컴포넌트에서 제한기와 관련된 사용자 정의 정리가 필요할 때만 onUnmount를 사용합니다.

반응형 상태​

어댑터는 셀렉터 인수가 반환한 상태만 구독합니다. 셀렉터가 없으면 어댑터 상태는 비어 있습니다. 컴포넌트나 다른 훅의 최상위 수준에서 유틸리티를 만들고 뷰에서 사용하는 필드만 선택합니다.

const limiter = useRateLimiter(
sendEvent,
{ limit: 5, window: 60_000 },
(state) => ({
isExceeded: state.isExceeded,
rejectionCount: state.rejectionCount,
}),
)

console.log(limiter.state.isExceeded, limiter.state.rejectionCount)

옵션 함수와 수명 주기 콜백은 기반 공개 유틸리티 인스턴스를 받습니다. 위 예제처럼 해당 콜백 안에서 .store.state를 읽는 방식을 지원합니다. 렌더링 코드는 여기에 나온 선택된 어댑터 상태를 읽어야 합니다.

앱에서 유지한 선택 상태를 복원하려면 initialState로 부분 스냅샷을 전달합니다. 이 스냅샷은 기본값과 병합됩니다. 지속 가능한 필드만 복원합니다. 대기 중인 타이머는 복원되지 않습니다.

일반적으로 유용한 상태에는 다음 항목이 포함됩니다.

  • executionCount: 완료된 수락 실행의 총횟수입니다.
  • executionTimes: 현재 시간 창 계산에 사용하는 타임스탬프입니다.
  • isExceeded: 현재 제한에 도달했는지 여부입니다.
  • rejectionCount: 시간 창이 가득 차서 거부된 호출 수입니다.
  • status: 'disabled', 'exceeded', 'idle' 중 하나입니다.

어댑터 시그니처는 Preact API 레퍼런스를, 전체 옵션 및 상태 타입은 공개 코어 레퍼런스를 참고합니다.