npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redis-gcra

v0.5.2

Published

Rate limiting based on Generic Cell Rate Algorithm

Downloads

10,370

Readme

Node Redis GCRA Library

Build Status npm version

This module is an implementation of GCRA for rate limiting based on Redis.

Installation

npm install redis-gcra

or

yarn install redis-gcra

API Documentation

RedisGCRA({ redis, keyPrefix, burst, rate, period, cost })

const RedisGCRA = require('redis-gcra');

const limiter = RedisGCRA({
  redis: undefined,
  keyPrefix: '',
  burst: 60,
  rate: 1,
  period: 1000,
  cost: 1
});

The redis-gcra module exposes a single function, which returns a limiter instance when called. It takes the following options:

| Option | Type | Default | Description | | :----- | :--- | ------- | :---------- | | redis | ioredis or node-redis instance* | | Required. The Redis client to be used. (* If you use node-redis, additional setup work is required; see below. You may also use a Redis-client-like wrapper that exposes the defineCommand method and the Redis del method.) | | keyPrefix | String | | A prefix for any keys that this limiter instance tries to create/access. | | burst | Number | 60 | The default burst value for this limiter instance. If provided, must be a number greater than or equal to 1. | | rate | Number | 1 | The default rate value for this limiter instance. If provided, must be a number greater than or equal to 1. | | period | Number | 1000 | The default period value for this limiter instance (milliseconds). If provided, must be a number greater than or equal to 1. | | cost | Number | 1 | The default cost value for for this limiter instance. If provided, must be a number greater than or equal to 0. |

Instance Functions

limit({ key, burst, rate, period, cost })

limiter.limit({
  key: 'user/[email protected]',
  burst: 1000,
  rate: 1,
  period: 1000,
  cost: 2
});

In order to perform rate limiting, you need to call the limit method. This function will attempt to consume the given cost from the token pool for this key. If there are not enough tokens available for the given cost, no tokens will be consumed. It takes the following options:

| Option | Type | Description | | :----- | :--- | :---------- | | key | String | Required. The key to limit/throttle on. The actual Redis key will be prefixed with any keyPrefix given when the limiter was created. | | burst | Number | The maximum number of tokens available (i.e., token regeneration stops when this number is reached). If not provided, defaults to the burst value provided when the limiter was created. If provided, must be a number greater than or equal to 1. | | rate | Number | The rate at which tokens regenerate over the given period. If not provided, defaults to the rate value provided when the limiter was created. If provided, must be a number greater than or equal to 1. | | period | Number | The period (in milliseconds) over which tokens are regenerated at rate. If not provided, defaults to the period value provided when the limiter was created. If provided, must be a number greater than or equal to 1. | | cost | Number | The cost in tokens of this limit call. If not provided, defaults to the cost value provided when the limiter was created. If provided, must be a number greater than or equal to 0. |

The limit call returns a Promise, which will resolve to an object with the following properties:

| Property | Type | Description | | :------ | :--- | :---------- | | limited | Boolean | Represents whether the given limit request was fulfilled. | | remaining | Number | The number of tokens remaining after this limit request. A request may be limited even if there are tokens available, if the cost was higher than the tokens available. If limited is true, remaining will be the number of tokens currently available without the requested cost being subtracted. | | retryIn | Number | The number of milliseconds to wait until the given request would be allowed. Will always be 0 if limited is false, and greater than 0 if limited is true. If the cost was higher than burst, it is never possible to fulfill the request, and so retryIn will be Infinity. | | resetIn | Number | The number of milliseconds until the token pool has regenerated to burst. Will be 0 if the token pool is already at the burst value. |

peek({ key, burst, rate, period })

limiter.peek({
  key: 'user/[email protected]',
  burst: 1000,
  rate: 1,
  period: 1000
});

The peek function allows you to look at the given state of the token pool for the given key. It will not consume any tokens. It takes the following options:

| Option | Type | Description | | :----- | :--- | :---------- | | key | String | Required. The limiter key to peek at. The actual Redis key will be prefixed with any keyPrefix given when the limiter was created. | | burst | Number | The maximum number of tokens available (i.e., token regeneration stops when this number is reached). If not provided, defaults to the burst value provided when the limiter was created. If provided, must be a number greater than or equal to 1. | | rate | Number | The rate at which tokens regenerate over the given period. If not provided, defaults to the rate value provided when the limiter was created. If provided, must be a number greater than or equal to 1. | | period | Number | The period (in milliseconds) over which tokens are regenerated at rate. If not provided, defaults to the period value provided when the limiter was created. If provided, must be a number greater than or equal to 1. |

The peek call returns a Promise, which will resolve to an object with the following properties:

| Property | Type | Description | | :------- | :--- | :---------- | | limited | Boolean | Represents if the given key is currently limited. This will be true only if there are no tokens in the pool. | | remaining | Number | The number of tokens remaining in the given key's token pool. | | resetIn | Number | The number of milliseconds until the token pool has regenerated to burst. Will be 0 if the token pool is already at the burst value. |

reset

limiter.reset({
  key: 'user/[email protected]'
});

The reset function allows you to reset the token pool for the given key. In essence, this just deletes the relevant key in Redis, which means the key is immediately back to having burst tokens available. It takes the following options:

| Option | Type | Description | | :----- | :--- | :---------- | | key | String | Required. The limiter key to reset. The actual Redis key will be prefixed with any keyPrefix given when the limiter was created. |

The reset call returns a Promise, which will resolve to a Boolean. If the result is true, the key was reset. If the result is false, the key did not need to be reset; there were already burst tokens available.

Node-redis usage

To use this module with node-redis, you must include the include the exported RedisGCRA.defineNodeRedisScripts script as part of the scripts definition in your createClient configuration:

const Redis = require('redis');
const RedisGCRA = require('redis-gcra');

const redis = Redis.createClient({
      scripts: {
        ...RedisGCRA.defineNodeRedisScripts(Redis),
        /* other custom user scripts defined here */
      }
    });
const limiter = RedisGCRA({ redis });

(...)

If you would like to otherwise customize the provided script definition, you can also import the LUA and customize the script definition further (for example, for usage with Typescript).

const Redis = require('redis');
const RedisGCRA = require('redis-gcra');

const redis = Redis.createClient({
  scripts: {
    performGcraRateLimit: Redis.defineScript({
      NUMBER_OF_KEYS: 1,
      SCRIPT: RedisGCRA.GCRA_LUA,
      transformArguments(key, now, burst, rate, period, cost) {
        return [key, now.toString(), burst.toString(), rate.toString(), period.toString(), cost.toString()];
      },
      transformReply(reply) {
        return reply;
      }
    })
  }
});

const limiter = RedisGCRA({ redis });

(...)

Example

In this example the rate limit bucket has 1000 tokens, recovering at a speed of 1 token per second.

const Redis     = require('ioredis');
const RedisGCRA = require('redis-gcra');

const redis = new Redis();
const limiter = RedisGCRA({ redis });

// In order to perform rate limiting, you need to call the 'limit' method.
let result = await limiter.limit({
  key: 'user/[email protected]',
  burst: 1000,  // limit on maximum tokens available
  rate: 1,      // rate at which tokens regenerate per period
  period: 1000, // period, in milliseconds, for token regeneration
  cost: 2       // cost in tokens for this limit request
});

result.limited;   // => false - request should not be limited
result.remaining; // => 998   - remaining number of tokens until limited
result.retryIn;   // => 0     - can retry without delay
result.resetIn;   // => ~2000 - in approximately 2 seconds tokens will be regenerated to burst limit

// call limit 500 more times in rapid succession and the 500th call will have:
// result = await limiter.limit(....)
result.limited;   // => true    - request should be limited
result.remaining; // => 0       - remaining number of tokens until limited
result.retryIn;   // => 2000    - can retry in approximately 2 seconds
result.resetIn;   // => 1000000 - in approximately 1000 seconds tokens will be regenerated to burst limit

The implementation utilizes a single key in Redis that matches the key you pass to the limit method. If you need to reset the rate limiter for a particular key, call the reset method:

// Let's imagine 'user/[email protected]' is limited.
// This will effectively reset the limit for the key:
await limiter.reset({ key: 'user/[email protected]' })
// limit is reset

You can also retrieve the current state of the rate limiter for a particular key without actually modifying the state. In order to do that, use the peek method:

let result = await limiter.peek({
  key: 'user/[email protected]',
  burst: 1000,
  rate: 1,
  period: 1000
});

result.limited;   // => false - request should not be limited
result.remaining; // => 1000  - remaining number of tokens until limited
result.resetIn;   // => 0     - "burst" tokens are already available

Inspiration

This code was inspired by the Ruby gem redis-gcra.

License

The module is available as open source under the terms of the MIT License.