Class: Berater::RateLimiter

Inherits:
Limiter
  • Object
show all
Defined in:
lib/berater/rate_limiter.rb

Constant Summary collapse

LUA_SCRIPT =
Berater::LuaScript(<<~LUA
  local key = KEYS[1]
  local ts_key = KEYS[2]
  local ts = tonumber(ARGV[1])
  local capacity = tonumber(ARGV[2])
  local interval_msec = tonumber(ARGV[3])
  local cost = tonumber(ARGV[4])

  local allowed -- whether lock was acquired
  local count -- capacity being utilized
  local msec_per_drip = interval_msec / capacity
  local state = redis.call('GET', key)

  if state then
    local last_ts -- timestamp of last update
    count, last_ts = string.match(state, '([%d.]+);(%w+)')
    count = tonumber(count)
    last_ts = tonumber(last_ts, 16)

    -- adjust for time passing, guarding against clock skew
    if ts > last_ts then
      local drips = math.floor((ts - last_ts) / msec_per_drip)
      count = math.max(0, count - drips)
    else
      ts = last_ts
    end
  else
    count = 0
  end

  if cost == 0 then
    -- just checking count
    allowed = true
  else
    allowed = (count + cost) <= capacity

    if allowed then
      count = count + cost

      -- time for bucket to empty, in milliseconds
      local ttl = math.ceil(count * msec_per_drip)
      ttl = ttl + 100 -- margin of error, for clock skew

      -- update count and last_ts, with expiration
      state = string.format('%f;%X', count, ts)
      redis.call('SET', key, state, 'PX', ttl)
    end
  end

  return { tostring(count), allowed }
LUA
)

Instance Attribute Summary

Attributes inherited from Limiter

#capacity, #key, #options

Instance Method Summary collapse

Methods inherited from Limiter

#==, #limit, new, #redis, #utilization

Constructor Details

#initialize(key, capacity, interval, **opts) ⇒ RateLimiter

Returns a new instance of RateLimiter.



4
5
6
7
# File 'lib/berater/rate_limiter.rb', line 4

def initialize(key, capacity, interval, **opts)
  super(key, capacity, interval, **opts)
  self.interval = interval
end

Instance Method Details

#intervalObject



9
10
11
# File 'lib/berater/rate_limiter.rb', line 9

def interval
  args[0]
end

#to_sObject



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/berater/rate_limiter.rb', line 91

def to_s
  msg = if interval.is_a? Numeric
    if interval == 1
      "every second"
    else
      "every #{interval} seconds"
    end
  else
    "per #{interval}"
  end

  "#<#{self.class}(#{key}: #{capacity} #{msg})>"
end