mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 23:58:31 -05:00
Sliding window rate limiting
Switched the algorithm to use a circular buffer based on a redis list
This commit is contained in:
parent
e72694c4ee
commit
d5958f8779
2 changed files with 21 additions and 11 deletions
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue