Rollie

Gem Version CI Code Quality Coverage Status Online docs

Rollie is a multi-purpose, fast, Redis backed rate limiter that can be used to limit requests to external APIs, in Rack middleware, etc. Rollie uses a dedicated Redis connection pool implemented using connection_pool for more efficient Redis connection management. The Redis algorithm was inspired by the rolling-rate-limiter node package.

The key implementation detail is that Rollie utilizes a rolling window to bucket invocations in. Meaning, if you set a limit of 100 per 30 seconds, Rollie will start the clock in instant it is first executed with a given key.

For example, first execution:

rollie = Rollie::RateLimiter.new("api", limit: 10, interval: 30000)
rollie.within_limit do
   puts Time.now
end
# => 2016-12-03 08:31:23.873

This doesn't mean the count is reset back to 0 at 2016-12-03 08:31:53.873. Its a continuous rolling count, the count is checked with every invocation over the last 30 seconds.

If you invoke this rate 9 times at 2016-12-03 08:31:53.500, you will only be able to make one more call until 2016-12-03 08:32:23.500.

Install

Add it to your Gemfile:

gem 'rollie'

Or install it manually:

gem install rollie

Usage

Rollie is simple to use and has only one method, within_limit. within_limit expects a block and that block will be executed only if you are within the limit.

Initialize Rollie with a key used to uniquely identify what you are limiting. Use the options to set the limit and interval in milliseconds.

# limit 30 requests per second.
twitter_rate = Rollie::RateLimiter.new("twitter_requests", limit: 30, interval: 1000)
status = twitter_rate.within_limit do
  twitter.do_something
end

The status will tell you the current state. You can also see the current count and how long until the bucket resets.

status.exceeded?
# => false
status.count
# => 1
status.time_remaining
# => 987 # milliseconds

Once exceeded:

status.exceeded?
# => true
status.count
# => 30
status.time_remaining
# => 461 # milliseconds

You can also use a namespace if you want to track multiple entities, for example users.

Rollie::RateLimiter.new(
  user_id,
  namespace: "user_messages",
  limit: 100,
  interval: 30000
)

Counting blocked actions

By default, blocked actions are not counted against the callee. This allows for the block to be executed within the rate even when there is a continuous flood of action. If you wish to change this behaviour, for example to require the callee to back off before being allowed to execute again, set this option to true.

request_rate = Rollie::RateLimiter.new(
  ip,
  namespace: "ip",
  limit: 30,
  interval: 1000,
  count_blocked: true
)

Configuration

By default Rollie will try to connect to Redis using ENV["REDIS_URL"] if set or fallback to localhost:6379. You can set an alternate Redis configuration:

Rollie.redis = {
    url: CONFIG[:redis_url],
    pool_size: 5,
    pool_timeout: 1,
    driver: :hiredis
}

If using rails, create an initializer config/initializers/rollie.rb with these settings.