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
@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}
@spec sweep(pos_integer()) :: non_neg_integer()
Drop counters from windows that have passed. Cheap; call it on a timer.