-
-
Notifications
You must be signed in to change notification settings - Fork 182
In memory Block Strategy
TL;DR: It makes your limiters at least 7 times faster.
It can be activated with setup inMemoryBlockOnConsumed and sometimes with inMemoryBlockDuration options.
If some actions consume inMemoryBlockOnConsumed points, a limiter starts using current process memory for blocking keys. Note, it works for consume method only, all other methods still request store.
In-memory Block Strategy can be used against DDoS attacks. Note, the most stable protection can be implemented on the infrastructure level.
If you don't have resources for that use Block Strategy. It will not protect your service from millions requests per second, however, it can protect from hundreds of thousands. Any limiter storage like Redis or Mongo provides in-memory block strategy to avoid too many requests to Storage.
Note, block in memory feature slightly postpones the moment of key is unblocked in memory. It depends on latency between your application and your store and overall Event Loop load. This inaccuracy is insignificant, since it usually takes a couple of milliseconds between result from store and block in memory event.
In-memory Block strategy algorithm developed with specificity rate limiter in mind:
- it doesn't use
setTimeoutto expire blocked keys, so doesn't overload Event Loop - blocked keys expired in two cases:
- if
keyis blocked, it launches collect of expired blocked keys. So it slows down only already blocked actions. - on adding a new blocked
key, when there are more than 999 blocked keys in total.
- if
There is simple Express 4.x endpoint,
which launched in node:10.5.0-jessie and redis:4.0.10-alpine Docker containers by PM2 with 4 workers
Note: Benchmark is done in local environment, so production will be much faster.
router.get('/', (req, res, next) => {
rateLimiter.consume((Math.floor(Math.random() * 5).toString()))
.then(() => {
res.status(200).json({}).end();
})
.catch(() => {
res.status(429).send('Too Many Requests').end();
});
});It creates 5 random keys.
It isn't real situation, but purpose is to show latency and calculate how it helps to avoid too many requests to Redis.
The same benchmarking setting for both tests:
bombardier -c 1000 -l -d 30s -r 2000 -t 1s http://127.0.0.1:3000
- 1000 concurrent requests
- test duration is 30 seconds
- not more than 2000 req/sec
5 points per second to consume
const rateLimiter = new RateLimiterRedis(
{
redis: redisClient,
points: 5,
duration: 1,
},
);Result:
Statistics Avg Stdev Max
Reqs/sec 1999.05 562.96 11243.01
Latency 7.29ms 8.71ms 146.95ms
Latency Distribution
50% 5.25ms
75% 7.20ms
90% 11.61ms
95% 18.73ms
99% 52.78ms
HTTP codes:
1xx - 0, 2xx - 750, 3xx - 0, 4xx - 59261, 5xx - 0
5 points per second to consume
When 5 points consumed, a key is blocked in current process memory to avoid further requests to Redis in the current duration window.
const rateLimiter = new RateLimiterRedis(
{
redis: redisClient,
points: 5,
duration: 1,
inMemoryBlockOnConsumed: 5,
},
);Result:
Statistics Avg Stdev Max
Reqs/sec 2011.39 390.04 3960.42
Latency 1.11ms 0.88ms 23.91ms
Latency Distribution
50% 0.97ms
75% 1.22ms
90% 1.48ms
95% 1.91ms
99% 5.90ms
HTTP codes:
1xx - 0, 2xx - 750, 3xx - 0, 4xx - 59268, 5xx - 0
- Seven times faster!
- Less requests to a store (59k less in this case during 30 seconds)
Get started
Middlewares and plugins
Migration from other packages
Limiters:
- Cluster
- Drizzle
- DynamoDB
- Etcd
- Memcached
- Memory
- MongoDB (with sharding support)
- MySQL
- PM2 Cluster
- PostgreSQL
- Prisma
- Redis
- SQLite
- Valkey: iovalkey and Valkey Glide
- BurstyRateLimiter
- RateLimiterUnion
- RateLimiterQueue
Wrappers:
- AWS SDK v3 Client Rate Limiter
- RLWrapperBlackAndWhite Black and White lists
- RLWrapperTimeouts Timeouts
Knowledge base:
- Block Strategy in memory
- Insurance Strategy
- Periodic sync to reduce number of requests
- Comparative benchmarks
- Smooth out traffic peaks
-
Usage example
- Minimal protection against password brute-force
- Login endpoint protection
- Websocket connection prevent flooding
- Dynamic block duration
- Different limits for authorized users
- Different limits for different parts of application
- Block Strategy in memory
- Insurance Strategy
- Third-party API, crawler, bot rate limiting