
When Node.js is the wrong tool for the job - vmware505
https://medium.com/@jongleberry/when-node-js-is-the-wrong-tool-for-the-job-6d3325fac85c#.gwnpqr3qs
======
klodolph
It seems like a lot of JavaScript developers are repeating things like "more
people know JavaScript so you don't have to learn a new language, which saves
you time". I don't get it. In my experience, if you find a good developer,
they can pick up C#, Swift, or Go pretty quickly, and if you can't find a good
developer, the fact that they already know JavaScript is not much of an
advantage. Even if the developer you hire already knows your language, they're
going to be spending time learning your code base and how your organization
works (shared repo? PRs? Feature branches? Code review? Coding standards?)

That, and nose.js developers seem to repeat the claim that node.js makes
delivery faster… but is it really any faster than ASP.NET, Rails, Django, or
Go stdlib? Those frameworks are so fast for prototyping and delivering bread-
and-butter apps as it is (and some of them let you do multithreading to boot).

I'm also really not interested in how things work for "typical CRUD apps"
because those are so trivial to write in any decent environment.

I'm worried that node.js articles are the same kind of echo chamber that Rails
articles were 10 ago.

~~~
mgkimsal
> I'm worried that node.js articles are the same kind of echo chamber that
> Rails articles were 10 ago.

I tend to agree.

> That, and nose.js developers seem to repeat the claim that node.js makes
> delivery faster…

The qualification was "if your team already knows JavaScript". OK... If the
team already knows Java... building something like this in Java would be
faster to deliver, and wouldn't hit some of those issues the author brought up
(4 vs 1, single vs threaded, etc).

And the whole "oh, you know language X" \- means almost nothing when the
project is more than trivial hello world. Every project I've been brought in
on, the qualifier was "must know tech X". And _almost_ always (there were
exceptions) I knew more about tech X and software dev in general than the
original developers, and the crunch was not "how do I do XYZ in this tech?" it
was "how do I get the other developers to actually use version control?" or
"use version control sanely?" or "document anything?" or "write tests" or
"have test data" or "have a repeatable build process"?

Knowing ASP or PHP or Ruby or whatever, but going in to a project without
repeatable build process, tests, documentation, requirements or version
control, is a recipe for disaster. Stressing the "deliver quickly" aspect of
any language, if you're actually trying to deliver for a business or with a
team of people, is extremely destructive short term thinking. And yes,
sometimes, in rare cases, it may be a necessary evil, but I think it's become
the norm with words like "agile" being thrown around as synonyms for "don't
have to write anything down".

~~~
astinit
As someone still studying and not in the workforce yet, are there really teams
out there that don't use version control?

~~~
the_af
Yes. Also, some huge multinational companies you're very likely to have heard
of don't do automated testing of any kind, as least for non-critical software
(source: my own experience). I'm not talking about TDD or any fancy agile
technique; I mean any kind of automated testing whatsoever.

If you're just out of university or read tech blogs or hackernews, you'd think
everyone these days is doing TDD, pair programming and agile. The reality is
that a lot of major businesses do _none of that_.

~~~
Roboprog
Re: automated testing. This can be quite time consuming to set up in many
environments. Step one: get an Oracle database instance/schema that you can
snapshot and roll back to a known starting state at will. Step two: get
multiple connected app instances into a state to support each test run. Step
three: just drop it, those things aren't gonna happen before you update your
resume :-)

I'm ignoring the approach that says: Let's just spend a LOT of time writing
mocks that stub out 90% of everything that matters, and pretend that the tests
actually show something that matters.

~~~
the_af
Agreed about integration tests sometimes being a pain in the... neck.

However, note I'm talking about something way more basic: many business don't
do _any kind of testing at all_ that isn't done by people manually trying the
system. In the case of internal tools, this "testing" is often done directly
by internal users trying to use the tool. This is way more common than one
would suspect from reading tech blogs (unless one reads TheDailyWTF, of
course).

~~~
mgkimsal
I try to stress to clients that testing will always be done - we have a choice
how much we do up front, behind the scenes, vs out in public, with real
customers, money and data on the line. Still people choose, for a number of
reasons, to have some/most/all of testing just be 'throw it out and see what
happens'.

------
smokeyj
> As node.js is not multi-threaded, we spin up 4 instances of node.js per
> server, 1 instance per CPU core. Thus, we cache in-memory 4 times per
> server.

And why not use a shared memory server?

> Operations started adding rules with 100,000s of domains, which caused a
> single set of rules to be about 10mb large ... If we weren’t using node.js,
> we could cut this bandwidth by 4 as there would only be one connection to
> the Redis cluster retrieving rule sets.

 _Maybe a 10mb json string isn 't the best design decision_.....

Or you know, you could have one node process connect to the Redis server, and
have the local processes read from a shared memory server.. Or you could not
store your rules as a 10mb friggin JSON string..

> When rule sets were 10mb JSON strings, each node.js process would need to
> JSON.parse() the string every 30 seconds. We found that this actually
> blocked the event loop quite drastically

Well then do it in another thread and save it to shared memory. Maybe, just
maybe, JSON strings aren't the tool for the job here.

~~~
leopoldfreeman
NodeJS is a multi-threaded process. You can verify it with top or ps command.
Async methods to decode JSON:
[https://github.com/nodejs/node-v0.x-archive/issues/7543](https://github.com/nodejs/node-v0.x-archive/issues/7543)

~~~
tayo42
The thread thing in nodejs seems very misunderstood. Only the javascript runs
in one thread. I think its libuv that uses 4 threads to do most of the work in
node.

------
rodp
While I agree Node.js isn't the right tool for any job -- just like anything
else, really -- after reading his description of the problem, I can't shake
off this feeling that the main issues he has with performance in this case
have very little to do with Node itself. Parsing a huge JSON string in any
language would block CPU for a while. This JSON then becomes a huge hash table
in memory, so no wonder each process uses up a lot of RAM. I don't know how
these rules are then used but it seems to me he might be better off trying to
rethink how to do shared memory in this case before he simply blames Node for
blocking CPU and wasting memory.

That said, I can imagine other languages (like Java or Go) could still end up
being more efficient than Node.

~~~
jonas21
The issue isn't that it takes a long time to parse the JSON. It's that the
server can't do anything else while it's parsing. In Java, for example, you
could parse the JSON on a background thread without affecting your ability to
serve requests.

Similarly, the memory issue isn't so much that a single copy of the table
takes a lot of space, but rather that they need to store 4 copies of the table
-- because they're running 4 different processes in order to utilize multiple
cores.

Both of these issues are specific to nodejs.

------
tyingq
_" Operations started adding rules with 100,000s of domains, which caused a
single set of rules to be about 10mb large"_

There's not enough detail to be sure, but this sounds more like _" when a
relational database would be a better idea than redis."_

Edit: That is, pushing the evaluation of the rules down...rather than pulling
a kv and walking 10MB (of JSON?) to get to the small number of rules that
apply for the transaction.

------
binocarlos
This is an excellent article which really highlights the underlying trade-offs
when you choose node for your service (i/o bound work vs cpu).

Unless you know for sure what limits you will hit - it makes sense to iterate
quickly and find out. Then, if the service is actually hitting limits (and
probably not the ones you thought) - re-write it in a multi-threaded
concurrent language like go, elixr etc - or a language designed to solve the
actual problems the service is hitting (which might be disk i/o or other
infrastructure level things not language choice)

------
dlojudice
They could have fixed part of the architecture by having a "cache service"
process (4 cpus: 3 for proxies, 1 for the cache service). With that they'd
have a single point consuming their limited resources (memory, cpu and socket
for redis connections), using IPC to communicate between process.

~~~
gaastonsr
I thought the same or even the same 4 cpus for proxies and a shared 1 for the
cache service.

------
neebz
JSON.parse() is one issue we faced regularly. Any large amount of data
fetching could block the event loop and the whole server slows down. It's very
unforgiving.

We go great length to figure out which attributes to fetch and add limits to
all our sql queries. These are best practices but with node they are must.

~~~
beejiu
Node.js isn't great for CPU bound tasks in general.

~~~
danenania
Parsing json is sneaky because it can show up in apps that are otherwise IO
bound (where Node shines) and don't seem like they should be CPU intensive on
the surface.

------
wehadfun
JavaScript is my first non-mathematical programming language and I haven’t
found the need to expand my programming skills to more

-Having a hard time taking anything this guy says seriously

------
yahyaheee
I debated between learning Node and Go for my latest project. I took a couple
days doing beginner tutorials on each, and Go was actually a lot easier for me
to learn. Could just be my background, but I know a couple other people who
picked it up in about a week too, it's surprisingly simple.

~~~
Slackwise
> it's surprisingly simple.

Actually, that's its fundamental design. They reduced everything down to a
very small core with very little features, so things would be obvious and you
don't have to learn or remember much. It's refreshingly simple!

With that said, I'd say they reduced it _too_ far down. There's no generics so
you end up using `interface{}` everywhere which often leads to issues due to
its late binding. Or you end up just using codegen tools, IIRC.

Also since there are no exceptions, you end up with constant checks for error
code returns, which end up usually just being strings and not much else. Not
saying exceptions are the best way to approach error handling, but they do
allow you to reverse through an entire function call stack and clean up any
state along the way, along with adding more granular error information you can
write handlers to react to. Go reminds me of the pain that was C error
handling and juggling error codes.

------
suzzer99
> On each server, rules are retrieved from Redis and cached in-memory using an
> LRU-cache. As node.js is not multi-threaded, we spin up 4 instances of
> node.js per server, 1 instance per CPU core. Thus, we cache in-memory 4
> times per server. This is a waste of memory!

This is completely standard and the only way to do node in-memory caching.
Think of each worker as a completely independent node process, which is only
bound to the cluster by a master process which has the ability spawn and kill
child cluster processes.

~~~
vkjv
> This is completely standard and the only way to do node in-memory caching.

This isn't accurate you can use shared memory. There are a few modules that
implement this. In addition, you can offload the JSON.parse to the dedicated
"caching" process that updates the shared memory.

~~~
suzzer99
Do you have a link that describes an example of this?

Ok nevermind, google is my friend:
[https://github.com/PaquitoSoft/memored](https://github.com/PaquitoSoft/memored)

I can see where this would come in handy. But at 240MB total resident memory
per CPU across 4 node workers that OP describes, I wouldn't hassle with it.

------
stevebmark
re: multiple processes duplicating memory, would a single menmcache instance
or similar solve this problem? I don't have any perspective on how that would
perform at scale vs individual programs reading from application state.
Although thinking about it, each process would probably have to store all that
data in app memory anyway...

------
cbem
It was an very unfortunate decision for Node devs to deep six multithreaded
web workers. A pull request implementing it was ready to go with an optional
flag to enable it but they did not want to support it. So node will be forever
more compute bound to a single thread blocking all I/O.

~~~
gaastonsr
> So node will be forever more compute bound to a single thread blocking all
> I/O.

I don't think I understood your point. But is not Node.js supposed to not
block long lasting tasks?

------
tannhaeuser
I'd also add that node.js might not be the right choice for complex backend
business logic with lots of service calls because of. Node.js' always-async
execution model tends which tends to make things more complicated than need
be.

~~~
tps5
I think "always async" is the main advantage of node.

My general (perhaps wrong) impression is that other languages commonly used in
backends are moving toward async io, usually through maturing libraries.

~~~
tannhaeuser
It's not that I don't like async (I think it's a defensible choice for
JavaScript), but that in my experience backend logic for e.g. e-commerce apps
doesn't benefit from using it. Projects which expose services to web frontends
are often implemented in an architecture where only one-shot, aggregated
service calls (and often times REST-y services) are exposed to Node.js, with
the actual business processing and granular service calls being implemented in
eg. Java web services in a synchronous programming style. I actually like that
architecture because it gives front end devs leaway to define their browser-
facing head server backend, rather than enshrine a dogmatic frontend/backend
architecture upfront.

It's true that other languages add async models (or emphasize those that they
already have), but eg. in the case of Java you're sitting on 20 years of
synchronous library and custom code, and it's not clear moving to async is
worth it at this pont.

------
donatj
"Usually" /snark

------
hitgeek
good detailed write up.

There were probably opportunities for the author to architect the system in
ways that were better suited to node (given that was the chosen platform), but
the architecture choices were not unreasonable by design. These are some good
things to consider when architecting a system, and considering node as the
platform.

I'm not sure I agree that node is the "perfect for simple CRUD apps" though

------
leshow
I think it's a lot closer to 10x as fast for Rust and 6-7x for Go.

------
BuuQu9hu
When WebAssembly comes, what will that mean for the node.js ecosystem?

~~~
kowdermeister
Nothing, WebAssembly targets the browser. Devs who already understand JS could
just easily pick up Node.

------
hmans
Always.

