Alumna::MemoryRateLimitStore
Inherits Alumna::RateLimitStore < Reference < Object
In-process rate-limit map. Use one instance per process (single backend). Swap for a Redis RateLimitStore to share limits across processes.
Previous versions kept a Hash(String, Tuple) that was never pruned. Under sustained traffic with many unique keys (e.g. a DDoS), the store grew indefinitely because expired windows were reset but never deleted.
This version fixes that with three deliberate choices, aligned with Alumna's philosophy of simplicity, explicitness, and performance:
-
Bounded memory
- Each entry stores both a wall-clock
reset_at(for HTTP headers) and a monotonicdeadline(Time::Instant). - Once
deadlinepasses, the entry is useless. It is removed by an amortized sweep that runs every 1,024 hits inside the same Sync::Mutex. - No background fiber, no timers, no hidden state. Memory usage is proportional to keys seen in the last window, not total history.
- Each entry stores both a wall-clock
-
Monotonic expiry
- All decisions use
Time.instant(monotonic clock), making the limiter immune to NTP adjustments, DST, or manual clock changes. Time.utcis used only to computeX-RateLimit-Resetfor clients.
- All decisions use
-
Testability
sizeandprune_expiredare exposed solely for specs, enabling deterministic tests without sleeps.
Hot path remains O(1): one Hash lookup under a Sync::Mutex. Cleanup is O(N) but amortized and infrequent, keeping throughput comparable to Go/Rust implementations while staying fully explicit.