livery_ratelimit_store (livery v0.2.0)
View SourceOwner and store for livery_ratelimit token buckets.
A supervised gen_server that owns one public named ETS table and reaps
idle buckets on a timer. The per-request token-bucket decision
(check/5) runs in the calling process directly against the public
table (lock-free CAS), so the gen_server is never on the hot path - it
only owns the table and runs cleanup. The table is public so requests
update buckets without serializing through the owner; this is safe
because Livery runs no untrusted in-VM code, and making it protected
would force every check through the owner and reintroduce exactly the
single-process bottleneck the lock-free design avoids.
Each row is {{Name, KeyDigest}, Tokens, LastMicros, Cap, Rate}.
KeyDigest is a SHA-256 of the rate-limit key, so raw bearer tokens are
never stored. Cap/Rate are denormalized so the sweep can compute
refill without the limiter config.
The table is bounded: once it holds ratelimit_max_keys rows
(application environment, default 1,000,000) new keys are shed
({deny, undefined}) rather than inserted, so a flood of distinct keys
cannot grow the table without limit. Idle buckets are reaped every
minute, so the bound is also released as load drops.
Summary
Functions
Token-bucket decision for {Name, KeyDigest}.
Reap fully-refilled buckets now; returns the number removed.
Types
-type result() :: {allow, float(), non_neg_integer() | undefined} | {deny, non_neg_integer() | undefined}.
-type state() :: #state{}.
Functions
Token-bucket decision for {Name, KeyDigest}.
Returns {allow, RemainingTokens, ResetSecs} (a token was consumed) or
{deny, RetryAfterSecs}. ResetSecs/RetryAfterSecs are undefined
when Rate =< 0 (no refill). Runs in the caller; lock-free.
-spec handle_call(term(), gen_server:from(), state()) -> {reply, term(), state()}.
-spec init([]) -> {ok, state()}.
-spec sweep() -> non_neg_integer().
Reap fully-refilled buckets now; returns the number removed.