Skip to content

Add fixed-window counter rate limiting#30

Open
rapind wants to merge 4 commits intonootr:mainfrom
pairshaped:sliding-window
Open

Add fixed-window counter rate limiting#30
rapind wants to merge 4 commits intonootr:mainfrom
pairshaped:sliding-window

Conversation

@rapind
Copy link

@rapind rapind commented Mar 13, 2026

Summary

  • Adds glimit/window module with fixed-window counters and layered windows
  • Uses ETS with atomic update_counter for lock-free, concurrent operation
  • Unlike the token bucket (which smoothly refills tokens), fixed-window counters
    divide time into discrete windows and count requests. Ideal for:
    • Login/verification attempt limiting (e.g. 5 attempts per 15 minutes)
    • API rate limiting with clear reset boundaries
    • Layered limits (e.g. 1/min + 3/15min + 10/hour + 20/day)
  • 11 new tests, README updated with documentation

Note on storage

The window module uses its own dedicated ETS table with atomic update_counter
operations. It does not go through the pluggable bucket.Store interface. This
is intentional since fixed-window counters are typically a single-node pattern
and benefit from the atomicity guarantees of ETS. A pluggable store interface
for window counters could be added in the future if there's demand for
distributed fixed-window rate limiting.

Usage

let limiter = window.new()

let windows = [
  window.Window(window_seconds: 60, max_count: 1),
  window.Window(window_seconds: 900, max_count: 3),
  window.Window(window_seconds: 3600, max_count: 10),
]

case window.check(limiter, email, windows, now_seconds) {
  Ok(Nil) -> // allowed
  Error(window.Denied(retry_after)) -> // denied
}

Depends on

Test plan

  • All 11 new window tests pass
  • All 88 tests pass (existing + ETS store + window)

rapind added 4 commits March 13, 2026 08:52
ETS provides lower-latency rate limiting by using atomic table operations
directly, avoiding the overhead of OTP actor messages. Suitable for
single-node deployments where low latency is important.

- ets_store.gleam: EtsStore type implementing bucket.Store interface
- ets_store_ffi.erl: Erlang FFI for ETS operations (new/get/set/delete/sweep/size)
- glimit.ets_store() builder for easy integration
- Periodic sweep timer for cleaning up full and idle buckets
- 10 new tests covering all ETS store functionality
Document the new ETS-backed storage backend with usage example,
performance characteristics, and comparison to in-memory mode.
Replace the OTP actor-based in-memory store with ETS as the default.
ETS provides lower-latency, lock-free rate limiting without actor
message overhead. Remove memory_store module entirely — custom stores
can still be plugged in via glimit.store().
Introduces glimit/window module with layered fixed-window counters
using ETS atomic update_counter for lock-free, concurrent operation.

Unlike the token bucket algorithm (which smoothly refills tokens),
fixed-window counters divide time into discrete windows and count
requests within each. Useful for login attempts, verification codes,
and API rate limiting with clear reset boundaries.

- window.gleam: WindowLimiter type with check/reset/cleanup/get_count
- window_ffi.erl: Erlang FFI using ETS select_delete for cleanup
- Supports layered windows (e.g. 1/min + 3/15min + 10/hour + 20/day)
- 11 tests covering all window functionality
@rapind
Copy link
Author

rapind commented Mar 15, 2026

Updated to reflect that ETS is the new default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant