Class: Labkit::RateLimit::Evaluator Private
- Inherits:
-
Object
- Object
- Labkit::RateLimit::Evaluator
- Defined in:
- lib/labkit/rate_limit/evaluator.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Evaluator holds the static parts of a rate limit check (name, rules, Redis) and exposes a per-request #check(identifier) method.
Defined Under Namespace
Classes: RuleCursor
Constant Summary collapse
- REDIS_KEY_PREFIX =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
"labkit:rl"- CHAR_VALUE_MAX_LENGTH =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
200- MISSING_VALUE_SENTINEL =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
"_unknown_"- INCR_SCRIPT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Atomic increment-with-TTL Lua script. The whole script runs as one operation from Redis's perspective, so there is no window between the increment and EXPIRE that can leak a key without TTL.
INCRBYFLOAT serves both count-mode (cost=1, equivalent to INCR for integer-encoded keys) and cost-mode callers, so a single script handles every rule shape. cost=0 also flows through INCRBYFLOAT; Redis treats the result as a no-op on the stored value while still observing the post-state count and TTL we return.
ttl_after < 0 covers TTL=-2 (key missing) and TTL=-1 (no expiry). The -1 case shouldn't arise with the atomic script, but self-healing recovers keys left without TTL by any prior bug.
Labkit::Redis::Script.new(<<~LUA) local ttl = ARGV[1] local cost = tonumber(ARGV[2]) local count = redis.call('INCRBYFLOAT', KEYS[1], cost) local ttl_after = redis.call('TTL', KEYS[1]) if ttl_after < 0 then redis.call('EXPIRE', KEYS[1], ttl) ttl_after = tonumber(ttl) end return {count, ttl_after} LUA
- SADD_SCRIPT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Atomic SADD + SCARD + conditional EXPIRE. SET-cardinality counterpart of INCR_SCRIPT; same shape (mutate, read TTL, set TTL when missing, return post-state TTL). count is SCARD, not the SADD return.
ttl_after < 0 covers TTL=-2 (key missing) and TTL=-1 (no expiry), so this also self-heals orphan keys left without TTL.
Labkit::Redis::Script.new(<<~LUA) local ttl = ARGV[1] local member = ARGV[2] redis.call('SADD', KEYS[1], member) local count = redis.call('SCARD', KEYS[1]) local ttl_after = redis.call('TTL', KEYS[1]) if ttl_after < 0 then redis.call('EXPIRE', KEYS[1], ttl) ttl_after = tonumber(ttl) end return {count, ttl_after} LUA
Instance Method Summary collapse
- #check(identifier, cost: 1, rule_context: nil) ⇒ Object private
-
#initialize(name:, rules:, redis:, logger:) ⇒ Evaluator
constructor
private
A new instance of Evaluator.
-
#peek(identifier, rule_context: nil) ⇒ Object
private
Read-without-increment counterpart to #check.
Constructor Details
#initialize(name:, rules:, redis:, logger:) ⇒ Evaluator
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of Evaluator.
69 70 71 72 73 74 |
# File 'lib/labkit/rate_limit/evaluator.rb', line 69 def initialize(name:, rules:, redis:, logger:) @name = name @rules = rules @redis = redis @logger = logger end |
Instance Method Details
#check(identifier, cost: 1, rule_context: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
76 77 78 79 80 81 82 83 84 85 |
# File 'lib/labkit/rate_limit/evaluator.rb', line 76 def check(identifier, cost: 1, rule_context: nil) cursor = RuleCursor.new check_rules(identifier, cost, rule_context, cursor) rescue StandardError => e # Intentionally broad: fail-open applies to any unexpected error (network, # timeout, OOM) not only Redis protocol errors. report_error_metrics log_error(e, identifier, cursor.rule) Result.error end |
#peek(identifier, rule_context: nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Read-without-increment counterpart to #check. Same matching and Result shape; the underlying Redis counter is not mutated and the TTL is not extended. A missing Redis key is treated as count=0 (matched, not exceeded).
90 91 92 93 94 95 96 97 |
# File 'lib/labkit/rate_limit/evaluator.rb', line 90 def peek(identifier, rule_context: nil) cursor = RuleCursor.new peek_rules(identifier, rule_context, cursor) rescue StandardError => e report_error_metrics log_error(e, identifier, cursor.rule) Result.error end |