Clojure, Node.js and Concurrency Fail
dosync.posterous.com
dosync.posterous.com
Node.js doesn't make it easy to share state between processes, but once you're scaling to multiple processes, the jump to multiple machines probably isn't far behind. You'll need to design a distributed algorithm, or just centralize your shared state in something like Redis anyway.
Concurrency failure.
For request/response type of scenarios (unlike batch processing) you basically have two options: (a) Use a multi-process shared memory design (or even just the file cache), which is very cumbersome to implement because you cannot use pointers and hence none of the data structures in your favorite language's library. (b) Use shared in-process state with powerful concurrency primitives. That's what clojure helps you do.
You cannot avoid to choose between these two architectures by using distribution across machines. Distribution is built on top of whatever you do on a single machine.
Expanding your network IO handling code to use multiple cores sounds like fun, but it's the wrong way to do things.
Network handling code is not CPU heavy. Have a single process handling the IO, and for CPU heavy tasks hand off to other threads, handled with callbacks.
I also wish we'd stop benchmarking these sorts of things in NNk requests/second. As if most use cases will get anywhere near that in production.
Unless you're CPU bound, there is absolutely no point.
I love Clojure, but it's concurrency constructs aren't that relevant to most web programming. Which is fine, but let's keep in mind that developing a web app with Clojure is painful now due to a lack of libraries / a large enough community. I'm eagerly awaiting the day that changes.
I agree with your comment regarding Clojure's concurrency constructs. I am building my web app to run on more than one machine. I have not yet had a need to use any of the concurrency constructs.
(On k-means clustering, in particular, Clojure-style transactional memory gives an almost linear speedup with the number of processor cores, without significantly changing the code. That's worth something.)
Of course, for examples like these, the tricky part is designing data structures that don't have many inherent memory conflicts. Red-black trees, for example, are tricky because the rebalancing transformations tend to step on other threads' toes.
When you encounter a new paradigm like this, its very important to work in it for at least a little and give yourself some time to untrain before writing off the usefulness of the paradigm. I work mostly in servers and the answer to your question is really quite a lot. (Unfortunately, I'm in the worst place, I untrained myself and am now able to see where this is useful, but not really able to use it at work. Sad.)
I am reminded of this I saw yesterday: http://www.reddit.com/r/programming/comments/cq6wo/why_dvcs_...
Pros:
1. Makes it very easy to turn an existing threaded
application into a multi-node application, often
no code changes required. Killer feature.
2. Integrates seamlessly with Spring and Ehcache. Perhaps
less relevant for Clojure developers but a big boon
for JEE people.
Cons:
1. It uses multicast for replication. This is an
implementation detail, not a conceptual drawback, but
it's a pain to set up when your machines live in
different network segments.
2. Can potentially transmit stupid amounts of data over
the wire. I suppose this is part design, part configuration.Some ideas for Terracotta friendly design I can think of, right off the bat:
1. If this node is the only one using this particular shared data structure right now, it gets to operate locally without network transactions until it's done.
2. If you consistently use a small part of a large data structure (eg: a hash), you'll only have that small part "paged in" on that one node. The whole data structure can be larger than RAM. Thus, node affinity can be important.
3. Be careful how much work is done inside a synchronized method of a shared data structure. It should be a medium amount. Too much, and you'll make other users wait, too little and you'll thrash the network.
On the physical hardware I tested node scales very linearly with the amount of cores added.
On Ec2 I have seen very strange results / bad performance. There is something seriously odd here, and I suspect it has to do with Amazons virtualization.
On the box the author tested with, I would expect node (or nginx) to easily serve 50k req / sec.
I'll have to do some more research to figure this out.