Rails 8: 組み込みのレート制限APIを導入(翻訳)
Web開発の動的な世界では、リクエストのフローを管理することがアプリケーションの応答性と信頼性を維持するうえで重要です。レート制限(rate limiting)はAPIの交通整理巡査として機能する強力な技術であり、リソースへの正当なアクセスを確保して潜在的な混乱を防ぎます。レート制限を一言で言うと、「ユーザー」「デバイス」「アプリケーション」が一定の時間枠内で許されるリクエスト数(レート)を制御することです。
本記事では、レート制限の概念を詳しく掘り下げたうえで、Rails 8.0における重要性と実装を探っていきます。
🔗 レート制限の必要性
レート制限は、以下の点を確保するうえで重要です。
- セキュリティ上の防御
- アプリケーションをDoS(Denial-of-Service: サービス拒否)攻撃から保護し、アプリケーションを溢れさせてクラッシュさせようとする試みを阻止します。
- リソースのバランシング
- ヘビーユーザーを制限してリソースが公平に配分されるようにすることで、リソース乱用を防止し、アプリの最適なパフォーマンスを維持します。
- ブルートフォース攻撃の防御
- パスワードの推測や脆弱性の悪用を繰り返し試みる無慈悲なハッカーを阻止します。
🔗 Rack::Attackが整えてきた道
Rails 8.0より前の時代に頼れるソリューションはrack-attack gemです。rack-attackを使うことで、rack_attack.rbにカスタムコードを追加してレート制限をセットアップできるようになります。この方法は効果的ですが、エンドポイントごとに手動で介入する必要があり、アプリケーションが成長すると作業が面倒になる可能性があります。この外部依存によって、カスタムコードを必要とする点や、エンドポイントに漏れがないよう継続的に注意を払う必要がある点など、いくつかの課題が生じていました。
🔗 Rails 8.0ネイティブのレート制限機能
Rails 8.0のAction Controllerにネイティブのレート制限機能が導入され、これらの作業を合理化して外部gemへの依存を排除します。コントローラ内でrate_limit
メソッドを使ってレート制限を直接設定できるようになりました。
使い方を見てみましょう。
🔗 制限を定義する
コントローラのアクション内でrate_limit
を利用して、許可する最大リクエスト数および対応する時間枠を指定します。rate_limit
には以下のパラメータを渡せます。
to
- 許可する最大リクエスト数を指定します。この値を超えるとレート制限エラー(429 Too Many Requests)が発生します。
within
- 指定時間枠内の最大リクエスト数です。
only
- コントローラでレート制限の対象とするアクションを指定します。
except
- コントローラでレート制限から除外するアクションを指定します。
コード例:
class SignupController < ApplicationController
rate_limit to: 4, within: 1.minute, only: :create
def create
# ...
end
end
🔗 詳細な制御
特定のアクションのみを対象としたり、カスタムロジックを用いることで細かな制御を行います。たとえば以下のように、IPアドレスベースの代わりにドメインベースでリクエストを制限できます。
rate_limit to: 4, within: 1.minute, by: -> { request.domain }, only: :create
🔗 レスポンスのカスタマイズ
レート制限を受けたリクエストにはデフォルトで「429 Too Many Requests」が返されますが、レスポンスをカスタマイズすることでユーザーにわかりやすくできます。
rate_limit to: 4, within: 1.minute, with: -> { redirect_to(ip_restrictions_controller_url), alert: "Signup Attempts failed four times. Please try again later." }, only: :create
🔗 カスタムのキャッシュストア
Rails 8.0の現時点のレート制限実装では、以下を含むさまざまなバックエンドをキャッシュストアとして利用できます。
- Memcached
- Redis(DalliなどのRailsオルタナクライアントも含む)
- DBベースのストア
- ファイルベースのストア
レート制限APIは、ActiveSupport::Cache
ストアを活用してRailsのキャッシュメカニズムとシームレスに統合されます。他のキャッシュデータと比較してレート制限を個別に処理する必要が生じた場合は、カスタムキャッシュストアを指定できます。この統合により、レート制限データを効率よく保存・取得できるようになり、パフォーマンスが最適化されます。
class SignupController < ApplicationController
RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
rate_limit to: 8, within: 2.minutes, store: RATE_LIMIT_STORE
end
🔗 レート制限で期待できること
現時点の組み込みレート制限機能は、拡張性に限界があるため、rack-attack gemベースのロジックを直接置き換えるのに利用できない可能性があります。良い例としては、レート制限アルゴリズムに指数関数的バックオフを用いるシナリオがあります。これはrack-attack gemでは実装できますが、現時点のRails組み込みレート制限では実装できません。
現在は、Railsに付属するデフォルトのキャッシュカウンタアルゴリズムを使うことになります。今後はより汎用性の高いレート制限インターフェイスによって、指数関数的バックオフやリーキーバケット1、トークンバケット2などの高度なアルゴリズムを探索可能になることが期待されます。そうした変更が行われれば、開発者が特定要件に応じて実装を差し替えられるようになるでしょう。
詳しくは以下のプルリクを参照してください。
- Add rate limiting to Action Controller via the Kredis limiter type by dhh · Pull Request #50490 · rails/rails
- Refactor
ActionController::RateLimiting
to useAS::Cache
by casperisfine · Pull Request #50781 · rails/rails
概要
元サイトの許諾を得て翻訳・公開いたします。
参考: 週刊Railsウォッチ20240123: レート制限APIを追加