Macaw.Plug.RateLimit (macaw_plug v0.1.4)

Copy Markdown View Source

Rate limiting by a generic cell rate algorithm (GCRA), in ETS.

Each key has one theoretical arrival time (TAT), advanced by one emission interval per request with compare-and-swap, so concurrent requests on one key can never both take the last slot. A limiter allows count requests per period on average, and bursts of up to burst.

Start the table once, in the host's supervision tree:

children = [Macaw.Plug.RateLimit.Table, ...]

Then limit with the plug, keyed by the client's address (after Macaw.Plug.ClientIp) unless told otherwise:

plug Macaw.Plug.RateLimit, id: :api, rate: {50, :second}, burst: 200

or call check/3 directly. Over the limit, the plug answers 429 with a Retry-After header and halts; :on_limit, a {module, function} taking the conn and the seconds to wait, answers instead (a JSON API's error shape, say).

Limiters share one table, each key stored as {id, key}. Keys idle long enough to have a full burst again are the same as absent, and the table's sweep removes them, so memory follows the active keys, not every key ever seen.

Summary

Functions

Takes a slot for key at now (monotonic microseconds, default the clock): :ok, or {:error, seconds_to_wait}, at least 1.

A limiter: id names it, rate: {count, :second | :minute | :hour} is its average, and burst (default count) the most requests it takes at once. Raises on a rate it can't honor, naming it.

Types

t()

@type t() :: %Macaw.Plug.RateLimit{
  id: term(),
  interval: pos_integer(),
  tolerance: non_neg_integer()
}

Functions

check(limiter, key, now \\ System.monotonic_time(:microsecond))

@spec check(t(), term(), integer()) :: :ok | {:error, pos_integer()}

Takes a slot for key at now (monotonic microseconds, default the clock): :ok, or {:error, seconds_to_wait}, at least 1.

new(id, opts)

@spec new(term(), keyword()) :: t()

A limiter: id names it, rate: {count, :second | :minute | :hour} is its average, and burst (default count) the most requests it takes at once. Raises on a rate it can't honor, naming it.