glimit
This module provides a distributed rate limiter that can be used to limit the number of requests or function calls per second for a given identifier.
A single actor is used to assign one rate limiter actor per identifier. The rate limiter actor then uses a Token Bucket algorithm to determine if a request or function call should be allowed to proceed. A separate process is polling the rate limiters to remove full buckets to reduce unnecessary memory usage.
The rate limits are configured using the following two options:
per_second
: The rate of new available tokens per second. Think of this as the steady state rate limit.burst_limit
: The maximum number of available tokens. Think of this as the burst rate limit. The default value is theper_second
rate limit.
The rate limiter can be applied to a function or handler using the apply
function, which returns a new function that checks the rate limit before
calling the original function.
Example
import glimit
let limiter =
glimit.new()
|> glimit.per_second(10)
|> glimit.burst_limit(100)
|> glimit.identifier(fn(request) { request.ip })
|> glimit.on_limit_exceeded(fn(_request) { "Rate limit reached" })
let handler =
fn(_request) { "Hello, world!" }
|> glimit.apply(limiter)
Types
A rate limiter.
pub type RateLimiter(a, b, id) {
RateLimiter(
rate_limiter_registry: RateLimiterRegistryActor(id),
on_limit_exceeded: fn(a) -> b,
identifier: fn(a) -> id,
)
}
Constructors
-
RateLimiter( rate_limiter_registry: RateLimiterRegistryActor(id), on_limit_exceeded: fn(a) -> b, identifier: fn(a) -> id, )
A builder for configuring the rate limiter.
pub type RateLimiterBuilder(a, b, id) {
RateLimiterBuilder(
per_second: Option(fn(id) -> Int),
burst_limit: Option(fn(id) -> Int),
identifier: Option(fn(a) -> id),
on_limit_exceeded: Option(fn(a) -> b),
)
}
Constructors
-
RateLimiterBuilder( per_second: Option(fn(id) -> Int), burst_limit: Option(fn(id) -> Int), identifier: Option(fn(a) -> id), on_limit_exceeded: Option(fn(a) -> b), )
Functions
pub fn apply(
func: fn(a) -> b,
config: RateLimiterBuilder(a, b, c),
) -> fn(a) -> b
Apply the rate limiter to a request handler or function.
Panics if the rate limiter registry cannot be started or if the identifier
function or on_limit_exceeded
function is missing.
pub fn apply_built(
func: fn(a) -> b,
limiter: RateLimiter(a, b, c),
) -> fn(a) -> b
Apply the rate limiter to a request handler or function.
This function is useful if you want to build the rate limiter manually using the
build
function.
pub fn build(
config: RateLimiterBuilder(a, b, c),
) -> Result(RateLimiter(a, b, c), String)
Build the rate limiter.
Note that using apply
will already build the rate limiter, so this function is
only useful if you want to build the rate limiter manually and apply it to multiple
functions.
To apply the resulting rate limiter to a function or handler, use the apply_built
function.
pub fn burst_limit(
limiter: RateLimiterBuilder(a, b, c),
burst_limit: Int,
) -> RateLimiterBuilder(a, b, c)
Set the maximum number of available tokens.
The maximum number of available tokens is the maximum number of requests that can be made in a single second. The default value is the same as the rate limit per second.
Example
import glimit
let limiter =
glimit.new()
|> glimit.per_second(10)
|> glimit.burst_limit(100)
pub fn burst_limit_fn(
limiter: RateLimiterBuilder(a, b, c),
burst_limit_fn: fn(c) -> Int,
) -> RateLimiterBuilder(a, b, c)
Set the maximum number of available tokens, based on the identifier.
Example
import glimit
let limiter =
glimit.new()
|> glimit.identifier(fn(request) { request.user_id })
|> glimit.per_second(10)
|> glimit.burst_limit_fn(fn(user_id) {
db.get_burst_limit(user_id)
})
pub fn identifier(
limiter: RateLimiterBuilder(a, b, c),
identifier: fn(a) -> c,
) -> RateLimiterBuilder(a, b, c)
Set the identifier function to be used to identify the rate limit.
Example
import glimit
let limiter =
glimit.new()
|> glimit.identifier(fn(request) { request.ip })
pub fn on_limit_exceeded(
limiter: RateLimiterBuilder(a, b, c),
on_limit_exceeded: fn(a) -> b,
) -> RateLimiterBuilder(a, b, c)
Set the handler to be called when the rate limit is reached.
Example
import glimit
let limiter =
glimit.new()
|> glimit.per_second(10)
|> glimit.on_limit_exceeded(fn(_request) { "Rate limit reached" })
pub fn per_second(
limiter: RateLimiterBuilder(a, b, c),
limit: Int,
) -> RateLimiterBuilder(a, b, c)
Set the rate of new available tokens per second.
Note that this is not the maximum number of requests that can be made in a single
second, but the rate at which tokens are added to the bucket. Think of this as the
steady state rate limit, while the burst_limit
function sets the maximum number of
available tokens (or the burst rate limit).
This value is also used as the default value for the burst_limit
function.
Example
import glimit
let limiter =
glimit.new()
|> glimit.per_second(10)
pub fn per_second_fn(
limiter: RateLimiterBuilder(a, b, c),
limit_fn: fn(c) -> Int,
) -> RateLimiterBuilder(a, b, c)
Set the rate limit per second, based on the identifier.
Example
import glimit
let limiter =
glimit.new()
|> glimit.identifier(fn(request) { request.user_id })
|> glimit.per_second_fn(fn(user_id) {
db.get_rate_limit(user_id)
})