Class: RubyRollingRateLimiter
- Inherits:
-
Object
- Object
- RubyRollingRateLimiter
- Defined in:
- lib/ruby_rolling_rate_limiter.rb,
lib/ruby_rolling_rate_limiter/errors.rb,
lib/ruby_rolling_rate_limiter/version.rb
Defined Under Namespace
Modules: Errors
Constant Summary collapse
- VERSION =
"0.1.1"
Instance Attribute Summary collapse
-
#current_error ⇒ Object
readonly
Your code goes here…
Instance Method Summary collapse
- #can_call_proceed?(call_size = 1) ⇒ Boolean
-
#initialize(limiter_identifier, interval_in_seconds, max_calls_per_interval, min_distance_between_calls_in_seconds = 1, redis_connection = $redis) ⇒ RubyRollingRateLimiter
constructor
A new instance of RubyRollingRateLimiter.
- #set_call_identifier(id) ⇒ Object
Constructor Details
#initialize(limiter_identifier, interval_in_seconds, max_calls_per_interval, min_distance_between_calls_in_seconds = 1, redis_connection = $redis) ⇒ RubyRollingRateLimiter
Returns a new instance of RubyRollingRateLimiter.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/ruby_rolling_rate_limiter.rb', line 11 def initialize(limiter_identifier, interval_in_seconds, max_calls_per_interval, min_distance_between_calls_in_seconds = 1, redis_connection = $redis) @limiter_identifier = limiter_identifier @interval_in_seconds = interval_in_seconds @max_calls_per_interval = max_calls_per_interval @min_distance_between_calls_in_seconds = min_distance_between_calls_in_seconds @redis_connection = redis_connection #Check to ensure args are good. validate_arguments # Check Redis is there check_redis_is_available end |
Instance Attribute Details
#current_error ⇒ Object (readonly)
Your code goes here…
9 10 11 |
# File 'lib/ruby_rolling_rate_limiter.rb', line 9 def current_error @current_error end |
Instance Method Details
#can_call_proceed?(call_size = 1) ⇒ Boolean
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/ruby_rolling_rate_limiter.rb', line 32 def can_call_proceed?(call_size = 1) if call_size > @max_calls_per_interval @current_error = {code: 0, result: false, error: "Call size too big. Max calls in rolling window is: #{@max_calls_per_interval}. Increase your max_calls_per_interval or decrease your call_size", retry_in: 0} return false end results = false now = DateTime.now.strftime('%s%6N').to_i # Time since EPOC in microseconds. interval = @interval_in_seconds * 1000 * 1000 # Inteval in microseconds key = "#{self.class.name}-#{@limiter_identifier}-#{@id}" clear_before = now - interval # Begin multi redis @redis_connection.lock("#{key}-lock") do |lock| @redis_connection.multi @redis_connection.zremrangebyscore(key, 0, clear_before.to_s) @redis_connection.zrange(key, 0, -1) cur = @redis_connection.exec if (cur[1].count <= @max_calls_per_interval) && ((cur[1].count+call_size) <= @max_calls_per_interval) && ((@min_distance_between_calls_in_seconds * 1000 * 1000) && (now - cur[1].last.to_i) > (@min_distance_between_calls_in_seconds * 1000 * 1000)) @redis_connection.multi @redis_connection.zrange(key, 0, -1) call_size.times do @redis_connection.zadd(key, now.to_s, now.to_s) end @redis_connection.expire(key, @interval_in_seconds) results = @redis_connection.exec else results = [cur[1]] end end if results call_set = results[0] too_many_in_interval = call_set.count >= @max_calls_per_interval time_since_last_request = (@min_distance_between_calls_in_seconds * 1000 * 1000) && (now - call_set.last.to_i) if too_many_in_interval @current_error = {code: 1, result: false, error: "Too many requests", retry_in: (call_set.first.to_i - now + interval) / 1000 / 1000, retry_in_micro: (call_set.first.to_i - now + interval)} return false elsif (call_set.count+call_size) > @max_calls_per_interval @current_error = {code: 2, result: false, error: "Call Size too big for available access, trying to make #{call_size} with only #{call_set.count} calls available in window", retry_in: (call_set.first.to_i - now + interval) / 1000 / 1000, retry_in_micro: (call_set.first.to_i - now + interval)} return false elsif time_since_last_request < (@min_distance_between_calls_in_seconds * 1000 * 1000) @current_error = {code: 3, result: false, error: "Attempting to thrash faster than the minimal distance between calls", retry_in: @min_distance_between_calls_in_seconds, retry_in_micro: (@min_distance_between_calls_in_seconds * 1000 * 1000)} return false end return true end return false end |
#set_call_identifier(id) ⇒ Object
27 28 29 30 |
# File 'lib/ruby_rolling_rate_limiter.rb', line 27 def set_call_identifier(id) raise Errors::ArgumentInvalid, "The id must be a string or number with length greater than zero" unless id.length > 0 @id = id end |