Skip to content

PM2 cluster

Roman edited this page Apr 22, 2020 · 5 revisions

RateLimiterCluster with PM2

Usage

Application launched with PM2 takes control over IPC. It requires to use PM2 messages bus to send and receive between master and forks. It also requires to configure application as master process is PM2 itself and it can not be used to store limits.

Note: maximum duration for PM2 Cluster limiter is 2147483 seconds or about 24 days because of setTimeout limitation.

Configure application

Create app.config.js

module.exports = {
  apps: [
    {
      name: "app-master",
      script: "./app-master.js",
      instances: 1,
      autorestart: true,
    }, {
      name: "app-worker",
      script: "./app-worker.js",
      instances: 4,
      exec_mode: "cluster",
      autorestart: true,
    }]
};

Prepare application

One instance of app-master.js is launched to store limits. It requires pm2 to get access to IPC through it.

// app-master.js
const pm2 = require('pm2');
const { RateLimiterClusterMasterPM2 } = require('rate-limiter-flexible');

new RateLimiterClusterMasterPM2(pm2);

4 instances of app-worker.js are launched to process requests.

// app-worker.js
const { RateLimiterCluster } = require('rate-limiter-flexible');

const rateLimiter = new RateLimiterCluster({
  keyPrefix: 'pm2clusterlimiter', // Must be unique for each limiter
  points: 100,
  duration: 1,
  timeoutMs: 3000 // Promise is rejected, if master doesn't answer for 3 secs
});
  
rateLimiter.consume(remoteAddress, 2) // consume 2 points
  .then((rateLimiterRes) => {
    // 2 points consumed
  })
  .catch((rateLimiterRes) => {
    // Not enough points to consume
  });

See all options here

Start pm2 app

Application is started with command pm2 start app.config.js.

Benchmark

Endpoint is pure NodeJS endpoint launched in node:10.5.0-jessie Docker containers with 4 workers and 1 master.

Endpoint is limited by RateLimiterCluster for 100 random keys with config:

new RateLimiterCluster({
    points: 5,
    duration: 1,
  });

By bombardier -c 1000 -l -d 30s -r 2000 -t 1s http://127.0.0.1:3000

Test with 1000 concurrent requests with maximum 2000 requests per sec during 30 seconds

  Reqs/sec      1991.25     496.53    3215.70
  Latency        2.35ms   349.22us     8.51ms
  Latency Distribution
     50%     2.31ms
     75%     2.79ms
     90%     3.25ms
     95%     3.53ms
     99%     4.09ms
  HTTP codes:
    1xx - 0, 2xx - 14489, 3xx - 0, 4xx - 45531, 5xx - 0

Instances state during test

┌────────────────┬────┬─────────┬─────────┬──────┬────────┬─────────┬────────┬─────┬───────────┬────────┬──────────┐
│ App name       │ id │ version │ mode    │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user   │ watching │
├────────────────┼────┼─────────┼─────────┼──────┼────────┼─────────┼────────┼─────┼───────────┼────────┼──────────┤
│ pm2-app-master │ 0  │ 1.0.0   │ fork    │ 2923 │ online │ 0       │ 32s    │ 35% │ 55.8 MB   │ animir │ enabled  │
│ pm2-app-worker │ 1  │ 1.0.0   │ cluster │ 2922 │ online │ 0       │ 32s    │ 14% │ 47.8 MB   │ animir │ enabled  │
│ pm2-app-worker │ 2  │ 1.0.0   │ cluster │ 2924 │ online │ 0       │ 32s    │ 9%  │ 46.6 MB   │ animir │ enabled  │
│ pm2-app-worker │ 3  │ 1.0.0   │ cluster │ 2925 │ online │ 0       │ 32s    │ 21% │ 52.6 MB   │ animir │ enabled  │
│ pm2-app-worker │ 4  │ 1.0.0   │ cluster │ 2926 │ online │ 0       │ 32s    │ 12% │ 46.4 MB   │ animir │ enabled  │
└────────────────┴────┴─────────┴─────────┴──────┴────────┴─────────┴────────┴─────┴───────────┴────────┴──────────┘
Clone this wiki locally