Live data from Hacker News

The Way of the Gopher: Making the Switch from Node.js to Golang

medium.com

51–60 of 189 posts

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#51
post #21

I have read so many articles which incentivize switching from Node.js to Go and every single one of them (this one included) blabber on about the Node.js event loop becoming congested - This is completely misguided - It only shows that the engineer didn't understand the problem. A single Node.js instance runs its business logic in a single process. A single Go instance can run its business logic in multiple processes…

>> Octo was also running across 2 medium EC2 instances which we bumped up to 4. > Of course if you're comparing a single instance of Go running on 4 CPU cores, it's going to be faster than a single instance of Node.js running on 1 CPU core. I'm not intimately familiar with Amazon, so to me it's unclear what "2 medium ec2 instances" mean. It might mean, 2 m3.medium general purpose instances, in which case they actuall…

Go's speed is comparable to Java and nearly as fast as raw C. The only thing faster is raw C++ - and good luck getting a web project finished in a reasonable time using that.

Node is not a speed demon. It's not as bad as Python, which can be insanely slow. But if you use Node, your server costs are always going to be some multiple of what they could be. (Also applies to RoR.)

Of course the usual argument is that servers are cheap and devs are expensive, so you'll be quicker to market with Node and that's worth the extra cost.

Which is fine until you need to run at speed and/or scale. If you start having to pile on the instances, you will be wasting money. If you're big enough to need hundreds of instances, you will be wasting a lot of money.

IME it's debatable whether Go can't also be faster for development. There's a learning bump at the start, but I found it easier to write code that just works, easier to write good tests, and easier to deal usefully with errors.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#52
post #41

Earlier quoted context omitted.

Totally agree! I would say the same thing if someone had a whole app written on Python and were having performance issues (I definitely wouldn't advise them to switch to Node.js or Golang) - If your ecosystem offers the tools to solve the problem, it's better to use these.

Why on earth are both of you getting downvoted so hard? I am so tired of reading hacker news articles with comments that are so pushed down because of subjective opinionated voting that is so obviously skewed towards one personality type. I have so many thoughts and opinions I want to contribute to these discussions but I have zero confidence it will fit within this type of environment that's been created. It really…

I agree. My voting policy reflects: I reserve down vote for comments that worsen the quality of discussion. Examples are rude or dismissive comments that fail to address any point in the OPs comment or article.

At its worst, I've seen people get down voted for asking beginner level questions. The calibre of folk who post here can, by its nature, make commenting a nervous activity. Getting down voted for trying to expand your knowledge is both harmful and wrong.

If I see a comment down voted without the above properties, I up vote to neutralise whether I agree or not. If the comment contributes to debate or discussion, it should be valued. I know I value alternate perspectives.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#53
post #49

Earlier quoted context omitted.

Recently I rewrote a medium sized Websocket app from Node to Go. Hoping for better performance and having heard a lot of good about Go. Since the application relies heavily on sharing resources between sockets I found out the hard way that concurrent programming in Go is not much easier than in Java. Sure, it has a better syntax and channels but compared to Node where you never have to think about things like these t…

> Since the application relies heavily on sharing resources between sockets... Which resources?

Connections are grouped in various ways. A lot of maps, etc. You can't even use a regular int safely, need to use an atomic for that.

I'm not bashing Go here, the memory usage is about 3x lower (which was the most important for me). And for a static language it's pretty cool.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#54
post #47

Earlier quoted context omitted.

> Node.js on the other hand doesn't need any marketing/propaganda; it just sells itself. If Node.js used language X,Y,Z instead of Javascript, it would certainly not have been the success it was. "Javascript on the server" was the marketing/propaganda . Nodejs came at the right moment, Js was exploding, websockets were exploding, coffeescript and co were exploding in popularity. What Go does better than Nodejs is a b…

I agree with almost all of your post except this: > With Go it doesn't matter if an operation is blocking or non blocking, that fact can totally be abstracted from the client code. No, it can't, and pretending that it can is misleading in a way that allows large teams of developers to cause themselves real problems. The only sense in which this is true is that you can write a Go function with a simple signature like…

> But you cannot write a non-blocking Go function in the same way.

The caller can make function "non-blocking" by wrapping the call in a goroutine themselves. (There's some subtle differences, but they are mostly irrelevant here). For this reason, I'd say there is (almost) no reason to introduce asynchrony in your API in the way you suggest. The rest of your post built on this example seems shaky to me, since it seems built on an example API that doesn't need to exist.

I'd say that "you don't have to care whether your code is async or not" is a overstating the case. I would append the qualifier "unless you're introducing concurrency". Considering that almost no low-level APIs are asynchronous, this usually happens rarely (or happens in low-level code like the HTTP server). Examples that have come up for me: making N parallel RPCs, writing a TCP server. In those situations, you care about async vs not.

In event-loop based systems, it seems like async is in my face all the time, even when doing things that are entirely sequential.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#55

I have read so many articles which incentivize switching from Node.js to Go and every single one of them (this one included) blabber on about the Node.js event loop becoming congested - This is completely misguided - It only shows that the engineer didn't understand the problem. A single Node.js instance runs its business logic in a single process. A single Go instance can run its business logic in multiple processes…

On the plus side, all the devs running to Go from Node, maybe we can finally get all those great libs from npmjs written in go, like Kik, isArray, isThirteen, and so many more classics of robust software engineering that the js community excels at.

Don't forget mature and standardized tools like build, dependency management and distribution

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#56
Originally, the web server was intended to be a very simple program with most of the complications in the browser. Most web app backbends are similar to this where all they do is query a database, cache or other web servers. This type of thing is perfect for node. If you are doing more complex stuff in your backend then go may be more appropriate.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#57
post #54
post #47

Earlier quoted context omitted.

I agree with almost all of your post except this: > With Go it doesn't matter if an operation is blocking or non blocking, that fact can totally be abstracted from the client code. No, it can't, and pretending that it can is misleading in a way that allows large teams of developers to cause themselves real problems. The only sense in which this is true is that you can write a Go function with a simple signature like…

> But you cannot write a non-blocking Go function in the same way. The caller can make function "non-blocking" by wrapping the call in a goroutine themselves. (There's some subtle differences, but they are mostly irrelevant here). For this reason, I'd say there is (almost) no reason to introduce asynchrony in your API in the way you suggest. The rest of your post built on this example seems shaky to me, since it seem…

> The caller can make function "non-blocking" by wrapping the call in a goroutine themselves.

Sure, but if they want the return value then either they need to construct the Future-y wrapper I just described or they need to assemble it together in a collection of other function calls wrapped inside a function that itself is either Future-y or uses a long-lived channel to communicate results.

It is not novel to build up a non-blocking system from purely blocking method invocations. We've been doing that for years: it's called threading. Doing things this way has many advantages when written with appropriate diligence, and I'm not pretending otherwise. However, if you actually care about communicating between these arbitrary threads of execution than you either need Futures or queues (both of which are essentially just channels in Go), and at this point you've got the exact same problems as you get in NodeJS or any other asynchronous programming environment.

> The rest of your post built on this example seems shaky to me, since it seems built on an example API that doesn't need to exist.

I don't think that's fair: as I mentioned above, the fact that you as library author would not write the Future-y extension doesn't mean that the Future-y extension isn't built: you just force your caller to build it. That's fine, it's a perfectly good architectural decision (probably you should't be making those decisions for your user), but it doesn't remove the problem.

> I'd say that "you don't have to care whether your code is async or not" is a overstating the case. I would append the qualifier "unless you're introducing concurrency".

Sure. The thing that matters here is that Node is always introducing concurrency, because Node is concurrent. This is why all Node programs have to care about concurrency: they are all concurrent because their system is concurrent.

This is desperately inconvenient for many one-off programs, which is why I personally don't use Node for anything like that: I'd much rather use Python or Rust or Go. But that was never my argument. My argument was about OP's assertion that "with Go it doesn't matter if an operation is blocking or non blocking, that fact can totally be abstracted from the client code. Writing callbacks is tedious, promises are tedious, co routines with yield need plumbing and async isn't in the spec."

The first sentence is dangerously misleading (while technically true, any system that does that is usable only in that one context), and the second one misses the point, which is that those things get effectively built anyway in any moderate-scale concurrent system in Go.

But my biggest point is this: Go isn't magic in regard to concurrency, and there is a weird amount of magical thinking around Go. Go is a very good language with a lot to like, and I like it quite a lot. But when boiled down to it, Go's concurrency model is threads with a couple of really useful primitives. And that's great, and it works really well. But it's not new or novel.

The sentence "with Go it doesn't matter if an operation is blocking or non blocking, that fact can totally be abstracted from the client code" is equally true if you replace "Go" with "C", or "Python", or "Java", or any language with a threaded concurrency model. There's no magic here. It's the same building blocks everyone else is using.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#58
post #57
post #54

Earlier quoted context omitted.

> But you cannot write a non-blocking Go function in the same way. The caller can make function "non-blocking" by wrapping the call in a goroutine themselves. (There's some subtle differences, but they are mostly irrelevant here). For this reason, I'd say there is (almost) no reason to introduce asynchrony in your API in the way you suggest. The rest of your post built on this example seems shaky to me, since it seem…

> The caller can make function "non-blocking" by wrapping the call in a goroutine themselves. Sure, but if they want the return value then either they need to construct the Future-y wrapper I just described or they need to assemble it together in a collection of other function calls wrapped inside a function that itself is either Future-y or uses a long-lived channel to communicate results. It is not novel to build u…

Go isn't just a threaded concurrency model, it uses an M:N greenthreads pattern. Also, when you say that Go I/O operations are blocking, it is true that they'll logically block a goroutine. However, under the hood, it uses the same libuv-style async IO (or IOCP on Windows) that Node does. An operating system thread doesn't get blocked; the goroutine is "shelved" and woken up again when the I/O is complete. It accomplishes the same kind of thing as Nodejs does, it just abstracts the async nature of the IO away from the programmer. I have to say I like it: procedural execution is easier to reason about.

Re: The Way of the Gopher: Making the Switch from Node.js to Golang

#60
post #34

Earlier quoted context omitted.

I generally agree with the sentiment about scaling across cores and nodes being the same; after all, you're eventually going to need to scale across nodes so you may as well go ahead and get that working. But there are times when it can be a pain. For instance, a node app I run at scale maintains an in-memory read cache that rarely gets updated. When we scale with processes, we end up having to duplicate the cache ac…

Or... just use the battle-tested tool that solves this exact problem well for you. Put your cache into Redis or other in-memory cache.

A network-addressable store such as Redis is not the same as just being able to pull stuff out of memory. A map guarded by an RWMutex is simpler in code, probably more performant for simple access patterns, simpler operationally and effectively can't fail, as the parent points out.

Yes, Redis can be the right tool. But, note how quickly we went from "I just want to run on multiple cores" to "I need to share data between my cluster workers so let's install Redis". It's just a total blowup of moving parts.

Great-grandparent argument reeks of elitism. I know how to deal with concurrency, parallelism, avoiding data races. Doesn't mean I need to waste my time implementing the primitives every project. To say that the language forcing you into multi-process architecture is a benefit, is a pitifully desperate remark. I wonder what single digit percentage of projects actually go into that territory? Maybe even less than 1%? Maybe with a runtime suitable for the task (e.g. Go or Java or hand-woven C++), less than 0.1%, because you do not run into pointless runtime bottlenecks to begin with?

I have implemented such projects, because we needed the workers to be geographically distributed, not because the language runtime fucked us over. This was a decision made in DESIGN not in DEFECT REMEDIATION.

I can think of a handful of "single-purposed" reasons to use Node on the server, such as DOM rendering (and other stuff that is only available in Javascript-land). But a serious project, no way. Not sure how people can stomach the inherent lack of safety in the language, the huge (relative to comparable substitutes) number of limitations in the runtime and the ecosystem churn.

Post reply on HN