タスクキューは多くのWebアプリケーションで必要不可欠な機能ですが、大量のタスクを処理する際にはレート制限が重要になります。特に外部API呼び出しやメール送信などのタスクでは、適切なレート制限なしに処理すると、サービス制限に引っかかったり、相手先のサーバーに負荷をかけてしまう可能性があります。
今回は、Go言語の人気タスクキューライブラリであるAsynqにレート制限を実装する方法を、複数のアプローチとともに解説します。
プロジェクト構成
このサンプルプロジェクトは以下の構成になっています。
- サンプルコードはGitHubで公開しています。
1 | asynq-late-limiter/ |
1. インターフェース設計
まず、レート制限の基盤となるインターフェースを定義します。
1 | // limiter/limiter.go |
このインターフェース設計により、異なるレート制限の実装を簡単に差し替えることができます。
2. レート制限の実装方式
2.1 golang.org/x/time/rateを使った実装
標準的なトークンバケットアルゴリズムを使った実装です。
1 | // limiter/rate_limiter.go |
メリット
- 実装が簡単
- メモリ効率が良い
- 高速
デメリット
- 単一プロセスでのみ動作
- 分散環境では使用できない
2.2 Redisベースの実装(改良版)
複数のワーカープロセス間でレート制限を共有する場合に適しています。
1 | // limiter/redis_rate_limiterv2.go |
メリット
- 分散環境での動作
- 複数のワーカー間でレート制限を共有
- 正確な待機時間の算出
デメリット
- Redisへの依存
- わずかなパフォーマンスオーバーヘッド
3. Asynqとの統合
タスクの定義
1 | // tasks/tasks.go |
ワーカーの設定
1 | // worker/main.go |
エラーハンドリングとリトライ戦略
1 | // レート制限エラーの判定 |
4. 実行方法
1. 依存関係のインストール
1 | go mod tidy |
2. Redisの起動
1 | # Dockerを使用する場合 |
3. ワーカーの起動
1 | go run worker/main.go |
4. タスクの投入
別のターミナルで以下のコマンドを実行。
1 | go run producer/main.go |
5. 実装のポイント
レート制限の適切な設定
- 外部API呼び出し: APIの制限に合わせて設定(例:100リクエスト/分)
- メール送信: メール送信サービスの制限に合わせて設定(例:10通/秒)
- データベース操作: データベースの負荷を考慮して設定
エラーハンドリング
1 | srv := asynq.NewServer( |
まとめ
このサンプルコードでは、Asynqにレート制限を実装する複数の方法を紹介しました。どの実装を選ぶかは、システムの要件によって決まります:
- 単一プロセス環境:
golang.org/x/time/rateベースの実装 - 分散環境: Redisベースの実装
インターフェースベースの設計により、後から実装を切り替えることも容易です。適切なレート制限により、安定したタスク処理システムを構築できます。