Last active
February 5, 2024 18:21
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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