Class: NbApiClient::RateLimiter
- Inherits:
-
Object
- Object
- NbApiClient::RateLimiter
- Defined in:
- lib/nb_api_client/rate_limiter.rb
Overview
A global (cross-process, cross-thread) distributed rate limiter backed by Redis. Implements a sliding-window algorithm in a Lua script (atomic ZADD/ZREMRANGEBYSCORE/ZCARD on a sorted set) to enforce "N requests per window" across every process sharing the same Redis. All callers share one window (configure via NbApiClient.configure).
Defined Under Namespace
Classes: RateLimitExhausted
Constant Summary collapse
- ACQUIRE_SLOT_SCRIPT =
- Remove all entries older than (now - window) to slide the window
- Count remaining entries in the set
- If under the limit, add a new entry (scored by timestamp) and return the new count
- If at/over the limit, return -1 (rejected)
The sorted set key is given a TTL of 2*window as a safety net to avoid unbounded memory growth if traffic stops entirely.
<<~LUA local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local now = tonumber(ARGV[3]) local member = ARGV[4] -- Prune entries outside the sliding window redis.call('ZREMRANGEBYSCORE', key, '-inf', now - window) local count = redis.call('ZCARD', key) if count < limit then redis.call('ZADD', key, now, member) -- Safety-net expiry so the key doesn't live forever if traffic stops redis.call('EXPIRE', key, window * 2) return count + 1 else return -1 end LUA
Class Method Summary collapse
-
.with_limit(&block) ⇒ Object
Blocks until a request slot is available, then yields to the caller.
Instance Method Summary collapse
-
#initialize ⇒ RateLimiter
constructor
A new instance of RateLimiter.
- #with_limit ⇒ Object
Constructor Details
#initialize ⇒ RateLimiter
Returns a new instance of RateLimiter.
46 47 48 |
# File 'lib/nb_api_client/rate_limiter.rb', line 46 def initialize @key = "nb_rate_limit:window" end |
Class Method Details
.with_limit(&block) ⇒ Object
Blocks until a request slot is available, then yields to the caller. Raises RateLimitExhausted if the wait exceeds configuration.rate_limit_max_wait_seconds.
42 43 44 |
# File 'lib/nb_api_client/rate_limiter.rb', line 42 def self.with_limit(&block) new.with_limit(&block) end |
Instance Method Details
#with_limit ⇒ Object
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 |
# File 'lib/nb_api_client/rate_limiter.rb', line 50 def with_limit config = NbApiClient.configuration waited = 0.0 loop do result = try_acquire_slot if result > 0 return yield else if waited >= config.rate_limit_max_wait_seconds raise RateLimitExhausted, "Rate limit exhausted after waiting #{waited}s" end # Sleep with jitter to spread retries and avoid thundering herd sleep_time = config.rate_limit_poll_interval + rand(0.0..config.rate_limit_max_jitter) sleep(sleep_time) waited += sleep_time end end rescue ConnectionPool::TimeoutError, Redis::BaseError, RedisClient::Error => e # Fail open: if Redis is unavailable, allow the request through. # NbApiClient::Request's 429 handling acts as a safety net. config.logger&.warn("[NbApiClient::RateLimiter] Redis error, failing open: #{e.class} - #{e.}") yield end |