PushX.RateLimiter (PushX v0.12.0)

Copy Markdown View Source

Client-side rate limiting for push notifications.

Prevents exceeding provider rate limits by tracking requests locally. Uses a fixed-window counter (windows aligned to the clock, counters bumped with a single atomic ETS operation), so concurrent senders can never race the check-then-increment or clobber each other's window resets. Client-side limiting is inherently best-effort: it bounds what this node sends, and the true arbiter is always the provider.

Configuration

config :pushx,
  rate_limit_enabled: true,
  rate_limit_apns: 5000,      # requests per window
  rate_limit_fcm: 5000,       # requests per window
  rate_limit_window_ms: 1000  # 1 second window

Usage

Rate limiting is automatically applied when enabled. You can also check manually:

case PushX.RateLimiter.check(:apns) do
  :ok -> # Proceed with sending
  {:error, :rate_limited} -> # Back off
end

How It Works

  1. Time is divided into fixed windows of rate_limit_window_ms
  2. Each key (provider or named instance) gets one atomic counter per window
  3. When the counter passes the limit, further requests are rejected
  4. A new window starts a fresh counter; old windows are swept periodically

Summary

Functions

Checks if a request would be allowed without incrementing.

Checks if a request can be made and increments the counter.

Returns a specification to start this module under a supervisor.

Returns the number of requests recorded in the current window for a key.

Returns the configured limit for a provider.

Returns remaining requests before rate limit is hit.

Resets the rate limiter for a key. Useful for testing.

Resets all rate limiters.

Starts the rate limiter process.

Types

key()

@type key() :: atom()

provider()

@type provider() :: :apns | :fcm

Functions

check(provider)

@spec check(provider()) :: :ok | {:error, :rate_limited}

Checks if a request would be allowed without incrementing.

check_and_increment(provider)

@spec check_and_increment(provider()) :: :ok | {:error, :rate_limited}

Checks if a request can be made and increments the counter.

Returns :ok if under the limit, {:error, :rate_limited} if over.

The two-arity form counts under an arbitrary key (e.g. a named-instance atom) while taking the limit from provider's config — each instance has its own credentials and therefore its own provider-side budget.

check_and_increment(key, provider)

@spec check_and_increment(key(), provider()) :: :ok | {:error, :rate_limited}

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

current_count(key)

@spec current_count(key()) :: non_neg_integer()

Returns the number of requests recorded in the current window for a key.

Counts attempts, including ones that were rejected over the limit.

limit(atom)

@spec limit(provider()) :: pos_integer()

Returns the configured limit for a provider.

remaining(provider)

@spec remaining(provider()) :: non_neg_integer()

Returns remaining requests before rate limit is hit.

reset(key)

@spec reset(key()) :: :ok

Resets the rate limiter for a key. Useful for testing.

reset_all()

@spec reset_all() :: :ok

Resets all rate limiters.

start_link(opts \\ [])

Starts the rate limiter process.