Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt to new Hammer API #56

Merged
merged 12 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
inputs: ["{mix,.formatter}.exs", "{lib,test}/**/*.{ex,exs}"]
]
47 changes: 26 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ on:
- master

jobs:
setup:
test:
runs-on: ubuntu-latest
env:
MIX_ENV: test
# The hostname used to communicate with the Redis service container
REDIS_HOST: localhost

services:
redis:
# Docker Hub image
Expand All @@ -29,41 +28,47 @@ jobs:
--health-retries 5

strategy:
fail-fast: false
# https://hexdocs.pm/elixir/compatibility-and-deprecations.html#between-elixir-and-erlang-otp
matrix:
os: [ubuntu-22.04, ubuntu-20.04]
elixir_version: [1.12.3, 1.13.3, 1.14.1]
otp_version: [24, 25]
exclude:
- otp_version: 25
elixir_version: 1.12.3
elixir: [1.15, 1.16, 1.17]
otp: [25, 26]
include:
- elixir: 1.14
otp: 25
- elixir: 1.17
otp: 27

steps:
- uses: actions/checkout@v4

- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp_version}}
elixir-version: ${{matrix.elixir_version}}
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}

- uses: actions/cache@v4
with:
path: |
deps
_build
key: deps-${{ runner.os }}-${{ matrix.otp_version }}-${{ matrix.elixir_version }}-${{ hashFiles('**/mix.lock') }}
key: test-otp-${{ matrix.otp }}-elixir-${{ matrix.elixir }}-ref-${{ github.head_ref || github.ref }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
deps-${{ runner.os }}-${{ matrix.otp_version }}-${{ matrix.elixir_version }}
test-otp-${{ matrix.otp }}-elixir-${{ matrix.elixir }}-ref-${{ github.head_ref || github.ref }}-mix-
test-otp-${{ matrix.otp }}-elixir-${{ matrix.elixir }}-ref-refs/heads/master-mix-

- run: mix deps.get

- run: mix format --check-formatted

- run: mix deps.unlock --check-unused

- run: mix deps.compile

- run: mix compile --warnings-as-errors

- run: mix credo --strict --format=oneline
- run: mix test --warnings-as-errors --cover --include redis --include slow

- run: mix test --warnings-as-errors --cover
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
elixir-version: 1
otp-version: 27
- run: mix format --check-formatted
3 changes: 2 additions & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
elixir 1.13.1
elixir 1.17.3-otp-27
erlang 27.1.2
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 7.0.0-rc.0 (2024-12-06)

### Changed

- Conform to new Hammer API
ruslandoga marked this conversation as resolved.
Show resolved Hide resolved
- Remove Poolboy as it introduces unnecessary blocking.

## 6.2.0 (2024-12-04)

### Changed
Expand Down
58 changes: 26 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
# HammerBackendRedis
# Hammer.Redis

[![Build Status](https://github.com/ExHammer/hammer-backend-redis/actions/workflows/ci.yml/badge.svg)](https://github.com/ExHammer/hammer-backend-redis/actions/workflows/ci.yml) [![Hex.pm](https://img.shields.io/hexpm/v/hammer_backend_redis.svg)](https://hex.pm/packages/hammer_backend_redis) [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/hammer_backend_redis)
[![Build Status](https://github.com/ExHammer/hammer-backend-redis/actions/workflows/ci.yml/badge.svg)](https://github.com/ExHammer/hammer-backend-redis/actions/workflows/ci.yml)
[![Hex.pm](https://img.shields.io/hexpm/v/hammer_backend_redis.svg)](https://hex.pm/packages/hammer_backend_redis)
[![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/hammer_backend_redis)
[![Total Download](https://img.shields.io/hexpm/dt/hammer_backend_redis.svg)](https://hex.pm/packages/hammer_backend_redis)
[![License](https://img.shields.io/hexpm/l/hammer_backend_redis.svg)](https://github.com/ExHammer/hammer-backend-redis/blob/master/LICENSE.md)

A Redis backend for the [Hammer](https://github.com/ExHammer/hammer) rate-limiter.

This backend is a thin [Redix](https://hex.pm/packages/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](https://redis.io/learn/operate/redis-at-scale/talking-to-redis/client-performance-improvements#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](https://hexdocs.pm/redix/real-world-usage.html#name-based-pool) and also [PartitionSupervisor.](https://hexdocs.pm/elixir/1.17.3/PartitionSupervisor.html) 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](https://youtu.be/CRGPbCbRTHA?t=753).
In other sources it's sometimes called a "fixed window counter".

**TODO:** document ttl issues if servers are misconfigured

## Installation

Hammer-backend-redis
Expand All @@ -14,52 +23,37 @@ can be installed by adding `hammer_backend_redis` to your list of dependencies i

```elixir
def deps do
[{:hammer_backend_redis, "~> 6.1"},
{:hammer, "~> 6.0"}]
[
{:hammer_backend_redis, "~> 7.0"}
]
end
```

## Usage

Configure the `:hammer` application to use the Redis backend:
Define the rate limiter:

```elixir
config :hammer,
backend: {Hammer.Backend.Redis, [delete_buckets_timeout: 10_0000,
key_prefix: "my_application:rate_limiter",
expiry_ms: 60_000 * 60 * 2,
redix_config: [host: "localhost",
port: 6379]]}
defmodule MyApp.RateLimit do
use Hammer, backend: Hammer.Redis
end
```

(the `redix_config` arg is a keyword-list which is passed to
[Redix](https://hex.pm/packages/redix), it's also aliased to `redis_config`,
with an `s`)

Another option to configure Redis is to use the Redis Url format (see https://hexdocs.pm/redix/Redix.html#start_link/1-using-a-redis-uri) to configure Redis. If both options are specified
the redis_url will be used first.
And add it to your app's supervision tree:

```elixir
config :hammer,
backend: {Hammer.Backend.Redis, [delete_buckets_timeout: 10_0000,
key_prefix: "my_application:rate_limiter",
expiry_ms: 60_000 * 60 * 2,
redis_url: "redis://HOST:PORT"]}
children = [
{MyApp.RateLimit, url: "redis://localhost:6379"}
]
```

And that's it, calls to `Hammer.check_rate/3` and so on will use Redis to store
the rate-limit counters.

See the [Hammer Tutorial](https://hexdocs.pm/hammer/tutorial.html) for more.

## Documentation

On hexdocs: [https://hexdocs.pm/hammer_backend_redis/](https://hexdocs.pm/hammer_backend_redis/)
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](https://hexdocs.pm/hammer_backend_redis/Hammer.Redis.html) for more details.

## Run tests locally

You need a running Redis instance. One can be started locally using `docker-compose up -d`.
See the docker-compose.yml for more details on.
You need a running Redis instance. One can be started locally using `docker compose up -d redis`.
See the [compose.yml](./compose.yml) for more details.

## Getting Help

Expand Down
5 changes: 5 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
redis:
image: redis:latest
ports:
- 6379:6379
15 changes: 0 additions & 15 deletions config/config.exs

This file was deleted.

7 changes: 0 additions & 7 deletions docker-compose.yml

This file was deleted.

37 changes: 0 additions & 37 deletions guides/Overview.md

This file was deleted.

Loading
Loading