Class: RedisThrottler::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/redis-throttler/base.rb

Instance Method Summary collapse

Constructor Details

#initialize(key, options = {}) ⇒ RedisThrottler

Create a RedisThrottler object.

Parameters:

  • key (String)

    A name to uniquely identify this rate limit. For example, 'emails'

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :bucket_span (Integer) — default: 600

    Time span to track in seconds

  • :bucket_interval (Integer) — default: 5

    How many seconds each bucket represents

  • :bucket_expiry (Integer) — default: @bucket_span

    How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span.

  • :redis (Redis) — default: nil

    Redis client if you need to customize connection options



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/redis-throttler/base.rb', line 14

def initialize(key, options = {})
  @key = key
  @bucket_span = options[:bucket_span] || 600
  @bucket_interval = options[:bucket_interval] || 5
  @bucket_expiry = options[:bucket_expiry] || @bucket_span
  if @bucket_expiry > @bucket_span
    raise ArgumentError.new("Bucket expiry cannot be larger than the bucket span")
  end
  @bucket_count = (@bucket_span / @bucket_interval).round
  if @bucket_count < 3
    raise ArgumentError.new("Cannot have less than 3 buckets")
  end
  @redis = options[:redis]
end

Instance Method Details

#add(subject, count = 1) ⇒ Integer

Increment counter for a given subject.

Parameters:

  • subject (String)

    A unique key to identify the subject. For example, '[email protected]'

  • count (Integer) (defaults to: 1)

    The number by which to increment the counter

Returns:

  • (Integer)

    increments within interval



35
36
37
38
39
40
41
42
43
44
# File 'lib/redis-throttler/base.rb', line 35

def add(subject, count = 1)
  bucket = get_bucket
  subject = "#{@key}:#{subject}"
  redis.pipelined do
    redis.hincrby(subject, bucket, count)
    redis.hdel(subject, (bucket + 1) % @bucket_count)
    redis.hdel(subject, (bucket + 2) % @bucket_count)
    redis.expire(subject, @bucket_expiry)
  end.first
end

#count(subject, interval) ⇒ Integer

Returns the count for a given subject and interval

Parameters:

  • subject (String)

    Subject for the count

  • interval (Integer)

    How far back (in seconds) to retrieve activity.

Returns:

  • (Integer)

    current count for subject



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/redis-throttler/base.rb', line 52

def count(subject, interval)
  bucket = get_bucket
  interval = [interval, @bucket_interval].max
  count = (interval / @bucket_interval).floor
  subject = "#{@key}:#{subject}"

  keys = (0..count - 1).map do |i|
    (bucket - i) % @bucket_count
  end
  redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i}
end

#exceeded?(subject, options = {}) ⇒ Boolean

Check if the rate limit has been exceeded.

Parameters:

  • subject (String)

    Subject to check

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :interval (Integer)

    How far back to retrieve activity.

  • :threshold (Integer)

    Maximum number of actions

Returns:

  • (Boolean)

    true if exceeded



72
73
74
# File 'lib/redis-throttler/base.rb', line 72

def exceeded?(subject, options = {})
  count(subject, options[:interval]) >= options[:threshold]
end

#exec_within_threshold(subject, options = {}) { ... } ⇒ Object

Execute a block once the rate limit is within bounds WARNING This will block the current thread until the rate limit is within bounds.

Examples:

Send an email as long as we haven't send 5 in the last 10 minutes

RedisThrottler.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
  send_another_email
end

Parameters:

  • subject (String)

    Subject for this rate limit

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :interval (Integer)

    How far back to retrieve activity.

  • :threshold (Integer)

    Maximum number of actions

Yields:

  • The block to be run



101
102
103
104
105
106
107
108
# File 'lib/redis-throttler/base.rb', line 101

def exec_within_threshold(subject, options = {}, &block)
  options[:threshold] ||= 30
  options[:interval] ||= 30
  while exceeded?(subject, options)
    sleep @bucket_interval
  end
  yield(self)
end

#within_bounds?(subject, options = {}) ⇒ Integer

Check if the rate limit is within bounds

Parameters:

  • subject (String)

    Subject to check

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :interval (Integer)

    How far back to retrieve activity.

  • :threshold (Integer)

    Maximum number of actions

Returns:

  • (Integer)

    true if within bounds



84
85
86
# File 'lib/redis-throttler/base.rb', line 84

def within_bounds?(subject, options = {})
  !exceeded?(subject, options)
end