Sliding window rate limiting

Switched the algorithm to use a circular buffer
based on a redis list
This commit is contained in:
Matt Van Horn 2013-05-25 12:37:28 -07:00
parent e72694c4ee
commit d5958f8779
2 changed files with 21 additions and 11 deletions

View file

@ -27,26 +27,36 @@ class RateLimiter
def performed! def performed!
return if rate_unlimited? return if rate_unlimited?
result = $redis.incr(@key).to_i if is_under_limit?
$redis.expire(@key, @secs) if result == 1 # simple ring buffer.
if result > @max $redis.lpush(@key, Time.now.to_i)
$redis.ltrim(@key, 0, @max - 1)
# In case we go over, clamp it to the maximum else
$redis.decr(@key) raise LimitExceeded.new(seconds_to_wait)
raise LimitExceeded.new($redis.ttl(@key))
end end
end end
def rollback! def rollback!
return if RateLimiter.disabled? return if RateLimiter.disabled?
$redis.decr(@key) $redis.lpop(@key)
end end
private private
def seconds_to_wait
@secs - age_of_oldest
end
def age_of_oldest
# age of oldest event in buffer, in seconds
Time.now.to_i - $redis.lrange(@key, -1, -1).first.to_i
end
def is_under_limit? def is_under_limit?
$redis.get(@key).to_i < @max # number of events in buffer less than max allowed? OR
($redis.llen(@key) < @max) ||
# age bigger than silding window size?
(age_of_oldest > @secs)
end end
def rate_unlimited? def rate_unlimited?

View file

@ -50,7 +50,7 @@ describe RateLimiter do
end end
it "raises an error the third time called" do it "raises an error the third time called" do
lambda { rate_limiter.performed! }.should raise_error lambda { rate_limiter.performed! }.should raise_error(RateLimiter::LimitExceeded)
end end
context "as an admin/moderator" do context "as an admin/moderator" do