Ruby library for time-based rate limiting
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
lib Rename SimpleLimit to GCRALimit, update stress.rb (#19) Apr 30, 2018
scripts Rename SimpleLimit to GCRALimit, update stress.rb (#19) Apr 30, 2018
spec
test
.gitignore
.travis.yml [feature] Add RollingLimit and LifetimeLimit (#12) Oct 3, 2017
Gemfile Remove Gemfile.lock from version control. Jan 9, 2018
LICENSE.txt Add MIT license. Feb 27, 2015
README.md Document with YARD and bump version to 1.0.0. May 5, 2015
Rakefile
traffic_jam.gemspec

README.md

TrafficJam

Build Status Coverage Status

This is a library for enforcing rate limits across concurrent processes. This can be used to cap the number of actions that may be performed in a certain period of time. More generally, this can be used to enforce a cap on an integer amount that can be incremented or decremented. A limit consists of an action name, a maximum amount, and a period of time in seconds.

Instead of guaranteeing that the number of actions will never exceed the cap the given timeframe, the approach we take is to use a continuously regenerating limit. The amount remaining will constantly increase at a rate of max / period until it hits the cap. If, for example, the limit is 60 per minute, a user could increment by 60 at once, then increment by 1 per second forever without hitting the cap. As a consequence, this algorithm guarantees that the total amount incremented will be less than twice the limit in any given timeframe.

Usage

require 'traffic_jam'

TrafficJam.configure do |config|
  config.redis = Redis.new(url: REDIS_URL)
end

limit = TrafficJam::Limit.new(
  :requests_per_user,
  user.id,
  max: 3,
  period: 1 # seconds
)
limit.increment      # => true
limit.increment(2)   # => true
limit.increment      # => false
limit.increment!     # => raises TrafficJam::LimitExceededError

sleep 1

limit.increment(2)   # => true
limit.exceeded?      # => false
limit.exceeded?(2)   # => true

limit.used           # => 2
limit.remaining      # => 1

Configuration

TrafficJam configuration object can be accessed with TrafficJam.config or in a block like TrafficJam.configure { |config| ... }. Configuration options are:

redis (required): A Redis instance to store amounts used for each limit.

key_prefix (default: "traffic_jam"): The string prefixing all keys in Redis.

Registering limits

Fixed limits can be registered for a key if the cap does not change depending on the value. All instance methods are available on the class.

TrafficJam.configure do |config|
  config.register(:requests_per_user, 3, 1)
end

limit = TrafficJam.limit(:requests_per_user, "user1")
limit.increment(2)  # => true

TrafficJam.increment(:requests_per_user, "user1", 1)  # => true
TrafficJam.used(:requests_per_user, "user1")          # => 3

Changing cap for a limit

Given an instance of TrafficJam::Limit with a maximum cap and a period, the behavior is to increase the amount remaining at a rate of max / period since the last time increment was called for the given value. If the cap is defined on a per-value basis, it is good practice to call increment(0) if the limit changes.

For example:

user.requests_per_hour = 10

limit = TrafficJam::Limit.new(
  :requests_per_user, user.id,
  max: user.requests_per_hour, period: 60 * 60
)
limit.increment(8)  # => true

sleep 60

limit.increment(0)

user.requests_per_hour = 20
limit = TrafficJam::Limit.new(
  :requests_per_user, user.id,
  max: user.requests_per_hour, period: 60 * 60
)
limit.increment(8)  # => true

Running tests

The REDIS_URI environment variable can be set in tests, and defaults to redis://localhost:6379.

rake test

To run a performance/stress test, see the test/stress.rb script.