class Congestion::RateLimiter
Attributes
key[RW]
options[RW]
redis[RW]
Public Class Methods
new(redis, key, opts = { })
click to toggle source
# File lib/congestion/rate_limiter.rb, line 5 def initialize(redis, key, opts = { }) self.redis = redis self.key = "#{ opts[:namespace] }:#{ key }" self.options = opts self.options[:interval] *= 1_000 self.options[:min_delay] *= 1_000 allowed? end
Public Instance Methods
allowed?()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 28 def allowed? add_request unless rejected? !rejected? end
backoff()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 45 def backoff if too_many? && too_frequent? [quantity_backoff, frequency_backoff].max elsif too_many? quantity_backoff elsif too_frequent? frequency_backoff else 0 end end
first_request()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 18 def first_request first = get_requests[2].first first ? first.to_i : nil end
last_request()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 23 def last_request last = get_requests[3].first last ? last.to_i : nil end
rejected?()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 33 def rejected? too_many? || too_frequent? end
too_frequent?()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 41 def too_frequent? last_request && time_since_last_request < options[:min_delay] end
too_many?()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 37 def too_many? total_requests > options[:max_in_interval] end
total_requests()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 14 def total_requests get_requests[1] end
Protected Instance Methods
add_request()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 85 def add_request unless options[:track_rejected] add_request = redis.multi do |t| t.zadd key, current_time, current_time # [0] - key added t.ttl key # [1] - key ttl end # TTL is -1 if not set, https://redis.io/commands/ttl if add_request[1] == -1 # ensure we set the expire TTL on the request key # using the raw interval here after the 'get_requests' limit check # should be close enough to the actual interval folks desire redis.pexpire key, options[:interval] end end end
current_time()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 59 def current_time @current_time ||= (Time.now.utc.to_f * 1_000).round end
expired_at()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 71 def expired_at current_time - options[:interval] end
frequency_backoff()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 80 def frequency_backoff millis = options[:min_delay] - time_since_last_request (millis / 1_000.0).ceil end
get_requests()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 101 def get_requests @requests ||= redis.multi do |t| t.zremrangebyscore key, 0, expired_at # [0] - clear old requests t.zcount key, '-inf', '+inf' # [1] - number of requests t.zrange key, 0, 0 # [2] - first request t.zrange key, -1, -1 # [3] - last request if options[:track_rejected] t.zadd(key, current_time, current_time) # [4] - Add the request if tracking rejected end # ensure the TTL is set after we've added the key if tracking rejected t.pexpire key, options[:interval] # [5] - expire request key end end
quantity_backoff()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 75 def quantity_backoff millis = options[:interval] - time_since_first_request (millis / 1_000.0).ceil end
time_since_first_request()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 67 def time_since_first_request current_time - first_request end
time_since_last_request()
click to toggle source
# File lib/congestion/rate_limiter.rb, line 63 def time_since_last_request current_time - last_request end