2024-12-22 Redis Rate Limit パターン
Redis を用いた素朴なIPベースのRate Limit を実装したRack Middleware。
https://github.com/toshimaru/Study/tree/main/ruby/rack/ratelimit
# app.rb
require_relative 'redis_rate_limitter'
class App
def call(env)
[ 200, {}, ["hello"] ]
end
end
use RedisRateLimiter, limit: 10, window: 60
run App.new
require 'redis'
class RedisRateLimiter
def initialize(app, limit: 60, window: 60)
@app = app
@limit = limit
@window = window
@redis = Redis.new
end
def call(env)
request = Rack::Request.new(env)
client_ip = request.ip
key = "rate_limit:#{client_ip}"
count = @redis.get(key).to_i
if count >= @limit
[429, { 'Content-Type' => 'text/plain' }, ["Rate limit exceeded. Try again later."]]
else
@redis.multi do |transaction|
transaction.incr(key)
transaction.expire(key, @window) if count == 0
end
@app.call(env)
end
end
end
Redis公式のベストプラクティス
その他のアルゴリズムの検討
今回実装したのは固定ウィンドウカウンタの制限アルゴリズム。
トークンバケットやリーキーバケットは LUA スクリプトを使う実装が必要っぽくて、手間がかかるっぽい。
Rails 7.2 から標準でレートリミット機能が搭載される
詳しい解説はこちら: Rails 7.2 Adds Rate Limiting to Action Controller | Saeloun Blog
class SessionsController < ApplicationController
rate_limit to: 10, within: 3.minutes, only: :create
end
ref. ActionController::RateLimiting::ClassMethods | RailsDoc(β)
導入経緯も面白くて、DHHがKredisに依存したかたちで初期実装を行い、その後キャッシュレイヤーを抽象化したActiveSupport::Cache
で書き換えられているのが面白い。
- 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
これによりCacheバックエンドがRedis以外の様々なものに対応できるようになった。素晴らしい抽象化が働いた例だと思う。