I am currently deeply learning Nodejs platform. As we know, Nodejs is single-threaded, and if it executes blocking operation (for example fs.readFileSync), a thread should wait to finish that operation. I decided to make an experiment: I created a server that responses with the huge amount of data from a file on each request
const { createServer } = require('http');
const fs = require('fs');
const server = createServer();
server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});
server.listen(8000);
Also, I launched 5 terminals in order to do parallel requests to a server. I waited to see that while one request is being handled, the others should wait for finishing blocking operation from the first request. However, the other 4 requests were responded concurrently. Why does this behavior occur?
What you're likely seeing is either some asynchronous part of the implementation inside of res.end() to actually send your large amount of data or you are seeing all the data get sent very quickly and serially, but the clients can't process it fast enough to actually show it serially and because the clients are each in their own separate process, they "appear" to show it arriving concurrently just because they're too slow reacting to show the actually arrival sequence.
One would have to use a network sniffer to see which of these is actually occurring or run some different tests or put some logging inside the implementation of res.end() or tap into some logging inside the client's TCP stack to determine the actual order of packet arrival among the different requests.
If you have one server and it has one request handler that is doing synchronous I/O, then you will not get multiple requests processes concurrently. If you believe that is happening, then you will have to document exactly how you measured that or concluded that (so we can help you clear up your misunderstanding) because that is not how node.js works when using blocking, synchronous I/O such as fs.readFileSync().
node.js runs your JS as single threaded and when you use blocking, synchronous I/O, it blocks that one single thread of Javascript. That's why you should never use synchronous I/O in a server, except perhaps in startup code that only runs once during startup.
What is clear is that fs.readFileSync('./big.file') is synchronous so your second request will not get started processing until the first fs.readFileSync() is done. And, calling it on the same file over and over again will be very fast (OS disk caching).
But, res.end(data) is non-blocking, asynchronous. res is a stream and you're giving the stream some data to process. It will send out as much as it can over the socket, but if it gets flow controlled by TCP, it will pause until there's more room to send on the socket. How much that happens depends upon all sorts of things about your computer, it's configuration and the network link to the client.
So, what could be happening is this sequence of events:
First request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.
Second request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.
At this point, the event loop might start processing the third or fourth requests or it might service some more events (from inside the implementation of res.end() or the writeStream from the first request to keep sending more data. If it does service those events, it could give the appearance (from the client point of view) of true concurrency of the different requests).
Also, the client could be causing it to appear sequenced. Each client is reading a different buffered socket and if they are all in different terminals, then they are multi-tasked. So, if there is more data on each client's socket than it can read and display immediately (which is probably the case), then each client will read some, display some, read some more, display some more, etc... If the delay between sending each client's response on your server is smaller than the delay in reading and displaying on the client, then the clients (which are each in their own separate processes) are able to run concurrently.
When you are using asynchronous I/O such as fs.readFile(), then properly written node.js Javascript code can have many requests "in flight" at the same time. They don't actually run concurrently at exactly the same time, but one can run, do some work, launch an asynchronous operation, then give way to let another request run. With properly written asynchronous I/O, there can be an appearance from the outside world of concurrent processing, even though it's more akin to sharing of the single thread whenever a request handler is waiting for an asynchronous I/O request to finish. But, the server code you show is not this cooperative, asynchronous I/O.
Maybe is not related directly to your question but i think this is useful,
You can use a stream instead of reading the full file into memory, for example:
const { createServer } = require('http');
const fs = require('fs');
const server = createServer();
server.on('request', (req, res) => {
const readStream = fs.createReadStream('./big.file'); // Here we create the stream.
readStream.pipe(res); // Here we pipe the readable stream to the res writeable stream.
});
server.listen(8000);
The point of doing this is:
Looks nicer.
You don't store the full file in RAM.
This works better because is non blocking, and the res object is already a stream, and this means the data will be transfered in chunks.
Ok so streams = chunked
Why not read chunks from the file and send them in real time instead of reading a really big file and divide that in chunks after?
Also why is really important on a real production server?
Because every time a request is received, your code is going to add that big file into ram, to that add this is concurrent so you are expecting to serve multiple files at the same time, so let's do the most advanced math my poor education allows:
1 request for a 1gb file = 1gb in ram
2 requests for a 1gb file = 2gb in ram
etc
That clearly doesn't scale nicely right?
Streams allows to decouple that data from the current state of the function (inside that scope), so in simple terms its going to be (with the default chunk size of 16kb):
1 request for 1gb file = 16kb in ram
2 requests for 1gb file = 32kb in ram
etc
And also, the OS its already passing a stream to node (fs) so it works with streams end to end.
Hope it helps :D.
PD: Never use sync operations (blocking) inside async operations (non blocking).
Related
I am new to nodejs but i am aware of asynchronous model of javascript in general. I come from grails/servlet background. In servlet, when request is sent to the server everything is synchronous, it computes the result and send it back to the client. If the result takes long then we thread it and store it somewhere to retrieve it later but the response is not hold rather another request is performed to get the result.
In nodejs however, my understanding so far is it waits for the response until it is computed from some asynchronous callbacks.
Now my assumption is, nodejs must return something to the client because the javascript callstack doesn't wait for the return. But NO, the proper response is sent to the client.
Now my question is how the client waits until it gets the response from the callbacks or some promises?
Here is an example:
var express = require('express');
var {mongoose} = require('./db/mongoose');
var {User} = require('./models/user');
var app = express();
app.get('/users',(req,res)=>{
User.find().then((result)=>{ // here response is calculated inside then
// which is retrieved later at this point how nodejs waits for
// this result
res.send(result)
},(e)=>{
});
});
app.listen(3000);
Now my assumption is, nodejs must return something to the client because the javascript callstack doesn't wait for the return.
It doesn't.
Now my question is how the client waits until it gets the response from the callbacks or some promises?
It just … waits. It doesn't need to be told to wait. It knows that sending a message over the network won't get an instant response.
If too much time passes before it gets a response, then it will timeout and give up.
Nodejs will be wait for the response and Node Js doesn't do anything for wait for the response. But if you wants to set any user prevent interface then you can use any library for it.
If client gets too much time for wait then nodejs itself throw any exception (if there are issue will be happen at server side) or timeout.
In general is an event loop only for IO ? and what exactly is an IO job?
For example let's say that a request comes into NodeJs which is then making an outbound http request to an API to get some data while not blocking the user in the meantime.
Is that an IO job and how would NodeJs handle it? what if instead of the http request I wanted to asynchronously make a lengthy calculation and then return a value to the user? Is that handled by the event loop too despite being CPU bound?
In general is an event loop only for IO ?
I wouldn't count timers (setTimeout, setInterval) and sheduling (setImmeadiate, process.nextTick) as IO but one could generally say that the events in the event loop are coming from the outside
and what exactly is an IO job?
That depends on the context you are talking about. Every program gets a certain Input by the user and generates a certain Output. In a terminal for example the input are your keypresses and the output is what is displayed. Whe talking about nodejs IO, one usually refer to network / file operations, or more generally: code not written in js.
For example let's say that a request comes into NodeJs which is then making an outbound http request to an API to get some data while not blocking the user in the meantime.
Is that an IO job and how would NodeJs handle it?
Nodejs would spawn a background thread that makes the request, the main process continues with other stuff in the meantime (continues with other events on the event queue). Then if the async request is done the background process pushes the result onto the event queue, the event loop will pull it from there and execute callbacks etc.
what if instead of the http request I wanted to asynchronously make a lengthy calculation and then return a value to the user?
You have to spawn another thread in nodejs, lengthy calculations are synchronous otherwise.
Is that handled by the event loop too despite being CPU bound?
Everything somewhen lands on the event loop, and everything gets executed on the CPU ...
I have read the difference between Multi thread mechanism and NodeJS Single thread mechanism here. I know less about thread concept.
My question
The above article says that all the Non Blocking I/O is handled using single thread in Event loop.
I have read through questions posted in this forum, but all it says is just the overview of how single thread is working and not the deeper mechanism. says something like...
Starts processing the Client Request
If that Client Request Does Not requires any Blocking IO Operations, then process everything, prepare response and send it back to client.
If there are like 2 or more Non Blocking requests in Event Queue, Event loop takes each requests and processes it.
First request enter Event Pool and starts processing and does not wait or hold till the response and meanwhile request 2 enters and starts processing without wait.
Now,since the 2nd request has taken the thread for processing (and all request is handled using single thread) , currently what is handling the 1st request process, If there is thread sharing , how is it happening ?
Is the first request process released when handling 2nd request and later comes back to 1st request ? if so how is it happening in thread perspective ?
how does single thread processes 2 or more request concurrently as basically thread will be assigned to a request until all it's process is finished
and how is single thread handled for both Input and Output operation at same time ?
is there any topic i am missing to read so that i'm getting this single thread event loop mechanism ?
First off, "single threaded" applies only to one thread running your Javascript. The node.js has other native threads for implementing some of the functions in the built-in library. For example, file I/O uses a thread pool in order to implement asynchronous file I/O. But, what's most important to understanding how your own Javascript runs is that there is only one thread of your Javascript.
Let's imagine that you have a simple web server like this:
const http = require('http');
function sendFile(res, filename) {
if (filename.startsWith("/")) {
filename = filename.slice(1) + ".html";
}
fs.readFile("1.html", (err, data) => {
if (err) {
res.writeHead(404);
res.end("not found");
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
}
});
}
const server = http.createServer((req, res) => {
if (req.url === "/1" || req.url === "/2" || req.url === "/3") {
sendFile(req.url);
} else {
res.writeHead(404);
res.end("not found");
}
});
server.listen(80);
This web server responds to requests for three URLs /1, /2 and /3.
Now imagine that three separate clients each request one of those URLs. Here's the sequence of events:
Client A requests http://myserver.com/1
Client B requests http://myserver.com/2
Client C requests http://myserver.com/3
Server receives incoming connection from client A, establishes the connection, client sends the request for /1 and the server reads and parses that request.
While the server is busy reading the request from client A, the requests from both client B and client C arrive.
The TCP stack handles incoming connections at the OS level (using other threads i.e. kernel level thread).
Notifications of the arriving connections are put in the node.js event queue. Because the node.js server is busy running Javascript for the client A connection, those two events sit in the event queue for now.
At the same time as those other connections are arriving, the node.js server is starting to run the request handler for /1. It finds a match in the first if statement and calls sendFile("/1").
sendFile() calls fs.readFile() and then returns. Because fs.readFile() is asynchronous, that file operation is started, but is handed over to the I/O system inside of node.js and then the function immediately returns. When sendFile() returns, it goes back to the http server request handler which also then returns. At this point, there's nothing else for this request to do. Control has been returned back to the interpreter to decide what to do next.
The node.js interpreter checks the event queue to see if there is anything in their to process. It finds the incoming request from client B and that request starts processing. This request goes through the same 8 and 9 steps until it returns with another fs.readFile() operations initiated.
Then step 10 is repeated for the incoming request from client C.
Then, some short time later, one of the three fs.readfile() operations that were previously initiated completes and places a completion callback into the Javascript event queue. As soon as the Javascript interpreter has nothing else to do, it finds that event in the event queue and begins to process it. This calls the callback that was passed to fs.readFile() with the two parameters that that function expects and the code in the callback starts to execute.
Assuming the fs.readFile() operation was successful, it calls res.writeHead(), then res.write(), then res.send(). Those three calls all send data to the underlying OS TCP stack where it is then sent back to the client.
After res.end() returns, control is returned back to the interpreter and it checks the event queue for the next event. If another fs.readFile() callback is already in the event queue, then it is pulled out of the event queue and processed like the previous one. If the event queue is empty, then the interpreter waits until something is put in the event queue.
If there are like 2 or more Non Blocking requests in Event Queue, Event loop takes each requests and processes it.
node.js only runs one at a time. But, the key is that asynchronous code in the request handler allows the handler to return control back to the system so that other events can be processed while that first request was waiting for its asynchronous operation to complete. This is a form of cooperative, non-pre-emptive multi-tasking. It's not multiple threads of Javascript. The first request handler actually starts and asynchronous operation and then returns (as if it was done). When it returns, the next event in the queue can start processing. At some later time when the asynchronous operation completes, it will insert its own event into the event queue and it will get back in line to use the single thread of Javascript again.
First request enter Event Pool and starts processing and does not wait or hold till the response and meanwhile request 2 enters and starts processing without wait.
Most of this has already been described above. If the Javascript thread is busy why request 2 enters the event queue, that request will sit in the event queue until the Javascript thread is no longer busy. It may have to wait a short period of time. But, it won't have to wait until request 1 is done, only until request 1 returns control back to the system and is, itself, waiting for some asynchronous operation to complete.
Now,since the 2nd request has taken the thread for processing (and all request is handled using single thread) , currently what is handling the 1st request process, If there is thread sharing , how is it happening ?
While the 2nd request is using the Javascript thread, the 1st request is not running any Javascript. It's native code asynchronous operations may be running in the background (all asynchronous operations require some native code), but there is only one piece of Javascript running at any given time so if the 2nd request is running some Javascript, then the first request is either waiting for its asynchronous operation to finish or that operation has already finished and an event is sitting in the event queue waiting for the 2nd request to be done so that event can get processed.
Is the first request process released when handling 2nd request and later comes back to 1st request ? if so how is it happening in thread perspective ?
This all works through the event queue. 1st request runs until it returns. 2nd request runs until it returns. When async operation from 1st request completes it inserts an item in the event queue. When the JS interpreter is free, it pulls that event from the event queue and runs it. There may be threads involved in native code implementations of asynchronous operations, but there is still only one thread of Javascript.
how does single thread processes 2 or more request concurrently as basically thread will be assigned to a request until all it's process is finished
It never actually runs multiple pieces of Javascript concurrently. The Javascript from each different operation runs until it returns control back to the interpreter. Asynchronous operations (such as file I/O or networking operations) can run concurrently and those are managed by native code, sometimes using additional threads and sometimes not. File I/O uses a thread pool to implement non-blocking, asynchronous file I/O. Networking uses OS event notifications (select, epoll, etc...), not threads.
and how is single thread handled for both Input and Output operation at same time ?
It doesn't in your Javascript. It would typically read, then write. It doesn't do both "at the same time". Now, the TCP stack may be doing some actual parallel work inside the OS, but that's all managed by the OS and even that probably gets serialized at the network interface at some point.Requests are handled by single thread where as input output processes are managed by os level threads created per each process by OS
is there any topic i am missing to read so that i'm getting this single thread event loop mechanism ?
Read every thing you can find about the Javascript event queue. Here are some references to get you started:
How does JavaScript handle AJAX responses in the background?
Where is the node.js event queue?
Node.js server with multiple concurrent requests, how does it work?
Asynchronous process handler in node
How does a single thread handle asynchronous code in Node.js?
I have been working in Node.js and I am wondering what exactly does listen method do, in terms of eventloop. If I had a long running request, does it mean that server will never listen since it can only do one work at a time.
var http = require('http');
function handleRequest(request, response) {
response.end('Some Response at ' + request.url);
}
var server = http.createServer(handleRequest);
server.listen(8083, function() {
console.log('Listening...')
})
Is server.listen listening to some event?
You can think of server.listen() as starting your web server so that it is actually listening for incoming requests at the TCP level. From the node.js http documentation for .listen():
Begin accepting connections on the specified port and hostname.
The callback passed to server.listen() is optional. It is only called once to indicate that the server has been successfully started and is now listening for incoming requests. It is not what is called on every new incoming request. The callback passed to .createServer() is what is called for every new incoming request.
Multiple incoming requests can be in process at the same time though due to the single-threaded nature of node.js only one request is actually executing JS code at once.
But, a long running request is generally idle most of the time (e.g. waiting for database I/O or disk I/O or network I/O) so other requests can be processed and run during that idle time. This is the async nature of node.js and why it is important to use asynchronous I/O programming with node.js rather than synchronous I/O processing because asynchronous I/O allows other requests to run during the time when node.js is just waiting for I/O.
Yes, it basically binds an event listener to that port; similar to how event listeners work in your own code. Going more in depth would involve sockets, etc...
https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback
The other answers are essentially correct, but I wanted to add more detail.
When you call createServer, the handler you pass in is what gets called on every incoming HTTP connection. But that is merely setting that up: it does not actually start the server or start listening for those connections. That doesn't happen until you call listen.
The (optional) callback for listen is just what gets called when the server has successfully started and is now listening for connections. Most of the time, it's simply used to log to the console that the server is started. You could also use it to record server start time for uptime monitoring. That callback is NOT invoked for every HTTP request: only once on server startup.
You don't even have to supply the callback for listen; it works fine without it. Here are some common variations (note that it's a good practice to let the port be specified by an environment variable, usually PORT; if that environment variable isn't set, there is a default):
// all in one line, no startup message
var server = http.createServer(handler).listen(process.env.PORT || 8083);
// two lines, no startup message
var server = http.createServer(handler); // server NOT started
server.listen(process.env.PORT || 8083); // server started, no confirmation
// most typical variation
var server = http.createServer(handler);
server.listen(process.env.PORT || 8083, function() {
// server started, startup confirmed - note that this only gets called once
console.log('server started at ' + Date.now());
});
Node allows you to spawn child processes and send data between them. You could use it do execute some blocking code for example.
Documentation says "These child Nodes are still whole new instances of V8. Assume at least 30ms startup and 10mb memory for each new Node. That is, you cannot create many thousands of them."
I was wondering if is it efficient, should I worry about some limitations? Here's example code:
//index.js
var childProcess1 = childProcess.fork('./child1.js');
childProcess1.send(largeArray);
childProcess1.once('message', function(formattedData) {
console.log(formattedData);
return false;
});
//child1.js
process.on('message', function(data) {
data = format(data); //do smth with data, then send it back to index.js
try{
process.send(data);
return false;
}
catch(err){
console.log(err);
return false;
}
});
The documentation is telling you that starting new node processes is (relatively) expensive. It is unwise to fork() every time you need to do work.
Instead, you should maintain a pool of long-running worker processes – much like a thread pool. Queue work requests in your main process and dispatch them to the next available worker when it goes idle.
This leaves us with a question about the performance profile of node's IPC mechanism. When you fork(), node automatically sets up a special file descriptor on the child process. It uses this to communicate between processes by reading and writing line-delimited JSON. Basically, when you process.send({ ... }), node JSON.stringifys it and writes the serialized string to the fd. The receiving process reads this data until hitting a line break, then JSON.parses it.
This necessarily means that performance will be highly dependent on the size of the data you send between processes.
I've roughed out some tests to get a better idea of what this performance looks like.
First, I sent a message of N bytes to the worker, which immediately responded with a message of the same length. I tried this with 1 to 8 concurrent workers on my quad-core hyper-threaded i7.
We can see that having at least 2 workers is beneficial for raw throughput, but more than 2 essentially doesn't matter.
Next, I sent an empty message to the worker, which immediately responded with a message of N bytes.
Surprisingly, this made no difference.
Finally, I tried sending a message of N bytes to the worker, which immediately responded with an empty message.
Interesting — performance does not degrade as rapidly with larger messages.
Takeaways
Receiving large messages is slightly more expensive than sending them. For best throughput, your master process should not send messages larger than 1 kB and should not receive messages back larger than 128 bytes.
For small messages, the IPC overhead is about 0.02ms. This is small enough to be inconsequential in the real world.
It is important to realize that the serialization of the message is a synchronous, blocking call; if the overhead is too large, your entire node process will be frozen while the message is sent. This means I/O will be starved and you will be unable to process any other events (like incoming HTTP requests). So what is the maximum amount of data that can be sent over node IPC?
Things get really nasty over 32 kB. (These are per-message; double to get roundtrip overhead.)
The moral of the story is that you should:
If the input is larger than 32 kB, find a way to have your worker fetch the actual dataset. If you're pulling the data from a database or some other network location, do the request in the worker. Don't have the master fetch the data and then try to send it in a message. The message should contain only enough information for the worker to do its job. Think of messages like function parameters.
If the output is larger than 32 kB, find a way to have the worker deliver the result outside of a message. Write to disk or send the socket to the worker so that you can respond directly from the worker process.
This really depends on your server resources and the number of nodes you need to spin up.
As a rule of thumb:
Try reusing running children as much as possible - this will save you 30ms start up time
Do not start unlimited number of children (1 per request for instance) - you will not run out of RAM
The messaging itself it relatively fast i believe. Would be great to see some metrics though.
Also, note that if you have single CPU or running a cluster (using all available cores) it doesn't make much sense. You still have limited CPU capacity and switching context is more expensive than running single process.