1

I am writing a image processing web service on NodeJS and in basic i need to figure out how to make the actual image processing threaded. When i test it through Apache AB NodeJS is only using one core and stalling, i am surely doing something wrong here. How can i redesign this simple app to make use of the multiple cores on my server CPU.

I scaled away all input filtering and simplified the image processing function to give you a idea of the app structure instead of long code bits.

In app.js

app.get('/generate/:q', function(req, res){
    var textString = req.params.q;
    res.setHeader("Content-Type", "image/png");
    res.end(generator.s());
});

In generate.js

var Canvas = require('canvas')
  , Font = Canvas.Font
  , fs = require('fs')
  , path = require("path")
  , plate = new Canvas.Image;

//To keep plate in RAM between requests.
fs.readFile(__dirname + '/plates/dhw132.png', function(err, squid){
    if (err) throw err;
    plate.src = squid;
});

exports.s = function () {
    canvas = new Canvas(731,1024);
    ctx = canvas.getContext('2d');
    ctx.drawImage(plate, 0, 0, plate.width, plate.height);
    return canvas.toBuffer();
}

How can i rewrite this to make the generator.s() threaded?

saxly
  • 89
  • 10
  • Node.js is single threaded. There's not a simple way to do it in NodeJs. http://stackoverflow.com/questions/10773564/which-would-be-better-for-concurrent-tasks-on-node-js-fibers-web-workers-or-t – WiredPrairie Oct 04 '13 at 10:47
  • Yes i realize this due to the way Node handles request but i still can't wrap my head around a simple way to make this executable in different cores, i read up about Child Process forking but it seems there is no easy way of reading the actual plate into memory once and then spawn the canvas function into a process that executes with a reference to the file in a different thread. – saxly Oct 04 '13 at 10:51
  • There is only one thread. The easiest would be to just load the image in each process. Data crunching isn't necessarily a great fit for NodeJs, as you can see. It works best with small async tasks. – WiredPrairie Oct 04 '13 at 10:54
  • Will try that and benchmark, currently it looks like this [HTOP](http://i.imgur.com/CvcV4Lv.png) – saxly Oct 04 '13 at 10:57
  • Be sure to use async file operations when possible of course too. – WiredPrairie Oct 04 '13 at 10:59
  • The problem is the performance i gained from doing it with Node and keep the image in RAM goes down if i load it every time. – saxly Oct 04 '13 at 12:32
  • (As I said, this may not be a good fit for Node.JS). – WiredPrairie Oct 04 '13 at 12:33
  • What do you recommend instead? Java? Python? – saxly Oct 04 '13 at 12:44
  • Recommend? Something that fits your requirements better -- either probably could work. Depends on your skills, time, etc. – WiredPrairie Oct 04 '13 at 13:36

1 Answers1

2

Node is single thread of course, but need for multi-threading is a very valid use-case. There's two ways I'm aware of.

Why don't you use clusters. You'll get a configurable number of process/threads, by default the number of cpus on your machine. Cluster essentially load-balances your application across processes, and transparently handles those processes sharing of the single listening http port.

http://nodejs.org/api/cluster.html

There's a wrapper for it as well here: https://github.com/dpweb/cluster-node

A different option, you could fork the process directly, here's an example where an uploaded file gets converted to mp3 using lame.. For your case, you would encapsulate all the image processing in a separate app, so the clusters option may be cleaner that doing that.

app.post('/process', function(req, res){
        var f = req.files.filen;
        fs.rename(f.path, f.name, function(err) {
            if (err){
                fs.unlink(f.name, ef);
                throw err;
                return;
            }
            fs.unlink(f.path, function() {
                    var ext = "." + req.body.extn;

                    require('child_process').exec("lame "+f.name, function(err, out, er) {
                            var nfn = f.name + '.mp3';
                            res.setHeader('Content-Type', 'application/octet-stream');
                            res.setHeader('Content-disposition', 'attachment; filename='+nfn);
                            res.setHeader("Content-Transfer-Encoding: binary");
                            res.setHeader('Accept-Ranges: bytes');

                            var size = fs.statSync(nfn).size;
                            console.log(size, f.name, nfn)
                            res.setHeader('Content-Length', size);
                            fs.createReadStream(nfn).pipe(res);
                            fs.unlink(nfn, ef); fs.unlink(f.name, ef);
                    })
            })
        })
})
C B
  • 12,482
  • 5
  • 36
  • 48