0.06
Repository is archived
No commit activity in last 3 years
No release in over 3 years
Library for Redis-backed time-based rate limiting
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 10.0

Runtime

< 5, >= 3.0
 Project Readme

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.