class

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:

  1. Bounded memory

    • Each entry stores both a wall-clock reset_at (for HTTP headers) and a monotonic deadline (Time::Instant).
    • Once deadline passes, 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.
  2. Monotonic expiry

    • All decisions use Time.instant (monotonic clock), making the limiter immune to NTP adjustments, DST, or manual clock changes.
    • Time.utc is used only to compute X-RateLimit-Reset for clients.
  3. Testability

    • size and prune_expired are 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.

Constructors

new(window : Time::Span, cleanup_every : Int32 = 1024)
Source

Instance methods

hit(key : String) : Tuple(Int32, Time)

Returns {current_count, reset_at_utc}

Source
prune_expired

Exposed an instant-based cleanup for future specs, not used by the Rule

Source
size

Exposed for specs, not used by the Rule

Source

Nested types