ruby 77 lines · 2 tabs

API throttling with custom Redis-based limiter

Alex Kumar Jan 2026
2 tabs
class RateLimiter
  def initialize(key:, limit:, window:)
    @key = "rate_limit:#{key}"
    @limit = limit
    @window = window
  end

  def allowed?
    now = Time.current.to_i
    window_start = now - @window

    Redis.current.multi do |pipeline|
      # Remove old entries
      pipeline.zremrangebyscore(@key, '-inf', window_start)

      # Add current request
      pipeline.zadd(@key, now, "#{now}-#{SecureRandom.hex(8)}")

      # Count requests in window
      pipeline.zcard(@key)

      # Set expiry
      pipeline.expire(@key, @window + 1)
    end.last <= @limit
  end

  def remaining
    now = Time.current.to_i
    window_start = now - @window

    Redis.current.zremrangebyscore(@key, '-inf', window_start)
    count = Redis.current.zcard(@key)

    [@limit - count, 0].max
  end

  def reset_at
    now = Time.current.to_i
    window_start = now - @window

    oldest = Redis.current.zrange(@key, 0, 0, with_scores: true).first
    return now + @window unless oldest

    oldest[1].to_i + @window
  end
end
2 files · ruby Explain with highlit

While Rack::Attack handles basic rate limiting, custom throttling logic gives fine-grained control over quotas, burst allowances, and per-feature limits. I implement a token bucket algorithm in Redis using sorted sets to track request timestamps per user/IP. Each request checks if the bucket has capacity and adds a timestamp; old timestamps beyond the window are pruned. This approach supports burst tolerance—users can make several requests rapidly as long as their average rate stays within limits. I expose remaining quota and reset time in response headers (X-RateLimit-Remaining, X-RateLimit-Reset) so clients can self-regulate. Different endpoints or subscription tiers get different bucket sizes.


Related snips

Share this code

Here's the card — post it anywhere.

API throttling with custom Redis-based limiter — share card
Link copied