Hacker News new | past | comments | ask | show | jobs | submit login
Node.js: details and best practices (a server side guy's analysis) (wilcoxd.com)
72 points by rpwilcox on Oct 29, 2011 | hide | past | favorite | 20 comments



It is hinted in the article that node.js is better for some use cases than others.  One thing node.js is good at is realtime apps and socket.io and the evented model make these easier.

However, as a server developer guy too, I must point out that if scalability ( or premature optimization ) is dear to you, I would look further than cluster to run multiple node processes. A good solution I found for a few projects is to use Redis pub sub to encapsulate socket.io messages to connected clients to make it easy, non-blocking and performant to scale to multiple nodes. For a project where I have some potential CPU bound code, I further split these operations in another process (let's say a game state machine with calculations) with which I exchange pub sub messages. In such realtime apps, the worst that can happen is that some process intensive functions may take a bit more time to dispatch information to the clients but the core communications node processes won't be blocked and will be screaming fast and horizontally scalable.

I've found that Redis pub sub is awesome for simple and fast interprocess communications.


> Underscore.js for Iterations: each implicitly blocks

???? Blocking refers to IO system calls. underscore is not an IO library. Does a 15 year programmer with C++ experience not know what an O_NONBLOCK system call is? Blocking or non blocking is about how you are performing IO with the external world. It has nothing to do with the world internal to your program: callbacks are merely correlated with nonblocking io, continuation passing style does not constitute nonblocking in an of itself. You do not have to use continuation passing style and lambdas for code that does not perform IO for it to be "nonblocking".


The way I understood it is that if I have a section of Javascript that takes, say, 1/2 a second to run, and does not yield control back to the event loop, by definition blocks the event loop from firing.

In a preemptive multithreaded environment it's different, but here you have one single thread and - at least to my understanding - any code that takes time to process blocks the event loop from being able to respond to incoming requests.

Unless I'm missing something and node can (say) preempt your process at certain defined conditions. ("Call a function? Ok, let's check the event loop first and come right back"). I didn't see that anywhere (but I also didn't want to dig into libenv.

I understand that IO is expensive, but so is performing string interpolation on a string in memory 50 times (for whatever reason).


This is exactly what Zed was pointing out with his intentionally pessimized fibonacci generator.

Sure node can make waiting on disk or network asyncronous - but it's no silver bullet, a poorly though out node.js server might scale just as badly as any other poorly thought out server in any other language. You still need to understand what's going on and not code yourself into the low performance corners.


> This is exactly what Zed was pointing out with his intentionally pessimized fibonacci generator.

You mean Ted Dziuba [1]?

[1]: http://teddziuba.com/2011/10/node-js-is-cancer.html


Exactly. The fibonacci generator (and subsequent async implementations) are one of the things I looked at when writing the article.


>I understand that IO is expensive, but so is performing string interpolation on a string in memory 50 times (for whatever reason).

I think you are conflating waiting and working. Both cost time, but they are not the same. Waiting can be avoided (nonblocking), working cannot.

Iterating over an array in most languages probably takes .000001 seconds. Reading from a bad connection could take 30+ seconds, or whatever your servers timeout is.

Since the 30 seconds is spent _waiting_, it can be avoided within a single thread of execution. On the other hand, if you are spending 30 seconds _working_ to compute prime numbers, this cannot be avoided in a single thread of execution.

Blocking vs nonblocking refers to dealing with waits. Does each IO operation do the waiting, or does each IO operation return immediately and we pool waits into a single poll() (etc.) call?

Parallelization refers to dealing with work. In the rare instance one needs to generate huge fibonacci sequences, one can do so in a seperate fork\process\thread\cpu core\machine, and report results back to the event loop via pipe\socket\shared memory. [1] Multiplexing waits in a single thread makes sense, multiplexing work in a single thread such as array iteration does not: on the contrary it sounds like a good way to increase the overhead of iteration and generate more work rather than reduce it.

It's confusing because some tools such as threads can be used to address both the problem of parallel work and the problem of parallel waits.

[1] edit: quick example in lua:

    -- In fork, write to the blocking end of a pipe
    local r = formfork(function(w)
        local work = fibonacci(10^10)
        w:write(work)
        w:close()
    end)

    -- In event loop, read from the nonblocking end of a pipe
    readline(r , 1024, function(r, line)
       print('got work', line)
       close(r)
     end)


The main issue preventing widespread use of underscore in nodejs land is that it isn't set up to accept node-style callbacks, hence you can't mix it with asynchronous code. Eg. Mapping an async function on each member of the array, the async library provides similar functions to underscore while providing callbacks for completion.

The same applies to using normal JS constructors, it's tricky to get a completion callback - I've been doing stuff like this:

    Klass.create = function(args, callback) {
        var instance = new Klass(args);
        instance.initialise(callback);
    }


> I've been doing stuff like this

Why use callbacks over 'return' for anything other than IO and waiting? If there's not two out of sync parties, there's no need to tell another person to call you back, and no advantage to performing an extra function call.

re: yummyfajitas: that does not use a callback, it returns a list. And if retrieveURL is nonblocking, then iteration is nonblocking.


> And if retrieveURL is nonblocking, then iteration is nonblocking.

Something I mention in the article/paper/whatever is using operations that are technically blocking to call functions that happen asynchronously. So there are certainly uses for sync iteration tools (like _.each())

I think it depends on where you put the async assumptions. Do you know/assume that the function itself takes care of the async aspects, or do you know/assume that the caller must take care to set up that structure?

If the answer is the former, then use whatever structure you want, because the async is taken care of for you. If the answer is the latter, perhaps you need a iteration function that works asynchronously.


> So there are certainly uses for sync iteration tools (like _.each())

_.each() is NOT blocking, anymore than "var x = 1" is blocking, "callback(error, result)", or asynchronous_operation(callback) is blocking. It introduces NO synchronous waits. It cannot magically decompile your nonblocking system calls, and recompile them as blocking system calls or insert "while(true)"s. You have to explicitly add the blocking code yourself.

> Do you know/assume that the function itself takes care of the async aspects, or do you know/assume that the caller must take care to set up that structure?

It's still nonblocking. Whether one figured out how to pass all the parameters to a function and complete their intended code is a different question then whether or not completed code is blocking. Is this code blocking? == Does this code introduce undesirable synchronous waits and stalls?

Seriously, write a simple C program and prove all of this to yourself.


_.each blocks. Yes, you are right that it does it in the same way that "var x = 1" blocks.

You cannot interrupt "var x = 1" and you cannot interrupt a javascript forEach while it is looping over an array. With a really large array this is going to cause problems, like that infamous fibonacci example.

Seriously, try it. It's not like a C program.


The example was intended specifically for when you want to do some IO in the initialisation of a class.

However, by having a callback interface the caller doesn't need to know whether a particular call involves IO or not - and you're also free to introduce IO if needed.

A useful example would be using some sort of 2 level caching, where some stuff is in memory, and other things are in a persistant store out-of-process.

Also, consider the following as an example of a use-case for a callback/async aware map function

async.map(list_of_ids, fetchURLFromAPI, function(err, urls) { request.respond(urls.join(",")); })


I believe the use case is stuff like this:

    _.map(listOfURLs, retrieveURL)


"Useful Javascript stuff I can NOT use (because it implicitly blocks): Underscore.js"

Huh? So what are we going to hear next: "Don't use prototype inheritance because chain traversal implicitly blocks"? Idiotic advise.

How about: Don't hold the event loop for too long. The faster you return, the faster you can serve.


Maybe one day someone will invent a mechanism that can assign some kind of time slice to each event being processed, and if it doesn't return control to the master event coordinator, it could preempt that task somehow, storing the context in which it is executing, and switching to a different one: that way we could write code without having to worry about this kind of stuff.

Thinking about it more, while most people are using these event loops to process unrelated web requests (the data being shared between these different contexts likely being small), we'd still run into mild data corruption issues. :( I guess we'd also then have to figure out a way to protect these critical sections of our code... maybe some way to lock people out of your data while you are using it.

(Silly pipe dream, I know.)

(edit: I've been thinking a lot about this idea, and I realized that once we figure out all of those critical sections in the code, we might actually be able to run multiple event loops on the same machine at once, taking advantage of multiple CPUs or other types of execution core, without having to make any other serious changes to our code. I'm starting to think someone should actually work on this.)


Great information, horrible website. I dont expect every whitepaper, website, or blog to have world class design, but this is information is horribly displayed. There is no reason for a website in 2011 to look this way.


It was originally written in OmniOutliner, then exported to HTML. I'll see about tweaking the CSS...


One of the things I did was write a defer function (ie process.nextTick) and just threw it anywhere I thought some operation could potentially be blocking. This requires an analysis of latency vs bandwidth for your application (for me, extra latency was acceptable and bandwidth was at a premium). This also allows use of underscore libraries, albeit with extra defer calls everywhere.

Unfortunately I could not convince my old company to use node.js in production because I was very curious to see how the extra function call overhead compared against the extra bandwidth.

On another note, I was surprised that the eventEmitter callbacks are not executed asynchronously. I assumed each one was run on the next tick. That always struck me as inconsistent with the rest of the API.


[disagreement with a select terminology eg. research, whitepaper; agreement with analysis, best practices]




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: