レート制限とクォータ制限
Squid バックエンドの関数にレート制限とクォータ制限を設定する
この機能を使うと、バックエンドに記述した関数に対して制限を定義できます。
- ユーザーごと、IP アドレスごと、グローバルなど、スコープごとに制限を定義できます。
- 1時間ごとから年ごとまでのクォータ更新期間を指定できます。更新期間は
renewPeriodパラメータで定義し、hourly、daily、weekly、monthly、quarterly、annuallyを指定できます。 - これらの特性をスタックして、複雑な制限を作成できます。
なぜバックエンドを制限するのか?
バックエンド関数にレート制限やクォータ制限を実装することで、不正利用の防止、リソース保護、コスト削減、サービス拒否(DoS)攻撃の緩和、そしてサービス品質の確保に役立ちます。
@limits デコレーター
Squid はデコレーターを提供しており、特定の executable、webhook、または OpenAPI 関数に適用する制限を定義するために使用できます。
次の例では、1 秒あたり 5 クエリと1 か月あたり 200 クエリの制限が適用されます。
import { executable, limits, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
@limits({ rateLimit: 5, quotaLimit: 200 })
@executable()
concat(str1: string, str2: string): string {
return `${str1}${str2}`;
}
}
@limits デコレーターは、任意の 2 つのパラメータ rateLimit と quotaLimit を受け取ります。
rateLimit
rateLimit は次の 3 つの形式で定義できます。
- 関数を制限したい 1 秒あたりのクエリ数を表す number。これはデフォルトで
globalスコープです。
@limits({ rateLimit: 5 })
- スコープのカスタマイズを有効にする object。
scopeパラメータにはuser、ip、globalを指定できます。
@limits({ rateLimit: { value: 7, scope: 'user' } })
- 複数の制限をスタックできる object のリスト。
@limits({
rateLimit: [
{ value: 5, scope: 'user' },
{ value: 10, scope: 'ip' }
]
})
このリスト内のすべての制限は、各クエリごとに消費されます。最初に消費された制限が超過を示した場合でも、例外がクライアントに返される前に他のすべての制限も消費されます。複数の制限が同じクエリを拒否している場合、最初に拒否したものがクライアントに返されます。
quotaLimit
quotaLimit も同じ 3 つの形式で定義できます。
- 関数をクエリできる合計回数を表す number。これはデフォルトで
globalスコープ、monthly更新期間です。
@limits({ quotaLimit: 5 })
- スコープおよび更新期間のカスタマイズを有効にする object。
@limits({ quotaLimit: { value: 7, scope: 'user', renewPeriod: 'annually' } })
注: scope と renewPeriod は任意で、省略した場合でも global と monthly のデフォルトが適用されます。
- 複数の制限をスタックできる object のリスト。
@limits({
quotaLimit: [
{ value: 7, scope: 'user', renewPeriod: 'monthly' },
{ value: 20, scope: 'user', renewPeriod: 'annually' }
]
})
rate と quota の両方の制限を使用する
2 つのパラメータを一緒に使うことで、レート制限とクォータ制限の両方を定義できます。
@limits({
rateLimit: [
{ value: 5, scope: 'user' },
{ value: 10, scope: 'ip' }
],
quotaLimit: [
{ value: 7, scope: 'user', renewPeriod: 'monthly' },
{ value: 20, scope: 'user', renewPeriod: 'annually' }
]
})
これらの制限がどのように評価されるかの詳細は、enforcement セクションを参照してください。
制限を理解する
制限はいくつでも定義できます。すべての制限は各クエリごとに消費されます。いずれかの制限を超過すると、それが他のすべての制限を上書きし、クエリは拒否されます。
たとえば、ある関数に「月次クォータ 5 クエリ」と「年次クォータ 10 クエリ」があるとします。
@limits({
quotaLimit: [
{ value: 5, renewPeriod: 'monthly' },
{ value: 10, renewPeriod: 'annually' }
]
})
月の最初の週に 5 クエリが実行されると、年次クォータに到達していなくても、その月の残り期間は追加のクエリが許可されません。同様に、年の最初の 2 か月で毎月 5 クエリずつ実行され合計 10 クエリになると、残りの月で月次クォータに達していなくても、その年の残り期間は追加のクエリが許可されません。
Enforcement
レート制限は常に評価され、クエリが拒否された場合でも消費されることがあります。これが何を意味するのか、2 つの例で見てみましょう。
レート制限に達する場合
次の設定例を考えます。
@limits({ rateLimit: 5, quotaLimit: 20 })
予算のタイムラインは次のとおりです。
| Event | Rate Budget Remaining | Quota Budget Remaining | Outcome |
|---|---|---|---|
| Starting values | 5 | 20 | |
| Make 5 queries | 0 | 15 | Queries succeed |
| Make a 6th query | 0 | 15 | Rate limit rejection |
レート制限が 6 回目のクエリを拒否し、クォータ制限は消費されません。
クォータ制限に達する場合
一方、クォータ超過では、常にレート制限の消費が発生します。
次の設定例を考えます。
@limits({ rateLimit: 10, quotaLimit: 5 })
予算のタイムラインは次のとおりです。
| Event | Rate Budget Remaining | Quota Budget Remaining | Outcome |
|---|---|---|---|
| Starting values | 10 | 5 | |
| Make 5 queries | 5 | 0 | Queries succeed |
| Make a 6th query | 4 | 0 | Quota limit rejection |
クォータ制限が 6 回目のクエリを拒否しますが、レート制限は 4 まで消費されます。
制限を超過した場合
制限を超えると、関数は状況に応じて "Rate limit on name exceeded" または "Quota on name exceeded" というメッセージを含む例外を返します。
name は、関数名、スコープ、(クォータ制限の場合は)更新期間を含む文字列です。スコープが user または IP の場合、ユーザー ID または IP アドレスも文字列に含まれます。
ユーザー/IP ベースの制限でユーザーまたは IP が不明な場合
ユーザー/IP ベースの制限を定義する際、何らかの理由で特定クライアントのユーザーまたは IP が不明な場合、そのクライアントは他の不明クライアントすべてと同じ単一の unknown エンティティとしてバケット化されます。つまり、ログインしていないすべてのユーザーは単一のユーザーと見なされ、同じレート/クォータバケットを消費します。
たとえば、次の制限がある場合:
@limits({ rateLimit: { value: 7, scope: 'user' } })
ログイン済みユーザーはそれぞれ 1 秒あたり 7 クエリの専用バケットを持ちますが、不明ユーザーは 1 秒あたり 7 クエリの単一バケットを共有します。
Atomicity
クエリがバッチとして送信され、バッチ途中で制限に到達した場合、バッチ全体が拒否されます。これにより、部分的な変更が行われないことが保証されます。
補充と更新
クォータ更新
未使用のクォータは次の期間に繰り越されません。各クォータには更新期間が定義されており、その正確な期間は次のとおりです。
| Period | Duration |
|---|---|
| hourly | 1 hour |
| daily | 1 day |
| weekly | 7 days |
| monthly | 30 days |
| quarterly | 90 days |
| annually | 365 days |
Squid は次の 2 つの方法でクォータを更新します(先に満たされた方)。
- Periodically: 毎時の先頭(各時刻の 0 分)に、各クォータ制限が更新対象かどうかチェックされます。
- On demand: クエリがクォータを超過した場合でも、その時点でクォータが更新対象であれば更新されます。
クォータ期間の開始時刻は、その特定のクォータ(関数、スコープ、更新期間、値のユニークな組み合わせ)がバックエンドのデプロイで初めて導入された時刻です。
レート制限の補充
消費バケットは徐々に補充され、指定レートの最大 3 倍までのバーストを許容します。
Gradual refill の例: @limits({ rateLimit: 5 }) を定義し、クライアントが制限を超えた場合、次のクエリを行うために必要な待ち時間は 1/5 秒(0.2 秒)だけです。
制限の変更
新しいバックエンドをデプロイすることで、制限はいつでも変更できます。クォータについては、特定の「limit combo」(関数、スコープ、renewPeriod のユニークな組み合わせ)に対する制限値を変更すると、アクティブなカウントがリセットされます。たとえば、ユーザーが 10 回呼び出していて、制限が 20 から 15 に変更された場合、そのユーザーは(残り 5 回ではなく)さらに 15 回呼び出せます。新しいバックエンドのデプロイで特定の「limit combo」に変更がない場合、アクティブなカウントはリセットされません。
アカウントへの影響を理解する
定義した制限の超過によって関数呼び出しが拒否された場合、その呼び出しは請求対象の利用量には カウントされません。ただし、Squid は課金プランに関連するクォータを維持しており、定義した制限で拒否されたかどうかに関係なく、すべてのクエリを課金プランのクォータに対して カウントします。
たとえば、次のクォータ制限を定義した場合:
@limits({ quotaLimit: 5 })
そして 8 回クエリを行うと、最初の 5 回は成功し、最後の 3 回は拒否されます。請求されるのは成功した 5 回のみですが、Squid はアカウントのクォータに対して 8 回分のクエリをカウントします。
Squid のクォータと課金に関する詳細は、Quotas and limits documentation を参照してください。