Ratelimit Ruby Library

repository·master·Indexed 42 days ago

https://github.com/ejfinneran/ratelimit

A Ruby library for distributed rate limiting across multiple servers using Redis. It allows developers to manage execution thresholds for specific subjects, such as users or API keys, providing methods to track activity counts, check if limits are exceeded, and execute code blocks within defined thresholds.

Tokens
1.7K
Snippets
10
Records
10
Agent score
4%

What's inside Ratelimit

  1. Connect to an external Redis instance

    master

    By default, Ratelimit connects to a Redis server at 127.0.0.1:6379. To use a remote or custom Redis server, instantiate a Redis client with your specific configuration and pass it to the Ratelimit.new constructor using the redis: keyword argument.

    redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
    ratelimit = Ratelimit.new("messages", redis: redis)
  2. Basic usage of Ratelimit

    master

    Initialize a Ratelimit instance with a unique name (e.g., for a specific type of action like "messages"). Use the add method to increment the count for a specific subject (like a phone number, email, or URL) and the count method to retrieve the number of executions for that subject within a specific time interval in seconds.

    ratelimit = Ratelimit.new("messages")
    5.times do
      ratelimit.add(phone_number)
    end
    ratelimit.count(phone_number, 30)
    # => 5
  3. Check if rate limits are exceeded or within bounds

    master

    You can check the current rate status using exceeded? or within_bounds?. Both methods require a subject, a threshold (maximum allowed executions), and an interval (time window in seconds).

    • exceeded?(subject, threshold: X, interval: Y): Returns true if the count for the subject in the given interval is greater than the threshold.
    • within_bounds?(subject, threshold: X, interval: Y): Returns true if the count is within the allowed threshold.
    ratelimit.exceeded?(phone_number, threshold: 10, interval: 30)
    # => false
    
    ratelimit.within_bounds?(phone_number, threshold: 10, interval: 30)
    # => true
  4. Execute code within a rate limit threshold

    master

    The exec_within_threshold method allows you to wrap code in a block that only executes if the rate limit has not been exceeded.

    Warning: This method is blocking; it will block execution until the block can be run (i.e., until the rate limit allows it). You must manually call ratelimit.add(subject) inside the block to record the execution.

    ratelimit.exec_within_threshold phone_number, threshold: 10, interval: 30 do
      some_rate_limited_code
      ratelimit.add(phone_number)
    end
  5. Initialize a Ratelimit object

    master

    To use Ratelimit, instantiate a new object with a unique key to identify the specific rate limit category (e.g., 'emails'). You can provide an optional options hash to configure the bucket behavior and the Redis connection.

    Options:

    • bucket_span (Integer): The total time span to track in seconds. Defaults to 600.
    • bucket_interval (Integer): The duration of each individual bucket in seconds. Defaults to 5.
    • bucket_expiry (Integer): How long data is kept in each bucket before auto-expiry. Cannot be larger than bucket_span. Defaults to @bucket_span.
    • redis (Redis): A Redis client instance. If not provided, a new Redis.new instance is used.

    Constraints:

    • bucket_expiry must be $\le$ bucket_span.
    • The resulting number of buckets (bucket_span / bucket_interval) must be at least 3.
    ratelimit = Ratelimit.new('emails', {
      bucket_span: 600,
      bucket_interval: 10,
      redis: Redis.new
    })
  6. Add to the rate limit counter with `add()`

    master

    Increment the counter for a specific subject (e.g., a user ID or email address) within the current time bucket.

    • subject (String): A unique identifier for the entity being rate-limited.
    • count (Integer): The amount to increase the counter by. Defaults to 1.

    Returns the new counter value for that subject.

    # Increment the count for 'user@example.com' by 1
    new_count = ratelimit.add('user@example.com')
  7. Execute a block within limits using `exec_within_threshold`

    master

    Executes a provided block only when the rate limit is within bounds.

    WARNING: This method is blocking. If the rate limit is exceeded, it will sleep for the duration of @bucket_interval and retry until the limit is no longer exceeded.

    • subject (String): The unique identifier.
    • options[:threshold] (Integer): Maximum actions allowed. Defaults to 30.
    • options[:interval] (Integer): Time window in seconds. Defaults to 30.

    Usage Pattern: Inside the block, you should perform your action and then manually call add(subject) to record the activity.

    ratelimit.exec_within_threshold('user@example.com', threshold: 5, interval: 600) do |rl|
      send_email()
      rl.add('user@example.com')
    end
    ratelimit.exec_within_threshold('user@example.com', threshold: 5, interval: 600) do |rl|
      send_another_email
      rl.add('user@example.com')
    end
  8. Check rate limit status with `exceeded?` and `within_bounds?`

    master

    Determine if a subject has surpassed a specific threshold within a given interval.

    exceeded?(subject, options = {})

    • subject (String): The unique identifier.
    • options[:interval] (Integer): How far back to look.
    • options[:threshold] (Integer): The maximum allowed actions.
    • Returns true if the count is $\ge$ threshold.

    within_bounds?(subject, options = {})

    • Returns the inverse of exceeded?.
    # Check if user has sent more than 5 emails in the last 10 minutes
    if ratelimit.exceeded?('user@example.com', interval: 600, threshold: 5)
      puts "Too many requests!"
    end
  9. Retrieve activity count with `count()`

    master

    Returns the total count for a given subject over a specific time interval.

    • subject (String): The unique identifier for the entity.
    • interval (Integer): How far back in seconds to retrieve activity. The interval is clamped between @bucket_interval and @bucket_span.

    Returns the sum of counts across all relevant buckets.

    # Get the count for 'user@example.com' over the last 60 seconds
    activity_count = ratelimit.count('user@example.com', 60)