レートおよびクォータ制限
レートおよびクォータ制限を使用して、あなたの Squid バックエンドの機能に制限をかけられます
この機能を使用すると、バックエンドで実装した機能に対して制限を定義できます。
- ユーザー別、IP アドレス別、グローバルなど、スコープごとに制限を定義できます。
- 毎時から年次までのクォータの更新期間を指定できます。更新期間は、renewPeriodパラメータを使用して定義し、hourly、daily、weekly、monthly、quarterly、またはannuallyを指定します。
- これらの特性を積み重ねることで、複雑な制限を設定できます。
なぜバックエンドに制限をかけるのか?
バックエンドの機能にレートおよびクォータ制限を実装することで、不正利用の防止、リソースの保護、コスト削減、Denial-of-Service (DoS) 攻撃の緩和、そしてサービス品質の保証に役立ちます。
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 デコレーターは、2つのオプションパラメータ、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' }
  ]
})
rateLimit と quotaLimit の併用
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回に達した場合、その年の残りの期間はクエリが許可されなくなります。
制限の適用
レート制限は常に評価され、クエリが拒否された場合でも消費される可能性があります。これが何を意味するのか、以下の2つの例で見てみましょう。
レート制限に達した場合
次の例の設定をご覧ください:
@limits({ rateLimit: 5, quotaLimit: 20 })
タイムラインは次のようになります:
| イベント | 残りのレート予算 | 残りのクォータ予算 | 結果 | 
|---|---|---|---|
| 初期値 | 5 | 20 | |
| クエリ5回実行 | 0 | 15 | クエリ成功 | 
| 6回目のクエリ実行 | 0 | 15 | レート制限による拒否 | 
レート制限により6回目のクエリは拒否され、クォータ制限は消費されません。
クォータ制限に達した場合
ただし、クォータを超過すると、常にレート制限の消費も伴います。
次の例の設定をご覧ください:
@limits({ rateLimit: 10, quotaLimit: 5 })
タイムラインは次のようになります:
| イベント | 残りのレート予算 | 残りのクォータ予算 | 結果 | 
|---|---|---|---|
| 初期値 | 10 | 5 | |
| クエリ5回実行 | 5 | 0 | クエリ成功 | 
| 6回目のクエリ実行 | 4 | 0 | クォータ制限による拒否 | 
クォータ制限により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クエリの単一バケットを共有します。
アトミック性
クエリがバッチで送信され、そのバッチ内の途中で制限に達した場合、バッチ全体が拒否されます。これにより、一部のみの変更が行われることを防ぎます。
リフィルと更新
クォータの更新
未使用のクォータは次の期間に繰り越されません。各クォータには更新期間が定義されており、その正確な期間は以下の通りです:
| 期間 | 継続時間 | 
|---|---|
| hourly | 1 hour | 
| daily | 1 day | 
| weekly | 7 days | 
| monthly | 30 days | 
| quarterly | 90 days | 
| annually | 365 days | 
Squid は、以下の2通りの方法で、先に該当するタイミングでクォータを更新します:
- Periodically:各時間の先頭(各時間の0分)に、各クォータ制限が更新対象かどうかを確認します。
- On demand:クエリがクォータを超過した場合、その時点でクォータが更新対象であれば更新されます。
クォータ期間の開始時刻は、特定のクォータ(関数、scope、renewPeriod、および value のユニークな組み合わせ)が初めてバックエンドに導入された時刻です。
レート制限のリフィル
消費バケットは徐々にリフィルされ、最大で定めたレートの3倍までのバーストを許可します。
Gradual refill の例: もし @limits({ rateLimit: 5 }) を定義し、クライアントが制限を超過した場合、クライアントは次のクエリを実行するまでに 1/5 秒 (0.2s) 待つだけで済みます。
制限の変更
新しいバックエンドをデプロイすることで、いつでも制限を変更できます。クォータに関しては、特定の「limit combo」(関数、scope、renewPeriod のユニークな組み合わせ)の制限値を変更すると、アクティブなカウントがリセットされます。たとえば、ユーザーが10回呼び出しており、制限が20から15に変更された場合、ユーザーはさらに15回の呼び出しが可能になります(5回ではありません)。新しいバックエンドのデプロイで該当する「limit combo」に変更がなければ、アクティブカウントはリセットされません。
アカウントへの影響の理解
定義した制限を超過したために関数呼び出しが拒否された場合、その呼び出しは請求対象の使用量にはカウントされません。しかし、Squid はあなたの請求プランに関連するクォータを管理しており、定義した制限により拒否されたかどうかにかかわらず、すべてのクエリは請求プランにカウントされます。
たとえば、次のクォータ制限を定義した場合:
@limits({ quotaLimit: 5 })
そして8回のクエリを実行すると、最初の5回は成功し、残りの3回は拒否されます。請求対象となるのは成功した5回のクエリのみですが、Squid はアカウントのクォータとして8回のクエリをカウントします。
Squid のクォータおよび請求に関する詳細については、Quotas and limits documentation をご覧ください。