レートとクォータ制限
Squidバックエンドの関数にレートとクォータ制限を適用
この機能を使用すると、バックエンドで作成した関数に対して制限を定義できます。
- ユーザー、IPアドレス、グローバルなどのスコープごとに制限を定義します。
- 時間単位から年単位まで、さまざまなクォータ更新期間を指定できます。更新期間は
renewPeriodパラメータを使って定義し、hourly、daily、weekly、monthly、quarterly、またはannuallyを指定します。 - これらの特性を積み重ねることで、複雑な制限を作成できます。
なぜバックエンドを制限するのか?
バックエンド関数にレートおよびクォータ制限を実装することで、乱用の防止、リソースの保護、コスト削減、DoS攻撃(Denial-of-Service)の軽減、そしてサービス品質の維持に役立ちます。
The @limits decorator
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 デコレーターは、オプションのパラメータである rateLimit と quotaLimit を取ります。
rateLimit
rateLimit は以下の3つの形式で定義できます:
- 関数に対して1秒あたりに制限したいクエリ数を表す number。この場合、デフォルトのスコープは
globalです:
@limits({ rateLimit: 5 })
- スコープのカスタマイズを可能にする object。
scopeパラメータはuser、ip、またはglobalが使用できます:
@limits({ rateLimit: { value: 7, scope: 'user' } })
- 複数の制限を積み重ねることができる list of objects:
@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 のデフォルトが適用されます。
- 複数の制限を積み重ねることができる list of objects:
@limits({
quotaLimit: [
{ value: 7, scope: 'user', renewPeriod: 'monthly' },
{ value: 20, scope: 'user', renewPeriod: 'annually' }
]
})
Using both rate and quota limits
レート制限とクォータ制限の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 セクションを参照してください。
制限の理解
任意の数の制限を定義できます。すべての制限は各クエリごとに消費されます。一度いずれかの制限が超過されると、他のすべての制限に優先してそのクエリは拒否されます。
たとえば、ある関数が1ヶ月あたり5クエリのクォータと1年あたり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クエリの独自バケットを持ちますが、不明なユーザーはすべて同じ7クエリのバケットを共有します。
Atomicity
バッチとしてクエリが送信され、その途中で制限に達した場合、バッチ全体が拒否されます。これにより、一部だけが処理されることが防がれます。
Refills & renewals
クォータ更新
未使用のクォータは次の期間に繰り越されません。各クォータには定義された更新期間があり、その期間は以下の通りです:
| 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: クエリがクォータを超過した場合、その時点でクォータが更新可能であれば更新されます。
クォータ期間の開始時刻は、その特定のクォータ(関数、スコープ、renewPeriod、value のユニークな組み合わせ)がバックエンドのデプロイメントに初めて導入された時刻です。
レート制限のリフィル
消費バケットは徐々にリフィルされ、最大で設定されたレートの3倍までのバーストが可能です。
例としての Gradual refill: 例えば、@limits({ rateLimit: 5 }) を定義し、クライアントが制限を超過した場合、クライアントは次のクエリを送信する前に1/5秒(0.2秒)待てばよいという意味です。
制限の変更
新しいバックエンドをデプロイすることで、いつでも制限を変更できます。クォータに関しては、特定の「limit combo」(関数、スコープ、renewPeriod のユニークな組み合わせ)に対する制限値の変更は、アクティブなカウントをリセットします。たとえば、あるユーザーが10回呼び出しを行い、制限が20から15に変更された場合、そのユーザーはさらに15回の呼び出しが可能になります(5回ではありません)。新しいバックエンドのデプロイメントで特定の「limit combo」に変更がない場合、アクティブなカウントはリセットされません。
アカウントへの影響の理解
定義された制限を超えたために関数呼び出しが拒否された場合、そのクエリは課金対象の利用にはカウントされません。しかし、Squidは請求プランに関連するクォータを管理しており、制限によって拒否されたかどうかにかかわらず、すべてのクエリが請求プランにカウントされます。
例えば、以下のようにクォータ制限を定義した場合:
@limits({ quotaLimit: 5 })
そして8回のクエリを実行すると、最初の5回は成功し、残りの3回は拒否されます。請求対象になるのは成功した5回分だけですが、Squidはアカウントのクォータに対して8回分のクエリをカウントします。
Squidのクォータおよび課金に関する詳細は、Quotas and limits documentation をご覧ください。