Monkey ? NodeJS ?, when & where…

I can not omit the huge impact that the NodeJS project is having as a server side solution with performance and features for new projects nowadays. As i wrote yesterday, i attended the Startechconf and at least two companies are putting their efforts to move to NodeJS as backend solution for their web infraestructure in a few projects: Yahoo and ForkHQ.

I did not know too much about NodeJS, so i dedicated some time to read the documentation and papers available, so being a web server side guy i would like to share my opinion, because i listen too much about that everybody must move to NodeJS.

The primary feature of NodeJS is that provides a framework  based in a language thats handled by thousands of people: Javascript, if you are a real web developer you know what is JavaScript and you know how to deal with it, so you can jump directly from the client to the server side and write your own implementation, based on an event driven infrastructure with reduced I/O and better performance than dynamic content generators available such as Ruby, Python or PHP.  It’s pretty interesting as technology which expose new possibilities to improve backend sides, but you must know when and where to use it.

The good thing is that Node abstract you from the dirty low level concepts of a web server like threading, shared memory, asynchronous sockets, reduced I/O, etc. But this have a cost, this is not magic, is just cool, because it works and have demonstrated to perform very well and have a level of trust as is written on top of V8 JavaScript engine supported by Google. The cost of an event driven solution is that if for some reason the program have an exception, the whole service will block or even crash depending of the case, so you must be aware because if something similar happen. As an example, if some Apache context fails, it will kill the process or thread and start a new one, which is not the case of a common event driven web server. What happen if you have 1000 connections transferring data and the program fail ?, it will be critical, and this things happens when working in high production environment, if you have 50 requests per day you are safe and you can stop reading now :)

Node fills fine if you have thousands of incoming connections and your computing time is reduced, but if you will work with some complexity querying a database, doing some memcache or similar, you should start considering different options.

From now i start talking about solutions for really higher performance, Node is fast, but you cannot compare it with Apache, because Apache is the slowest web server available, compare it with NginX or Monkey. I will do a test now using the Apache Benchmark Utility comparing the NodeJS hello world example against Monkey which will serve a file which contains the Hello World message, the benchmark utility will perform 100.000 requests through 5000 concurrent connections.

NodeJS Benchmark

edsiper@monotop:/home/edsiper/# ab -n 100000 -c 5000 http://localhost:8888/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)

Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests

Server Software:
Server Hostname:        localhost
Server Port:            8888
Document Path:          /
Document Length:        11 bytes

Concurrency Level: 5000
Time taken for tests: 9.403 seconds
Complete requests: 99747
Failed requests: 0
Write errors: 0
Total transferred: 7481025 bytes
HTML transferred: 1097217 bytes
Requests per second: 10608.48 [#/sec] (mean)
Time per request: 471.321 [ms] (mean)
Time per request: 0.094 [ms] (mean, across all concurrent requests)
Transfer rate: 776.99 [Kbytes/sec] received

 

The NodeJS server was capable to serve 10608 requests per second and took 9 seconds to serve the 100.000 requests. Now let’s see how Monkey did…

 

Monkey HTTP Daemon Benchmark

edsiper@monotop:/home/edsiper/# ab -n 100000 -c 5000 http://localhost:2001/h.txt
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)

Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests

Server Software:        Monkey/0.30.0
Server Hostname:        localhost
Server Port:            2001
Document Path:          /h.txt
Document Length:        13 bytes
Concurrency Level:      5000
Time taken for tests:   5.718 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Total transferred:      20300000 bytes
HTML transferred:       1300000 bytes
Requests per second:    17489.54 [#/sec] (mean)
Time per request:       285.885 [ms] (mean)
Time per request:       0.057 [ms] (mean, across all concurrent requests)
Transfer rate:          3467.16 [Kbytes/sec] received

 

Monkey did 17.489 requests per second and took 5.7 seconds to serve the 100.000 requests. Ooops! :)

 

The impressive results are even better, because Monkey performed 100.000 I/O to retrieve a file from the hard disk and also send a couple of extra bytes one each response (Monkey does not cache file contents or metadata). Serve a file is a slow process due to I/O, so i will do a test later with the same case serving some fixed content through a plugin (something similar to what Node is doing in the test example).

What am trying to say here, is that depending of what are you trying to accomplish and the complexity of your backend., NodeJS can be the solution for your environment as well you could need something even more scalable like Monkey, but the learning curve of NodeJS is short and the learning curve of Monkey is a little high, but this last one  provides a better performance because all is well written in C, as well any extension through the C API interface requires some knowledge which in NodeJS are hidden, you have to balance between goals, knowledge, learning curve and deadlines.

[UPDATE]:

  • Joe provide me a new code to launch Node with multiple workers, so Node increase the performance, the new values were updated.
  • Joe

    Node is not necessarily the fastest of the pack, but the difference you are getting look really unrealistic to me. Are you sure that
    - both webservers run with the same keepAlive settings?
    - both webservers use the same number of worker threads / processes?

  • http://edsiper.linuxchile.cl Eduardo Silva

    @Joe

    The test did not use the KeepAlive feature, each request was a new TCP connection. Monkey used 5 threads as workers to handle event driven events.

    I am not sure how to handle the threading on Node to improve performance, if you know how please let me know so i can adjust my test.

  • http://edsiper.linuxchile.cl Eduardo Silva

    @Joe

    For some reason, apache benchmark with NodeJS got stuck before finish, i could increase the hits to this:

    Concurrency Level: 5000
    Time taken for tests: 14.548 seconds
    Complete requests: 98767
    Failed requests: 0
    Write errors: 0
    Total transferred: 7407525 bytes
    HTML transferred: 1086437 bytes
    Requests per second: 6789.19 [#/sec] (mean)
    Time per request: 736.465 [ms] (mean)
    Time per request: 0.147 [ms] (mean, across all concurrent requests)
    Transfer rate: 497.26 [Kbytes/sec] received

    the requests per seconds were increased but still doing a little slow..

  • Joe

    @Eduardo Silva
    That’s more like it.

    To run a benchmark that does use all the cores (provided that you’re running node 0.6):
    “`
    var cluster = require(‘cluster’);
    var os = require(‘os’);

    if (cluster.isMaster) {
    for (var i = 0, n = os.cpus().length; i < n; ++i) cluster.fork();
    } else {
    // Insert http server example here
    }
    “`

  • http://edsiper.linuxchile.cl Eduardo Silva

    Now it improved a little bit:

    Server Software:
    Server Hostname: localhost
    Server Port: 8888

    Document Path: /
    Document Length: 11 bytes

    Concurrency Level: 5000
    Time taken for tests: 9.403 seconds
    Complete requests: 99747
    Failed requests: 0
    Write errors: 0
    Total transferred: 7481025 bytes
    HTML transferred: 1097217 bytes
    Requests per second: 10608.48 [#/sec] (mean)
    Time per request: 471.321 [ms] (mean)
    Time per request: 0.094 [ms] (mean, across all concurrent requests)
    Transfer rate: 776.99 [Kbytes/sec] received

  • http://edsiper.linuxchile.cl Eduardo Silva

    @Joe

    I have updated the results. thanks for the code!

  • Fabian Frank

    Hi Eduardo,

    it would be very interesting to see how Monkey and Node.js compare when one or more back-ends have to be called to render the response. Either an RDMBS or a JSON API, etc. I don’t think it is very surprising that a fully async C implementation, can outperform a similar application implemented in a scripting language. Just in the same way as an ASM reimplementation of Monkey will be able to outperform its C implementation. Btw. even if Monkey does not cache the file contents, the OS will. Or have you verified with iostat that Monkey is really generating Disk IO for every request? Also I think it is fair to say that Node.js has a lot more to do here, running the V8 engine to execute the hello world code, compared to Monkey’s read system call that results in a cache hit of the OS.

    In my understanding, btw., Node.js is not built for fast static file serving. I think it’s much more suitable for implementing server side code, for example rendering, in web applications.

    Regards,
    Fabian

  • Gart

    It would be interesting to see tests using a multi-threaded client (like weighttp, written for Lighttpd) instead of the single-threaded Apache Benchmark tool.

    Since your server is using several threads, you need to use the same number of threads for the client.

    Your server will look faster (because it really is).

  • http://edsiper.linuxchile.cl Eduardo Silva

    @Gart

    ab is running in threaded mode with 5000 concurrents (5000 clients performing requests).

    Monkey uses a fixed number of threads and is based in an event driven model, it does not need to spawn 1 thread per client…

  • http://edsiper.linuxchile.cl Eduardo Silva

    Hi @Fabian Frank

    We are hard working on that part, we already merged JSON support into Duda and we are working backend stuff such as Redis and Memcache.

    We will keep posting our advances, we aim to write a flexible event-driven web services implementation, more news shortly..

    regards,