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