Performance challenges
Processing long running queries with Node
Section titled “Processing long running queries with Node”Since Node is single-threaded, there is a need of workaround if it comes to a long-running calculations.
Note: this is “ready to run” example. Just, don’t forget to get jQuery and install the required modules.
Main logic of this example:
- Client sends request to the server.
- Server starts the routine in separate node instance and sends immediate response back with related task ID.
- Client continiously sends checks to a server for status updates of the given task ID.
Project structure:
project │ package.json │ index.html │ ├───js │ main.js │ jquery-1.12.0.min.js │ └───srv │ app.js ├─── models │ task.js └─── tasks data-processor.jsapp.js:
var express = require('express');var app = express();var http = require('http').Server(app);var mongoose = require('mongoose');var bodyParser = require('body-parser');
var childProcess= require('child_process');
var Task = require('./models/task');
app.use(bodyParser.urlencoded({ extended: true }));app.use(bodyParser.json());
app.use(express.static(__dirname + '/../'));
app.get('/', function(request, response){ response.render('index.html');});
//route for the request itselfapp.post('/long-running-request', function(request, response){ //create new task item for status tracking var t = new Task({ status: 'Starting ...' });
t.save(function(err, task){ //create new instance of node for running separate task in another thread taskProcessor = childProcess.fork('./srv/tasks/data-processor.js');
//process the messages comming from the task processor taskProcessor.on('message', function(msg){ task.status = msg.status; task.save(); }.bind(this));
//remove previously openned node instance when we finished taskProcessor.on('close', function(msg){ this.kill(); });
//send some params to our separate task var params = { message: 'Hello from main thread' };
taskProcessor.send(params); response.status(200).json(task); });});
//route to check is the request is finished the calculationsapp.post('/is-ready', function(request, response){ Task .findById(request.body.id) .exec(function(err, task){ response.status(200).json(task); });});
mongoose.connect('mongodb://localhost/test');http.listen('1234');task.js:
var mongoose = require('mongoose');
var taskSchema = mongoose.Schema({ status: { type: String }});
mongoose.model('Task', taskSchema);
module.exports = mongoose.model('Task');data-processor.js:
process.on('message', function(msg){ init = function(){ processData(msg.message); }.bind(this)();
function processData(message){ //send status update to the main app process.send({ status: 'We have started processing your data.' });
//long calculations .. setTimeout(function(){ process.send({ status: 'Done!' });
//notify node, that we are done with this task process.disconnect(); }, 5000); }});
process.on('uncaughtException',function(err){ console.log("Error happened: " + err.message + "\n" + err.stack + ".\n"); console.log("Gracefully finish the routine.");});index.html:
<!DOCTYPE html><html> <head> <script src="./js/jquery-1.12.0.min.js"></script> <script src="./js/main.js"></script> </head> <body> <p>Example of processing long-running node requests.</p> <button id="go" type="button">Run</button>
<br />
<p>Log:</p> <textarea id="log" rows="20" cols="50"></textarea> </body></html>main.js:
$(document).on('ready', function(){
$('#go').on('click', function(e){ //clear log $("#log").val('');
$.post("/long-running-request", {some_params: 'params' }) .done(function(task){ $("#log").val( $("#log").val() + '\n' + task.status);
//function for tracking the status of the task function updateStatus(){ $.post("/is-ready", {id: task._id }) .done(function(response){ $("#log").val( $("#log").val() + '\n' + response.status);
if(response.status != 'Done!'){ checkTaskTimeout = setTimeout(updateStatus, 500); } }); }
//start checking the task var checkTaskTimeout = setTimeout(updateStatus, 100); }); });});package.json:
{ "name": "nodeProcessor", "dependencies": { "body-parser": "^1.15.2", "express": "^4.14.0", "html": "0.0.10", "mongoose": "^4.5.5" }}Disclaimer: this example is intended to give you basic idea. To use it in production environment, it needs improvements.