メインコンテンツまでスキップ

Rate と Quota の制限

server-side の rate limit と quota limit により、バックエンド function を不正利用から保護します。​

Rate and Quota Limiting を使用する理由​

バックエンド function はインターネットに公開されています。任意の client が呼び出すことができ、制限がなければ、単一の user または bot が service に過大な負荷をかけたり、API quota を使い果たしたり、想定外の費用を発生させたりする可能性があります。

Backend code
// Without limits: any client can call this as fast as they want
@executable()
async callExternalApi(query: string): Promise<ApiResult> {
const apiKey = this.secrets['API_KEY'];
return await fetch(`https://api.example.com?q=${query}`, {
headers: { Authorization: `Bearer ${apiKey}` },
}).then((r) => r.json() as Promise<ApiResult>);
}

// With limits: 10 requests/second per user, 1000/month per user
@limits({
rateLimit: { value: 10, scope: 'user' },
quotaLimit: { value: 1000, scope: 'user', renewPeriod: 'monthly' },
})
@executable()
async callExternalApi(query: string): Promise<ApiResult> {
const apiKey = this.secrets['API_KEY'];
return await fetch(`https://api.example.com?q=${query}`, {
headers: { Authorization: `Bearer ${apiKey}` },
}).then((r) => r.json() as Promise<ApiResult>);
}

1 つの decorator で、どの client も bypass できない server-side enforcement を実現できます。

概要​

@limits decorator を使用すると、任意の executable、webhook、または OpenAPI function に rate limit(1 秒あたりの request 数)および quota limit(期間あたりの合計 call 数)を定義できます。function body が実行される前に、server-side で limit が適用されます。

使用する場面​

シナリオ推奨
burst による不正利用を防止する(例: 高頻度な API call)rate limit を使用
請求期間内の合計使用量を制限するquota limit を使用
高コストな external API を保護する両方を併用する
未認証 access を制限するglobal または ip scope の limit を使用
user ごとに公平な利用量を設定するauthentication とともに user scope の limit を使用

仕組み​

  1. backend function に @limits decorator を追加します
  2. backend の deploy 時に、Squid が limit を登録します
  3. call ごとに、Squid は function を実行する前にすべての limit を確認します
  4. いずれかの limit を超過した場合、call は error で拒否され、function body は実行されません
  5. rate limit bucket は徐々に refill され、quota limit は固定期間ごとに renew されます

クイックスタート​

前提条件​

  • squid init で初期化された Squid backend project
  • NPM からインストールされた @squidcloud/backend package

ステップ 1: @limits decorator を追加する​

limits を import し、function に適用します。

Backend code
import { executable, limits, SquidService } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@limits({ rateLimit: 5, quotaLimit: 200 })
@executable()
async greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
}

これにより、greet は global に 1 秒あたり 5 call、1 か月あたり 200 call に制限されます。

ステップ 2: backend を開始または deploy する​

ローカル開発では、Squid CLI を使用して backend をローカルで実行します。

squid start

cloud に deploy するには、backend の deployを参照してください。

ステップ 3: limit の動作を確認する​

client から function を呼び出します。limit 超過後の subsequent call は拒否されます。

Client code
try {
const result = await squid.executeFunction('greet', 'World');
console.log(result);
} catch (error) {
console.error('Limit exceeded:', error.message);
}

@limits Decorator​

@limits decorator は、rateLimit と quotaLimit の 2 つの optional parameter を受け取ります。

rateLimit​

rateLimit は 3 つの形式で定義できます。

  • function を制限する 1 秒あたりの query 数を表す数値。default では global scope になります。
Backend code
@limits({ rateLimit: 5 })
  • scope のカスタマイズを有効にするobject。scope parameter には user、ip、global を指定できます。
Backend code
@limits({ rateLimit: { value: 7, scope: 'user' } })
  • 複数の limit を stack できるobject の list。
Backend code
@limits({
rateLimit: [
{ value: 5, scope: 'user' },
{ value: 10, scope: 'ip' }
]
})
注記

この list のすべての limit は、query ごとに消費されます。最初に消費された limit が超過を通知した場合でも、exception が client に返される前にほかのすべての limit も消費されます。複数の limit が同じ query を拒否する場合、最初に拒否した limit が client に返されます。

quotaLimit​

quotaLimit も同じ 3 つの形式で定義できます。

  • function を query できる合計回数を表す数値。default では global scope および monthly renewal period になります。
Backend code
@limits({ quotaLimit: 5 })
  • scope と renewal period のカスタマイズを有効にするobject。
Backend code
@limits({ quotaLimit: { value: 7, scope: 'user', renewPeriod: 'annually' } })

注記: scope と renewPeriod は optional であり、指定しない場合も global と monthly の default が適用されます。

  • 複数の limit を stack できるobject の list。
Backend code
@limits({
quotaLimit: [
{ value: 7, scope: 'user', renewPeriod: 'monthly' },
{ value: 20, scope: 'user', renewPeriod: 'annually' }
]
})

Rate limit と quota limit の併用​

2 つの parameter を併用して、rate limit と quota limit の両方を定義できます。

Backend code
@limits({
rateLimit: [
{ value: 5, scope: 'user' },
{ value: 10, scope: 'ip' }
],
quotaLimit: [
{ value: 7, scope: 'user', renewPeriod: 'monthly' },
{ value: 20, scope: 'user', renewPeriod: 'annually' }
]
})

これらの limit の評価方法については、enforcement section を参照してください。

Limit の理解​

任意の数の limit を定義できます。すべての limit は query ごとに消費されます。いずれかの limit を超過すると、他のすべての limit より優先され、query は拒否されます。

たとえば、function に月あたり 5 query の quota と年あたり 10 query の quota があるとします。

Backend code
@limits({
quotaLimit: [
{ value: 5, renewPeriod: 'monthly' },
{ value: 10, renewPeriod: 'annually' }
]
})

月の最初の週に 5 query を実行した場合、annual quota に達していなくても、その月の残りの期間では query を実行できません。同様に、年の最初の 2 か月間に月あたり 5 query、合計 10 query を実行した場合、残りの月で monthly quota に達していなくても、その年の残りの期間では query を実行できません。

Enforcement​

rate limit は常に評価され、query が拒否された場合でも消費されることがあります。これが何を意味するか、2 つの例で説明します。

Rate limit に達する場合​

次の configuration を使用します。

Backend code
@limits({ rateLimit: 5, quotaLimit: 20 })

budget の推移:

Event残り Rate Budget残り Quota Budget結果
初期値520
5 query を実行015Query は成功
6 回目の query を実行015Rate limit により拒否

rate limit は 6 回目の query を拒否し、quota limit は消費されません。

Quota limit に達する場合​

一方、quota の超過時には常に rate limit が消費されます。

次の configuration を使用します。

Backend code
@limits({ rateLimit: 10, quotaLimit: 5 })

budget の推移:

Event残り Rate Budget残り Quota Budget結果
初期値105
5 query を実行50Query は成功
6 回目の query を実行40Quota limit により拒否

quota limit は 6 回目の query を拒否しますが、rate limit は引き続き消費され、budget は 4 になります。

Limit を超過した場合​

limit を超過した場合、function は適切に "Rate limit on \name` exceeded"または"Quota on `name` exceeded"` という message を含む exception を返します。

name は、function 名、scope、quota limit の場合は renew period を含む string です。scope が user または IP の場合、user ID または IP address も string に含まれます。

User または IP が不明な場合の User/IP-based limit​

user/IP-based limit を定義する場合、何らかの理由で client の user または IP が不明であれば、その client は他のすべての unknown client とともに、単一の unknown entity として bucket 化されます。つまり、login していないすべての user は単一の user として扱われ、同じ rate/quota bucket を消費します。

たとえば、次の limit がある場合:

Backend code
@limits({ rateLimit: { value: 7, scope: 'user' } })

login 済み user はそれぞれ 1 秒あたり 7 query の専用 bucket を取得しますが、unknown user は全員で 1 秒あたり 7 query の単一 bucket を共有します。

Atomicity​

query が batch として送信され、その batch 内の途中で limit に達した場合、batch 全体が拒否されます。これにより partial change が行われないことを保証します。

Refill と Renewal​

Quota renewal​

未使用 quota は次の period に carry-over されません。各 quota には renewal period が定義され、その正確な duration は以下のとおりです。

PeriodDuration
hourly1 時間
daily1 日
weekly7 日
monthly30 日
quarterly90 日
annually365 日

Squid は、先に発生した方の方法で quota を renew します。

  1. 定期的: 毎時 0 分に、各 quota limit が renewal 対象かどうか確認されます。
  2. オンデマンド: query が quota を超過しても、その時点で quota が renewal 対象であれば、renew されます。

quota period の start time は、特定の quota(function、scope、renew period、value の unique な組み合わせ)が backend deployment で初めて導入された時刻です。

Rate limit refill​

consumption bucket は徐々に refill され、指定 rate の最大 3 倍までの burst を許可します。

注記

段階的な refill の例: @limits({ rateLimit: 5 }) を定義して client が limit を超過した場合、次の query を実行するまでに client が待つ必要があるのは 1/5 秒(0.2 秒)だけです。

Limit の変更​

新しい backend を deploy することで、いつでも limit を変更できます。quota の場合、特定の「limit combo」(function、scope、renewPeriod の unique な組み合わせ)の limit value を変更すると、active count が reset されます。たとえば user が 10 call を実行し、limit を 20 から 15 に変更した場合、user はさらに 15 call を実行できます(5 ではありません)。新しい backend deployment で指定した「limit combo」に変更がない場合、active count はreset されません。

Error Handling​

Error type​

limit を超過すると、client は HTTP status code 429(Too Many Requests) の error を受け取ります。error message は、超過した limit を示します。

  • Rate limit: "Rate limit on <name> exceeded"
  • Quota limit: "Quota on <name> exceeded"

<name> には function 名、scope、quota の場合は renewal period が含まれるため、どの limit に到達したかを正確に識別できます。

Client-side の処理​

client で limit error を catch し、適切に処理します。

Client code
try {
const result = await squid.executeFunction('callExternalApi', query);
console.log(result);
} catch (error: any) {
if (error?.statusCode === 429 || error?.message?.includes('limit')) {
console.warn('Rate or quota limit exceeded. Try again later.');
// Show a user-friendly message or implement backoff
} else {
console.error('Unexpected error:', error.message);
}
}

Troubleshooting​

症状考えられる原因解決方法
予期せず 429 error が発生するunknown user が user scope limit の単一 bucket を共有している各 user が専用 bucket を取得できるよう、authenticationを要求する
deploy のたびに limit が reset されるlimit value の変更により active count が reset されるdeploy 間で count を維持するには値を変更しない
想定どおりに quota が renew されないrenewal は毎時または次の超過 call 時に確認される次の毎時の確認まで待つか、別の call を実行して on-demand renewal を trigger する
burst に対して rate limit が厳しすぎるdefault bucket は 3 倍の burst を許可するが、limit が低すぎる可能性がある想定される burst pattern に対応できるよう rate limit value を増やす

ベストプラクティス​

適切な scope を選ぶ​

  • global: system-wide protection(例: external API への合計 load の制限)に使用します。すべての caller が同じ bucket を共有します。
  • ip: client ごとの limit が必要で authentication がない場合に使用します。public endpoint に適しています。
  • user: user ごとに公平な利用量を設定する場合に使用します。有効にするには authentication が必要です。そうでない場合、未認証 user は全員で 1 つの bucket を共有します。

適切な値を設定する​

  • rate limit は external API の limit または service の capacity に基づいて設定します。
  • quota limit は billing period ごとの想定 usage pattern に基づいて設定します。
  • まず余裕のある limit から始め、観測された traffic に基づいて厳しくします。

Rate limit と quota limit を重ねる​

defense in depth のために、両方を併用します。

  • Rate limit は burst による不正利用(例: bot が毎秒 100 request を送信すること)から保護します
  • Quota limit は継続的な不正利用(例: user が 1 か月で 10,000 request を送信すること)から保護します

Authentication と組み合わせる​

user scope limit を意味のあるものにするには、user が認証されている必要があります。そうでない場合、anonymous user は全員で 1 つの bucket を共有し、1 つの不正 client が全員の limit を使い果たす可能性があります。user scope limit は常に authentication check と組み合わせてください。

Backend code
@limits({
rateLimit: { value: 10, scope: 'user' },
quotaLimit: { value: 500, scope: 'user', renewPeriod: 'monthly' },
})
@executable()
async protectedAction(data: string): Promise<string> {
this.assertIsAuthenticated();
// ...
return `Processed: ${data}`;
}

backend function の保護の詳細については、backend での auth の使用を参照してください。

コード例​

Layered limit を備えた API proxy​

この例では、rate limit と quota limit、authentication、error handling のすべてを使用して、external API への request を proxy する現実的な service を示します。

Backend code
import { executable, limits, SquidService } from '@squidcloud/backend';

interface TranslationResult {
translatedText: string;
detectedLanguage: string;
}

export class TranslationService extends SquidService {
@limits({
rateLimit: [
{ value: 5, scope: 'user' },
{ value: 20, scope: 'global' },
],
quotaLimit: [
{ value: 100, scope: 'user', renewPeriod: 'daily' },
{ value: 2000, scope: 'global', renewPeriod: 'monthly' },
],
})
@executable()
async translate(text: string, targetLang: string): Promise<TranslationResult> {
this.assertIsAuthenticated();

if (!text || text.length > 5000) {
throw new Error('Text must be between 1 and 5000 characters');
}

const apiKey = this.secrets['TRANSLATION_API_KEY'];
const response = await fetch('https://api.translation.example.com/v1/translate', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ text, target: targetLang }),
});

if (!response.ok) {
console.error('Translation API error:', response.status);
throw new Error('Translation service unavailable');
}

return response.json() as Promise<TranslationResult>;
}
}

この configuration では、4 層の保護を提供します。

  • User ごとの rate limit(5/秒): 単一 user が endpoint に過度な request を送信することを防止します
  • Global rate limit(20/秒): external API を保護するため、合計 throughput を制限します
  • User ごとの daily quota(100/日): user ごとの fair usage limit
  • Global monthly quota(2000/月): external API の budget 保護

Account への影響の理解​

定義した limit の超過により function call が拒否された場合、billable usage には count されません。ただし、Squid は billing plan に関連する quota を維持しており、定義した limit により拒否されたかどうかにかかわらず、すべての query が billing plan に count されます。

たとえば、次の quota limit を定義したとします。

Backend code
@limits({ quotaLimit: 5 })

8 query を実行した場合、最初の 5 query は成功し、最後の 3 query は拒否されます。請求対象となるのは成功した 5 query のみですが、Squid は account の quota に対して 8 query を count します。

Squid の quota と billing の詳細については、Quotas and limits documentationを参照してください。