Hammer.Redis
A Redis backend for the Hammer rate-limiter.
This backend is a thin Redix wrapper. A single connection is used per rate-limiter. It should be enough for most use-cases since packets for rate limiting requests are short (i.e. no head of line blocking) and Redis is OK with pipelining (i.e. we don't block awaiting replies). Consider benchmarking before introducing more connections since TCP performance might be unintuitive. For possible pooling approaches, see Redix docs on pooling and also PartitionSupervisor. Do not use poolboy or db_connection-like pools since they practically disable pipelining which leads to worse connection utilisation and worse performance.
The algorithm we are using is the first method described (called "bucketing") in Rate Limiting with Redis. In other sources it's sometimes called a "fixed window counter".
Requirements
Redis 7.0 or later is required. The fixed window algorithm relies on the NX option of
EXPIREAT and the sliding window algorithm
relies on EXPIRETIME, both of which were
introduced in Redis 7.0.
On older Redis versions (which are end-of-life), these commands fail, so counter keys never receive a TTL and are never cleaned up. Rate limiting appears to work correctly, but the keyspace grows unbounded until Redis runs out of memory.
Installation
Hammer-backend-redis
is available in Hex, the package
can be installed by adding hammer_backend_redis to your list of dependencies in mix.exs:
def deps do
[
{:hammer_backend_redis, "~> 7.0"}
]
end
Usage
Define the rate limiter:
defmodule MyApp.RateLimit do
use Hammer, backend: Hammer.Redis
end
And add it to your app's supervision tree:
children = [
{MyApp.RateLimit, url: "redis://localhost:6379"}
]
And that's it, calls to MyApp.RateLimit.hit/3 and so on will use Redis to store
the rate-limit counters. See the documentation for more details.
Configuring SSL
Under the hood, Hammer.Redis uses Redix, which supports SSL connections. To configure SSL, pass the SSL options directly when starting the rate limiter. For example:
children = [
{MyApp.RateLimit,
[
host: "...",
port: 6379,
timeout: 5000,
ssl: true,
socket_opts: [
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
]},
# ...
]
Run tests locally
You need a running Redis instance. One can be started locally using docker compose up -d redis.
See the compose.yml for more details.
Getting Help
If you're having trouble, open an issue on this repo.