RateLimiterMan

View Source

Warning

This is a very early release. It works but has some rough edges, and shouldn't be considered production-ready for most use cases. It is probably not the most performant implementation out there, but it solves the need for which it was created.

A simple rate limiter implementation, adapted from a blog post by Alex Koutmous.

Warning

Currently, only the leaky bucket rate limiter algorithm is implemented by this package.

This package supports multiple rate limiter instances in your application. Just follow the instructions, using a different config key for each rate limiter you want to add.

Getting started

Install the package

Add this package to your list of dependencies in mix.exs, then run mix deps.get:

{:rate_limiter_man, "0.2.0"}

Configure your application

Add the config for your rate limiter instance:

config/config.exs

import Config

config :your_project, YourProject.SomeApi,
  # Required items
  rate_limiter_algorithm: RateLimiterMan.LeakyBucket,
  rate_limiter_max_requests_per_second: 1
  # Optional items
  ## rate_limiter_logger_level: :debug # Enable logging when the rate limiter handles a request

Warning

The log statements may contain sensitive data (e.g. API keys). There is currently no way of modifying the contents of the Logger statement.

Add the rate limiter to your application's supervision tree

lib/your_project/application.ex


defmodule YourProject.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children =
      [
        # Add a task supervisor. This only needs to be added once:
        RateLimiterMan.start_task_supervisor(),
        # Add one of these lines for each rate limiter instance. The OTP app name
        # (`:your_project`) and config key (`YourProject.SomeApi`) must match the keys used in
        # your config file
        RateLimiterMan.start_rate_limiter(:your_project, YourProject.SomeApi)
      ]

    opts = [strategy: :one_for_one, name: YourProject.Supervisor]

    Supervisor.start_link(children, opts)
  end
end

Now, the rate limiter process will start working when you start your application: iex -S mix

Example: Use the rate limiter when making a request

Any function can be passed to the rate limiter. For simplicity, this example will not make any HTTP requests.

lib/your_project/some_api.ex

defmodule YourProject.SomeApi do
  def hello_world do
    request_id = System.unique_integer()

    # Make the request
    RateLimiterMan.make_request(
      _otp_app = :your_project,
      _config_key = YourProject.SomeApi,
      _request_handler = {Enum, :join, [["Hello", "world!"], " "]},
      send_response_to_pid: self(),
      request_id: request_id
    )

    # Receive the response
    response = RateLimiterMan.receive_response(request_id, _timeout = :timer.seconds(5))

    IO.puts(response)

    response
  end
end

Let's try it out. Start an interactive shell with iex -S mix:

iex> for _ <- 1..3, do: YourProject.SomeApi.hello_world()
Hello world!
Hello world!
Hello world!
["Hello world!", "Hello world!", "Hello world!"]

If you set your rate limiter with the config in the instructions, you should see the phrase "Hello world!" printed to the terminal 3 times, and the rate limiter ensured that only one "request" was handled every second.

If everything worked, you can now adapt the rate limiter for use in your application.

More information

Project documentation: https://hexdocs.pm/rate_limiter_man

Hex package: https://hex.pm/rate_limiter_man