How to make Node cluster skip a busy worker process?

171 Views Asked by At

I'm trying to write in Node (currently using version 18) a web server where one of the routes can be computation heavy so it will run for a long time and max out its CPU core with 100%.
To keep every other request responsive I thought that a Node cluster would be a great fit.

Extremely simplifying the code I ended up with:

import cluster from 'cluster';
import { createServer } from 'http';

const startUpTime = Date.now();

function requestListener(request, response) {
  console.log(`[${(Date.now()-startUpTime)/1000}] pid ${process.pid} is serving URL ${request.url}`);
  response.writeHead(200);
  if (request.url === '/slow') {
    for (let cnt = 0; cnt < 100; cnt++) {
      for (let i = 0; i < 1000000000; i++ ) { /* just wait */ }
      response.write(`${cnt}.`);
    }
  }
  response.end(`... pid ${process.pid} done...\n`);
  console.log(`[${(Date.now()-startUpTime)/1000}] pid ${process.pid} finished serving URL ${request.url}`);
}

if (cluster.isPrimary) {
  for (let i = 0; i < 4; i++) {
    let worker = cluster.fork();
  }
} else {
  // the worker:
  const server = createServer(requestListener);
  server.listen(8000, 'localhost', () => {
    console.info(`Server ${process.pid} is running`);
  });
}

But this code doesn't run as expected. Although my system has 12 cores and is basically idle this is happening:

  1. Running in terminal #1 curl -v http://127.0.0.1:8000/test - immediate result as expected
  2. Running in terminal #1 curl -v http://127.0.0.1:8000/test - immediate result as expected
  3. Running in terminal #2 curl -v http://127.0.0.1:8000/slow - immediate headers, slowly building up response text 0.1.2.3 and 100% CPU load, as expected
  4. Running in terminal #1 curl -v http://127.0.0.1:8000/test - immediate result as expected
  5. Running in terminal #1 curl -v http://127.0.0.1:8000/test - immediate result as expected
  6. Running in terminal #1 curl -v http://127.0.0.1:8000/test - immediate result as expected
  7. Running in terminal #1 curl -v http://127.0.0.1:8000/test - no immediate result as is is blocked till request 3. is finished

The console output shows exactly the problem:

Server 78797 is running
Server 78799 is running
Server 78798 is running
Server 78800 is running
[3.028] pid 78797 is serving URL /test
[3.029] pid 78797 finished serving URL /test
[3.515] pid 78799 is serving URL /test
[3.52] pid 78799 finished serving URL /test
[5.963] pid 78798 is serving URL /slow
[7.27] pid 78800 is serving URL /test
[7.272] pid 78800 finished serving URL /test
[7.976] pid 78797 is serving URL /test
[7.976] pid 78797 finished serving URL /test
[8.568] pid 78799 is serving URL /test
[8.569] pid 78799 finished serving URL /test
[32.492] pid 78798 finished serving URL /slow
[32.495] pid 78798 is serving URL /test
[32.495] pid 78798 finished serving URL /test

So why is the request at step 7. sent to the busy worker (pid 78798) although three other processes are idle and getting bored?

The manual promised me to do it a bit more intelligent than dumb round robin (https://nodejs.org/api/cluster.html#cluster_how_it_works):

The cluster module supports two methods of distributing incoming connections.

The first one (and the default one on all platforms except Windows) is the round-robin approach, where the primary process listens on a port, accepts new connections and distributes them across the workers in a round-robin fashion, with some built-in smarts to avoid overloading a worker process.

How can I change that behavior, so that all requests are answered immediately, even when one of the processes of my cluster pool is completely busy?

0

There are 0 best solutions below