Skip to content

Instantly share code, notes, and snippets.

@ncr
Last active February 5, 2024 18:21
Show Gist options
  • Save ncr/c2b1b62a8d2b636daca1dff12b457a31 to your computer and use it in GitHub Desktop.
Save ncr/c2b1b62a8d2b636daca1dff12b457a31 to your computer and use it in GitHub Desktop.
Simple Limiter based on individual keys instead of Redis hashes which are impossible to expire individually
require 'redis'
require 'redis-namespace'
class Limiter
def initialize(name:, threshold:, interval:, time_span: 600, bucket_span: 5)
@name, @threshold, @interval, @time_span, @bucket_span = name, threshold, interval, time_span, bucket_span
raise ArgumentError if @interval > @time_span || @interval < @bucket_span
@redis ||= Redis::Namespace.new(:limiter, redis: $redis || Redis.new)
@all_buckets_count = (@time_span / @bucket_span).floor
@sliding_window_buckets_count = (@interval.to_f / @bucket_span).floor
end
def add(count: 1)
key = [@name, get_bucket_index].join(":")
@redis.multi do
@redis.incrby(key, count)
@redis.expire(key, @interval)
end
nil
end
def count
current_bucket_index = get_bucket_index
bucket_indices = @sliding_window_buckets_count.times.map do |i|
(current_bucket_index - i) % @all_buckets_count
end
@redis.multi do
bucket_indices.map do |i|
key = [@name, i].join(":")
@redis.get(key)
end
end.map(&:to_i).sum
end
def exec_within_threshold
sleep @bucket_span while exceeded?
yield
end
def exceeded?
count >= @threshold
end
private
def get_bucket_index
((Time.now.to_i % @all_buckets_count) / @bucket_span).floor
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment