How do I use Node.js clusters with my simple Express app?

11,412

Actually, your workload is not really I/O bound: it is CPU bound due to the cost of jade-based dynamic page generation. I cannot guess the complexity of your jade template, but even with simple templates, generating HTML pages is expensive.

For my tests I used this template:

html(lang="en")
  head
    title Example
  body
    h1 Jade - node template engine
    #container
      ul#users
        each user in items
          li User:#{user}

I added 100 dummy strings to the items key in Redis.

On my box, I get 475 req/s with node.js CPU at 100% (which means 50% CPU consumption on this dual core box). Let's replace:

res.render( 'index', { items: items } );

by:

res.send( '<html lang="en"><head><title>Example</title></head><body><h1>Jade - node template engine</h1><div id="container"><ul id="users"><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li><li>User:NOTHING</li></ul></div></body></html>' );

Now, the result of the benchmark is close to 2700 req/s. So the bottleneck is clearly due to the formatting of the HTML page.

Using the cluster package in this situation is a good idea, and it is straightforward. The code can be modified as follows:

var cluster = require('cluster')

if ( cluster.isMaster ) {
  for ( var i=0; i<2; ++i )
    cluster.fork();
} else {
  var
      express = require( 'express' ),
      app     = express.createServer(),
      redis   = require( 'redis' ).createClient();

  app.configure( function() {
      app.set( 'view options', { layout: false } );
      app.set( 'view engine', 'jade' );
      app.set( 'views', __dirname + '/views' );
      app.use( express.bodyParser() );
  });

  function log( what ) { console.log( what ); }

  app.get( '/', function( req, res ) {
      redis.lrange( 'items', 0, 50, function( err, items ) {
            if( err ) { log( err ); } else {
              res.render( 'index', { items: items } );
            }
      });
  });

  app.listen( 8080 );
}

Now the result of the benchmark is close to 750 req/s with 100 % CPU consumption (to be compared with the initial 475 req/s).

Share:
11,412

Related videos on Youtube

vjk2005
Author by

vjk2005

I design and develop web apps and Chrome extensions. Tools of choice are Node.js + MongoDB for the server-side and standard JS/Jquery for the client-side.

Updated on June 14, 2022

Comments

  • vjk2005
    vjk2005 almost 2 years

    — I built a simple app that pulls in data (50 items) from a Redis DB and throws it up at localhost. I did an ApacheBench (c = 100, n = 50000) and I'm getting a semi-decent 150 requests/sec on a dual-core T2080 @ 1.73GHz (my 6 y.o laptop), but the proc usage is very disappointing as shown: enter image description here

    Only one core is used, which is as per design in Node, but I think I can nearly double my requests/sec to ~300, maybe even more, if I can use Node.js clusters. I fiddled around quite a bit but I haven't been able to figure out how to put the code given here for use with my app which is listed below:

    var 
        express = require( 'express' ),
        app     = express.createServer(),
        redis   = require( 'redis' ).createClient();
    
    app.configure( function() {
        app.set( 'view options', { layout: false } );
        app.set( 'view engine', 'jade' );
        app.set( 'views', __dirname + '/views' );
        app.use( express.bodyParser() );
    } );
    
    function log( what ) { console.log( what ); }
    
    app.get( '/', function( req, res ) {
        redis.lrange( 'items', 0, 50, function( err, items ) {
                if( err ) { log( err ); } else {
                    res.render( 'index', { items: items } );
                }
        });
    });
    
    app.listen( 8080 );
    

    I also want to emphasize that the app is I/O intensive (not CPU-intensive, which would've made something like threads-a-gogo a better choice than clusters).

    Would love some help in figuring this out.

  • vjk2005
    vjk2005 almost 12 years
    wow, thanks!, that worked really well — imgur.com/Cpqy6 — I spent most of yesterday pushing it from 80 to 150 and now a simple if block is making all that work look like peanuts. The note about templates was very educational; I knew there would be some cost but didn't think it'd be "475 to 2700" big. Can I optimize Express? Btw, I also wanted to share the CPU stats — imgur.com/n7WPv — I went as high as 15 workers but both cores never went to 100%. I haven't enabled Redis persistence so it shouldn't touch the disk, but around the 30K-request mark my disk spins crazy. Am I overlooking something?
  • Didier Spezia
    Didier Spezia almost 12 years
    With a dual core box, the optimal number of worker processes is 2. I don't know how much memory you have, but if you start too many workers, you may trigger swapping. On my box, one node process takes around 50 MB in memory.
  • Didier Spezia
    Didier Spezia almost 12 years
    The performance bottleneck is not express but jade. You could try another templating engine (still working with express). Here is a list: github.com/joyent/node/wiki/modules#wiki-templating
  • vjk2005
    vjk2005 almost 12 years
    oh no, by "I went as high as 15 workers" — I meant I started at 2, as listed in your answer, & worked my way up through 3, 4, 5 and gave up at 15; none of those configs saw both the cores reach 100% simultaneously, which got me wondering if I was doing something wrong.
  • vjk2005
    vjk2005 almost 12 years
    "The performance bottleneck is not express but jade." — whoops, I meant "jade" but said "express". What according to you is faster than Jade?
  • vjk2005
    vjk2005 almost 12 years
    " I don't know how much memory you have, but if you start too many workers, you may trigger swapping." — I have 1.5 GB out of which it used up 500 MB with swap around 260 MB out of 3.5 GB. Puzzler.
  • vjk2005
    vjk2005 almost 12 years
    Just to close out the discussion on templates, I found this benchmark of different template engines — paularmstrong.github.com/node-templates/benchmarks.html, handy for those hitting this question who wish to improve their template performance as well.
  • Didier Spezia
    Didier Spezia almost 12 years
    According to my own benchmark, the fastest ones are Swig, nTenjin, thunder and doT in that order.
  • vjk2005
    vjk2005 almost 12 years
    Alrighty, I'll check out Swig and will update here if the I get significant improvements over Jade. Thanks again, Didier.
  • Aleksei Zabrodskii
    Aleksei Zabrodskii over 11 years
    You should try NODE_ENV=production node app.js instead of node app.js .