
# レート制限

Queuetyは、外部APIやその他の制約があるリソースへの過負荷を防ぐため、ハンドラーごとのレート制限に対応しています。ハンドラーが上限に達すると、ワーカーはそのジョブを保留し、ウィンドウに空きができたら再び取得します。

## レート制限の設定

ジョブのディスパッチ時にレート制限を適用します。

```php
use Queuety\Queuety;

Queuety::dispatch( 'call_openai', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds
```

引数は次の2つです。
1. `$max` - ウィンドウ内で許可される実行の最大数
2. `$window` - ウィンドウの時間（秒）

ディスパッチ時の上限は、ジョブをディスパッチするPHPプロセスにのみ登録されます。`wp queuety work`のように別プロセスで動作するワーカーからは見えないため、プロセス間で適用する上限は、以下のようにハンドラーの`config()`に設定してください（[#3](https://github.com/inline0/queuety/issues/3)）。

## ハンドラー単位のレート制限

ハンドラーの`config()`メソッドにレート制限を設定すると、すべてのディスパッチに適用されます。

```php
use Queuety\Handler;

class CallOpenAIHandler implements Handler {
    public function handle( array $payload ): void {
        // Call the API...
    }

    public function config(): array {
        return [
            'rate_limit' => [ 60, 60 ], // 60 requests per minute
        ];
    }
}
```

## 仕組み

レート制限はハンドラー名ごとに追跡されます。ジョブを取得した後、ワーカーはハンドラーの上限（`config()`から取得）を登録し、レートリミッターを確認します。上限に達している場合、ワーカーは遅延も試行回数の消費もなく、ジョブを`pending`に戻してから`QUEUETY_WORKER_SLEEP`秒間待機し、再度ポーリングします。`--once`を指定して起動したワーカーは、代わりに終了します。

各ワーカーはメモリ内カウンターを保持し、5秒ごとに`queuety_logs`テーブルから更新します。このカウンターは、過去`$window`秒間におけるそのハンドラーの`completed`ログエントリー数を数えます。カウントは共有ログテーブル（および設定されている場合は共有キャッシュ）から取得するため、ハンドラー単位の制限は1つのワーカー内だけでなく、すべてのワーカープロセスに適用されます。失敗した実行は上限に含まれません。

## プログラムからのアクセス

レートリミッターに直接アクセスします。

```php
$limiter = Queuety::rate_limiter();

// Register a rate limit
$limiter->register( 'call_openai', 60, 60 );

// Check whether a handler has reached its limit
$limited = $limiter->is_limited( 'call_openai' ); // true while the limit is reached

// Seconds until the current window resets
$wait = $limiter->time_until_available( 'call_openai' );
```

`register()`と`is_limited()`は、現在のPHPプロセス内のリミッターに作用します。ここで上限を登録しても、別途実行中のワーカーが適用する制限は変わりません。

## 活用例

- **外部APIの制限。** レート制限をAPIのクォータ（e.g例：OpenAIの60 RPM）に合わせます。
- **メール送信の抑制。** 短時間に大量のメールを送信するのを防ぎます。
- **リソースの保護。** CPU負荷の高いハンドラーを制限し、他のジョブが滞らないようにします。
