Class: Pecorino::Adapters::RedisAdapter
- Inherits:
-
BaseAdapter
- Object
- BaseAdapter
- Pecorino::Adapters::RedisAdapter
- Defined in:
- lib/pecorino/adapters/redis_adapter.rb
Overview
An adapter for storing Pecorino leaky buckets and blocks in Redis. It uses Lua to enforce atomicity for leaky bucket operations
Defined Under Namespace
Classes: RedisScript
Constant Summary collapse
- ADD_TOKENS_SCRIPT =
RedisScript.new("add_tokens_conditionally.lua")
Instance Method Summary collapse
-
#add_tokens(key:, capacity:, leak_rate:, n_tokens:) ⇒ Object
Adds tokens to the leaky bucket.
-
#add_tokens_conditionally(key:, capacity:, leak_rate:, n_tokens:) ⇒ Object
Adds tokens to the leaky bucket conditionally.
-
#blocked_until(key:) ⇒ Object
Returns the time until which a block for a given key is in effect.
-
#initialize(redis_connection_or_connection_pool, key_prefix: "pecorino") ⇒ RedisAdapter
constructor
A new instance of RedisAdapter.
-
#set_block(key:, block_for:) ⇒ Object
Sets a timed block for the given key - this is used when a throttle fires.
-
#state(key:, capacity:, leak_rate:) ⇒ Object
Returns the state of a leaky bucket.
Methods inherited from BaseAdapter
Constructor Details
#initialize(redis_connection_or_connection_pool, key_prefix: "pecorino") ⇒ RedisAdapter
Returns a new instance of RedisAdapter.
30 31 32 33 |
# File 'lib/pecorino/adapters/redis_adapter.rb', line 30 def initialize(redis_connection_or_connection_pool, key_prefix: "pecorino") @redis_pool = redis_connection_or_connection_pool @key_prefix = key_prefix end |
Instance Method Details
#add_tokens(key:, capacity:, leak_rate:, n_tokens:) ⇒ Object
Adds tokens to the leaky bucket. The return value is a tuple of two values: the current level (Float) and whether the bucket is now at capacity (Boolean)
43 44 45 46 47 48 49 50 |
# File 'lib/pecorino/adapters/redis_adapter.rb', line 43 def add_tokens(key:, capacity:, leak_rate:, n_tokens:) keys = ["#{@key_prefix}:leaky_bucket:#{key}:level", "#{@key_prefix}:leaky_bucket:#{key}:last_touched"] argv = [leak_rate, n_tokens, capacity, _conditional = 0] decimal_float_level, at_capacity_int, _ = with_redis do |redis| ADD_TOKENS_SCRIPT.load_and_eval(redis, keys, argv) end [decimal_float_level.to_f, at_capacity_int == 1] end |
#add_tokens_conditionally(key:, capacity:, leak_rate:, n_tokens:) ⇒ Object
Adds tokens to the leaky bucket conditionally. If there is capacity, the tokens will be added. If there isn’t - the fillup will be rejected. The return value is a triplet of the current level (Float), whether the bucket is now at capacity (Boolean) and whether the fillup was accepted (Boolean)
56 57 58 59 60 61 62 63 |
# File 'lib/pecorino/adapters/redis_adapter.rb', line 56 def add_tokens_conditionally(key:, capacity:, leak_rate:, n_tokens:) keys = ["#{@key_prefix}:leaky_bucket:#{key}:level", "#{@key_prefix}:leaky_bucket:#{key}:last_touched"] argv = [leak_rate, n_tokens, capacity, _conditional = 1] decimal_float_level, at_capacity_int, did_accept_int = with_redis do |redis| ADD_TOKENS_SCRIPT.load_and_eval(redis, keys, argv) end [decimal_float_level.to_f, at_capacity_int == 1, did_accept_int == 1] end |
#blocked_until(key:) ⇒ Object
Returns the time until which a block for a given key is in effect. If there is no block in effect, the method should return ‘nil`. The return value is either a `Time` or `nil`
78 79 80 81 82 83 84 |
# File 'lib/pecorino/adapters/redis_adapter.rb', line 78 def blocked_until(key:) seconds_from_epoch = with_redis do |r| r.get("#{@key_prefix}:leaky_bucket:#{key}:block") end return unless seconds_from_epoch Time.at(seconds_from_epoch.to_f).utc end |
#set_block(key:, block_for:) ⇒ Object
Sets a timed block for the given key - this is used when a throttle fires. The return value is not defined - the call should always succeed.
67 68 69 70 71 72 73 74 |
# File 'lib/pecorino/adapters/redis_adapter.rb', line 67 def set_block(key:, block_for:) raise ArgumentError, "block_for must be positive" unless block_for > 0 blocked_until = Time.now + block_for with_redis do |r| r.setex("#{@key_prefix}:leaky_bucket:#{key}:block", block_for.to_f.ceil, blocked_until.to_f) end blocked_until end |
#state(key:, capacity:, leak_rate:) ⇒ Object
Returns the state of a leaky bucket. The state should be a tuple of two values: the current level (Float) and whether the bucket is now at capacity (Boolean)
37 38 39 |
# File 'lib/pecorino/adapters/redis_adapter.rb', line 37 def state(key:, capacity:, leak_rate:) add_tokens(key: key, capacity: capacity, leak_rate: leak_rate, n_tokens: 0) end |