Rate Limiter

Available versions:

Installation

composer require aeon-php/rate-limiter

Description

RateLimiter components makes possible to limit frequency of operations execution.

Following example shows how to use Leaky Bucket algorithm with Rate Limiter. This configuration will let 5 immediate executions with restore rate 1 execution every minute. It's recommended to replace Symfony\Component\Cache\Adapter\ArrayAdapter (in memory) adapter used by PSRCacheStorage with a more permanent storage, like for example Redis.

<?php

$calendar = GregorianCalendar::UTC();

$rateLimiter = new RateLimiter(
    new LeakyBucketAlgorithm(
        $calendar,
        $bucketSize = 5,
        $leakSize = 1,
        $leakTime = TimeUnit::minute()
    ),
    new PSRCacheStorage(
        new ArrayAdapter(),
        $calendar
    )
);

$rateLimiter->hit('operation.id');
$rateLimiter->hit('operation.id');
$rateLimiter->hit('operation.id');
$rateLimiter->hit('operation.id');
$rateLimiter->hit('operation.id');
try {
    $rateLimiter->hit('operation.id');
} catch (RateLimitException $exception) {
    echo "Rate limit exceeded, please wait {$exception->retryIn()->inSecondsPrecise()} seconds";
}

// this will put process into sleep for next ~59 seconds
$rateLimiter->throttle('operation.id', SystemProcess::current());

Rate Limiter implements following limiting algorithms:

Rate Limiter implements following types of storage

  • MemoryStorage
  • PSRCacheStorage

It's worth to mention that PSRCacheStorage works perfectly fine with any caching library that implements PSR-6: Caching Interface. Most of available PSR-6 implementations can be found at packagist.

RateLimiter API is pretty much self-explanatory

<?php

namespace Aeon\RateLimiter;

final class RateLimiter
{
    /**
     * Record next hit, throws an extension where there are no available hits left according to the selected algorithm.
     *
     * @throws \Aeon\RateLimiter\Exception\RateLimitException
     */
    public function hit(string $id) : void;

    /**
     * Estimate time required to the next hit. If current capacity is greater than 0, time will be 0.
     */
    public function estimate(string $id) : TimeUnit;

    /**
     * Returns current capacity according to the selected algorithm, when there are no available hits left, it will return 0.
     * Use RateLimiter::estimate method to find out when next hit will be possible.
     */
    public function capacity(string $id) : int;

    /**
     * Initial available capacity before registering any hits or when all hits time out.
     */
    public function capacityInitial() : int;

    /**
     * Time required to fully reset to the total capacity.
     */
    public function resetIn(string $id) : TimeUnit;

    /**
     * Try to record next hit, in case of rate limit exception take the cooldown time and sleep current process.
     */
    public function throttle(string $id, Process $process) : void;
}

RateLimitException does not only tell you that the execution is forbidden, it also says how long you should wait.

<?php

    public function throttle(string $id, Process $process) : void
    {
        try {
            $this->algorithm->hit($id, $this->storage);
        } catch (RateLimitException $rateLimitException) {
            $process->sleep($rateLimitException->cooldown());
            $this->algorithm->hit($id, $this->storage);
        }
    }