Pixelex.RateLimit (Pixelex v0.1.0)

Copy Markdown View Source

A fixed-window counter in ETS. Enough to bound an open endpoint, and no more.

POST /px/e is unauthenticated by necessity — most of the clicks worth counting happen before anyone signs in — so something has to stand between it and a script. Three things do, and none of them is auth: the per-site event allowlist, the consent gate, and this.

Why not a rate-limiting library

Because the requirement is "one number per IP per minute" and that is :ets.update_counter/4. Adding a dependency to every consumer's tree for twenty lines is the trade this library keeps declining.

Ceiling

A fixed window, not a sliding one, so a caller can send 2 × limit across a window boundary. For the job — separating a human from a script by three orders of magnitude — that is irrelevant. ponytail: fixed window; swap in a sliding log if pixelex ever needs to bill on this rather than bound it.

Per node, too: with three nodes behind a load balancer the effective limit is three times the configured one. Also fine, for the same reason.

Summary

Functions

Count one hit against key. {:allow, count} or {:deny, limit}.

Drop counters from windows that have passed. Cheap; call it on a timer.

Functions

hit(key, window_ms, limit)

@spec hit(String.t(), pos_integer(), pos_integer()) ::
  {:allow, pos_integer()} | {:deny, pos_integer()}

Count one hit against key. {:allow, count} or {:deny, limit}.

iex> Pixelex.RateLimit.hit("test:#{System.unique_integer()}", 60_000, 2)
{:allow, 1}

sweep(window_ms)

@spec sweep(pos_integer()) :: non_neg_integer()

Drop counters from windows that have passed. Cheap; call it on a timer.