RateLimiterMan

View Source

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

Warning

This is probably not the most performant rate limiter out there, but it solves the need for which it was created: adding rate limiting to an application that receives responses from multiple third-party APIs, each of which has its own rate limiter instance/config.

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.4"}

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

Tip

Using the example above, the otp_app is :your_project, and the config_key is YourProject.SomeApi. These values are used to identify each rate limiter instance by the various functions in this package.

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 the task supervisor before adding any rate limiters. The task supervisor should only
        # be declared once
        RateLimiterMan.add_task_supervisor(),
        # Add the desired rate limiter(s). The OTP app name and config key must match the app name
        # and key used in your config file
        RateLimiterMan.add_rate_limiter(:your_project, YourProject.SomeApi),
        # RateLimiterMan.add_rate_limiter(:your_project, YourProject.SomeOtherApi),
        # RateLimiterMan.add_rate_limiter(:your_project, YourProject.YetAnotherApi)
      ]

    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