Skip to content

yuval-po/token-bucket-rate-limiter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

token-bucket-rate-limiter

A Token-Bucket Rate Limiter library for Node.js

token-bucket-rate-limiter Package Version
Node Version Coverage Status License

This library is a Token-Bucket-based rate-limiter, suitable for use in both route and code levels. It's designed to be lightweight, modern, concise, easy to use and flexible.

Bucket based limiters are useful in managing load in resource constrained scenarios whilst providing QOS-like capabilities.

Every server must decide,
To throttle or let traffic ride.
Without a rate limiter's aid,
It may crash under the load's weight.
But with a limiter set just right,
Requests come in at a steady height.
Remember, it's key from the start,
Lest your server falls apart!

Short poem by ChatGPT


Important note:

This library makes no attempt to support multithreaded scenarios.
The rate limiter implementation is not thread safe.


Installation

  • npm install token-bucket-rate-limiter
  • yarn add token-bucket-rate-limiter

Basic Usage

Imperative

The simplest form of usage is the imperative way- create a bucket instance and use it directly.

This approach is useful when you require custom behaviors not available on the built-in Decorator and Middleware implementations.

import { TokenBucket } from 'token-bucket-rate-limiter';
const bucket = new TokenBucket({ capacity: 100 });
export function rateLimitedAction(): void {
bucket.take(10);
// Do something
}


Decorator

The decorator approach is useful for when you'd like to enforce limits on class methods.
A prime example would be a Controller or Service class.

Please note: the built-in decorator implementation will attempt to automatically refund issued tokens.

If the rate-limited method returns a Promise, the decorator will automatically refund the issued ticket once the Promise has resolved.
If the method returns a non-Promise value, the decorator refunds the ticket immediately.
The behavior of the decorator is dependent on the refund configuration of the bucket used for rate limiting.

import { TokenBucket, RateLimit } from 'token-bucket-rate-limiter';
const bucket = new TokenBucket({ capacity: 100 });
// A service class with all methods using the same bucket (i.e. resource pool)
export class Service {
@RateLimit({ bucket, cost: 1 })
public lightWeightOperation(): void {
// Do something
}
@RateLimit({ bucket, cost: 10 })
public mediumWeightOperation(): void {
// Do something demanding
}
@RateLimit({ bucket, cost: 50 })
public async heavyWeightOperation(): Promise<void> {
// Do something very demanding
}
}


Middleware

Express middleware-based limitation is also available. It is a rather flexible approach as it can be specific or generic depending on its position in the middleware stack.

The middleware can also fulfil the role of a 'dumb' rate limiter by indiscriminantly routing all requests through it

import Express from 'express';
import { createRateLimitMiddleware, TokenBucket } from 'token-bucket-rate-limiter';
const bucket = new TokenBucket({ capacity: 100 });
function createServer() {
const app = Express();
// Three routes sharing the same bucket, i.e. resource pool
app.get('/light-weight-operation', createRateLimitMiddleware({ bucket, routeCost: 1 }));
app.get('/medium-weight-operation', createRateLimitMiddleware({ bucket, routeCost: 10 }));
app.get('/heavy-weight-operation', createRateLimitMiddleware({ bucket, routeCost: 50 }));
app.listen();
}


Configuration & Advanced Usage

The bucket's core is designed to be flexible enough to support most common use-cases. Below are some example configurations that can be used to customize its behavior.


Basic Configuration

The simplest configuration creates a bucket with a fixed capacity and no automatic drip:

import { TokenBucket } from 'token-bucket-rate-limiter';
const bucket = new TokenBucket({ capacity: 100 });


Auto-Drip Configuration

A common use case for token buckets is to periodically add (drip) tokens to the bucket at a fixed rate.
This can be accomplished using the automaticDrip configuration property:

import { TokenBucket } from 'token-bucket-rate-limiter';
import { Duration } from 'unitsnet-js';
const config = {
capacity: 100,
automaticDrip: {
enabled: true,
interval: Duration.fromSeconds(5),
tokens: 5
}
};
const bucket = new TokenBucket(config);

In this example, the bucket will automatically add 5 tokens every 5 seconds.


Refund Configuration

Token buckets can be configured to allow refunds of 'issued' tokens. This can be useful in situations where you'd like to 'return' the capacity to the server after an operation has concluded.

For example, you perform, on behalf of the user, a demanding database operation.

Say, for simplicity's sake, that the operation takes 100% of the server's capacity.
While you don't want multiple such operations to run concurrently, you may want to immediately 'refund' this capacity as soon as the operation is over.

The refund configuration property can be used to enable refunds:

import { TokenBucket } from 'token-bucket-rate-limiter';
import { Duration } from 'unitsnet-js';
const config = {
capacity: 100,
behavior: {
refund: {
enabled: true,
refundTicketsExpiry: Duration.fromMinutes(30)
}
}
};
const bucket = new TokenBucket(config);

In this example, the bucket is configured to allow refunds with a refund window of 30 minutes.


Auto-Refund Configuration

Token buckets can also be configured to automatically refund expired tokens. This can be useful in situations where the 'tail end' of operations is not fully visible, for lack of a better term.

Suppose you're protecting a proxy server. You know the request has a certain 'weight'. You route it to the target server and start polling for the operation's status.
At some point, you may lose connection with the target server, at which point you have a choice to make;
Do you consider this capacity 'lost' and allow the token bucket to refill slowly or do you choose a cutoff point at which you're reasonably certain the server, regardless of conditions, has either fulfilled or dropped the request?

Auto-refund is the latter, where you may state that an operation that takes more than 2 minutes, for instance is 'lost at sea' and its capacity can be immediately returned to the bucket.

Another possible (though somewhat dubious) use for this mechanism is to safeguard against developer errors. If a developer makes a mistake and an operation that should refund itself (i.e. call ITokenTicket.refund()) does not do so, you can experience rapid capacity loss.

The autoRefund configuration property can mitigate this, to some degree by ensuring token tickets are eventually reclaimed:

import { TokenBucket } from 'token-bucket-rate-limiter';
import { Duration } from 'unitsnet-js';
const config = {
capacity: 100,
behavior: {
refund: {
enabled: true,
refundTicketsExpiry: Duration.fromMinutes(30),
autoRefund: {
enabled: true
}
}
}
};
const bucket = new TokenBucket(config);

In this example, the bucket is configured to allow refunds with a refund window of 30 minutes and auto-refunds of expired tokens enabled.


Misc Usage Notes

Here's a list of a few things (in no particular order) to keep in mind, when using the library.

  • Bucket objects are not 'free'.
    Each contains a dedicated internal cache and timers.
    They're not 'expensive' objects but keep in mind that if you intend to create them by the thousands (you shouldn't need to) the resources will probably add up.

  • Expanding upon the previous point, buckets contain resources, such as timers, that must be freed.
    While the bucket and associated components are able to detect when they are no longer needed and free up those resources, you should still strive to call the bucket's dispose function to cleanly reclaim said resources.

  • The library is not designed for true multithreaded usage. If this scenario is of interest to you, please drop me a mail or contact me on GitHub.

  • The library is not meant for or tested on browsers. If you do end up somehow using it like that, please let me know!

  • This library uses weak-event (full disclosure, another library of mine) which requires Node.js version >= 14.6.
    While the parts of the library responsible for this requirements are not actually being used here, I've elected to leave it as is. If this is a a blocker for you, consider letting me know- this limitation isn't set in stone.

  • This package comes unbundled. There are no funky imports or any unusual tricks here so you can use it as-is or feed it to your bundler of choice.


Bucket Configuration Options Rundown


Config Option Type Description
bucketName string The bucket's display name
capacity number The bucket's capacity, i.e., the maximum number of tokens the bucket can hold
startEmpty boolean A boolean indicating whether the bucket should be empty upon construction.
By default, buckets are constructed 'full', that is, they initially hold the designated capacity
automaticDrip.enabled boolean A boolean indicating whether the bucket should periodically receive new tokens
automaticDrip.interval Duration The regular interval at which tokens should be dripped into the bucket
automaticDrip.tokens number The number of tokens to drip per interval
behavior.refund.enabled boolean A boolean indicating whether the bucket allows refunding of tickets
behavior.refund.refundTicketsExpiry Duration The duration for which token tickets are valid.
Expired tickets cannot be refunded.
If autoRefund is enabled, expired tickets are automatically 'reclaimed' by the bucket (i.e. the tokens are returned)
behavior.refund.autoRefund.enabled boolean A boolean indicating whether the bucket should enable auto-refunding of expired tickets.
This property is ignored if behavior.refund.enabled is false

Other notes

For any bugs, questions, suggestions or comments, feel free to hit me on my mail (I may take a while to notice) at yuval.pomer or open an issue at my GitHub

Feedback, positive or otherwise is appreciated and welcome.

Credit where credit's due, some of this documentation was generated using OpenAI's ChatGPT which saves quite a bit of hassle.


Changelog

Changelog may be found at the project's GitHub here

About

A Token Bucket Rate Limiter library for Node.js

Resources

License

Stars

Watchers

Forks

Packages

No packages published