Build limits with GlobalRateLimiter.Limit.new/2, which validates its
options. Do not construct the %GlobalRateLimiter.Limit{} struct literal
directly.
:window is a positive number of milliseconds; :limit is a non-negative
maximum count.
The resource may be any Erlang term. Include every dimension that needs an
independent limit, such as an action and user, tenant, API key, or IP
address.
Keep resource terms deterministic across nodes. Do not include a PID,
reference, or node-local value in a resource intended to be cluster-wide.
The whole limit — resource, window, and limit — identifies the tracked
consumption group. The same resource with a different window or limit is
tracked independently, and changing a limit's window or limit value starts a
fresh consumption history. Use stable values for each logical limiter.
Use GlobalRateLimiter.try_consume/2 when the operation must either start
immediately or be rejected. It returns {:ok, remaining},
{:error, {:rate_limited, retry_after_ms}} when current consumption leaves
insufficient capacity, or {:error, :count_exceeds_limit} when the
requested count can never fit.
Use GlobalRateLimiter.consume/3 when the caller may block until capacity
is available. Prefer a finite timeout in request-handling processes.
A successful call has already consumed capacity when it returns
{:ok, remaining}. Do not call try_consume/2 again after a successful
consume.
Do not implement check-then-consume logic with remaining/1. It is an
observational snapshot; try_consume/2 and consume/3 perform the atomic
capacity check.
Counts must be positive. A count greater than the limit always returns
{:error, :count_exceeds_limit}.
Waiting callers are monitored. If a caller exits while queued, its entry is
removed without consuming capacity.
A timeout leaves no reservation behind. A timeout of 0 succeeds only when
capacity is immediately available. There is no default timeout; pass
:infinity explicitly to wait without a deadline.
FIFO ordering applies only to waiters for the same limit on one node.
Immediate consumers and waiters on other nodes may overtake them.
retry_after_ms is a hint from the current cluster snapshot, not a
reservation. Another caller may consume the capacity first.
Distribution and operational limits
GlobalRateLimiter starts with the application; do not manually start its
:pg scope, registry, supervisor, counters, or waiters.
Cluster-wide enforcement requires connected Erlang nodes. Consumption is
coordinated with :global and discovered through :pg.
State is in memory. Node or application restarts discard that node's
counters, and a network partition allows each partition to enforce its own
view of the limit.
GlobalRateLimiter is intended for low-traffic keys. Do not use it as a
high-throughput or durable distributed quota system.