From Node.js to Go
blog.scaledrone.com
blog.scaledrone.com
Edit (and shameless plug): http://assil.me/2015/11/07/roadomatic-node-vs-go.html
You might need to read the previous post (and only other one!) to understand how the GeoJSON geospatial query works.
At best it's only a very temporary traffic boost, though, unless the content is legitimately interesting enough to warrant return visits.
I think that's what HN calls a "voting ring" and attempts to detect and counteract.
If you have any sort of long lived mobile connection, you will encounter half closed sockets due to mobile network quirks. At scale you really want to clean these up. To do so, you need an application level timeout and you want to listen to the PING/PONG control frames to reset that timeout.
Could golang.org/x/net/context.WithTimeout be used to handle this?
https://eladnava.com/write-synchronous-node-js-code-with-es6...
[0] http://www.ecma-international.org/ecma-262/6.0/#sec-promise-...
See:
[1] : http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
Manual promise transpilation is better than manual callback transpilation, but it's still manual transpilation. Why are you working so hard?
Sometimes I get mistaken as an advocate for Go. I'm not really. What I am an advocate for is using languages where you don't need an inner-platform of promises because the language is already that color. Go just happens to be the most familiar Algol-esque of the bunch right now. Don't want to learn Go? Go learn Erlang or Elixir. Or maybe if you're feeling saucy, Haskell. Or some of the other choices popping up lately. If you're writing network servers you will not be able to understood how you ever functioned without this.
The only excuse for mixed-style codebases is a codebase in the process of being converted from one style to the other.
The basic idea is that you wrap the generator in a sort of trampoline that turns it into a promise. This 'trampoline' detects whenever the generator falls down to it (via some `yield` of some Promise) and then waits on that promise before resuming the generator-trampoline combo. The promise then returns its result directly into generator.next(), where it becomes the value of the `yield` expression, thus within the generator `x = yield myPromise()` creates a new Promise by calling myPromise, suspends execution until that Promise completes (by dropping down to the trampoline, which bounces us up into the myPromise().then()), and finally gets the value and assigns it to x.
You have just one scope, the scope of the function* of the generator, in which all of the results of all of your Promises live.
That said, for anything like a website or an API you probably won't need to worry much about channels or concurrency at all. The stdlib handles each web request on a different goroutine, so you write synchronous code, yet your server is concurrent.
http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-...
function node(fn, ...args) {
return new Promise((accept, reject) => {
args.push((err, ...vals) => err? reject(err) : accept.apply(this, vals));
fn.apply(this, args);
});
}
What does this do? The argument of `new Promise` is a function which takes two functions, `accept` and `reject`, as its arguments. The function will be immediately run and is meant to do some stuff, possibly asynchronously, and then eventually call the first one of these on success or the second on failure. (One thing that takes a while to get used to about Promises is that generally the asynchronous thing starts when the Promise is created, so execution is not deferred ever.) We just take the args that it was called with, and append our own usual-Node-style callback to those arguments, asking it to pipe the Node error to the reject() function or the successful values to the accept() function.Then you need a sort of "trampoline" to convert a generator-yielding-promises into a single promise. That looks like this:
function go(gen_maker) {
var gen, goNext;
gen = gen_maker();
goNext = val => {
var item = gen.next(val);
return item.done? item.value : item.value.then(goNext);
};
return Promise.resolve(undefined).then(goNext);
}
Now what does this do? It defines a semirecursive function goNext which will hand itself to any new promises. The goNext function just runs the generator for one step with its argument, and looks at what it got out of that generator. If the generator has completed, it says "I'm done, great, return that last value!" and if it hasn't completed it says "okay, I have a Promise here, I need to tell it that when it's done computing what it's computing, it should goNext() with that value, to hand that value and control back to the generator." Finally after defining this magical function we begin the first gen.next(undefined) run by creating a Promise which immediately resolves to undefined, and telling it to goNext() with it. Of course, `gen` is the output `GeneratorFunction` that is generated by running a function* generator-maker on some arguments.With those two basics, you do eventually de-nest everything to just one layer of asynchronicity. You of course cannot do better than this to get truly synchronous code, but you might not need to if your last expression would have been undefined anyway.
For example, here is how you then write something which accepts two arguments and appends the first to the second, using a 10MB buffer in memory. (Yes, I know this is just `fs.createReadStream(process.argv[1]).pipe(fs.createWriteStream(process.argv[2], {flags: 'a'})`, with some messing around with the high-water mark; it's just to prove a point.)
// In this comment, we pretend node and go are defined as above, that we have
// checked for exactly two args on process.argv and logged some usage
// instructions if not, etc.
var fs = require('fs');
const buff_size = 10 * 1024 * 1024;
go(function* () {
var in_file, out_file, buff, count;
buff = new Buffer(buff_size);
in_file = yield node(fs.openFile, process.argv[1], 'r');
do {
out_file = yield node(fs.openFile, process.argv[2], 'a');
count = yield node(fs.read, in_file, buff, 0, buff_size, null);
yield node(fs.write, out_file, buff, 0, count);
} while (count === buff_size);
yield node(fs.close, in_file);
yield node(fs.close, out_file);
});
In terms of larger libraries that do the same, so that you don't have to roll your own `node` and `go` functions, see e.g. http://taskjs.org/ and some other similar projects.I see it a lot from backend developers who never really knew JS that well and then moved on to the next shiny thing (right now Go, but Haskell, Rust, etc) without ever having a good grasp of WHY they has issues - instead they blame node/JS.
In a year or so I fully expect all these articles to switch to "why Go sucks" as the cycle repeats.
This "oh it's AOT compiled so it's faster" mantra is not accurate causation.
Python for example is also compiled into *.pyc files, but it's still slower than go.
The real reason why Go would be faster than JS, is not as much about compiled/not compiled but statically typed vs dynamically typed.
In statically typed language the types are already predetermined early on (during compilation) so the code no longer has to perform type checking at runtime.
On one recent project, the go memory profiler was showing about 35MB allocated while top showed memory usage at 500MB to 1GB. In another instance the performance profiler would show microseconds elapsed while the user experienced tens of minutes with no response from the program.
I really think they should consider including a function level profiler instead of just a sampler, as well as provide more insight/control over all the goroutines that are generated.
Just saying golang is better than node.js may not be saying all that much.
Besides the lack of data to illustrate their point, their point is well know and has been well documented for decades. The reason people use these higher level, higher overhead languages is generally time-to-market, maintainability and development cost.
I don't so much care about the performance difference data, I can measure that myself easily and cheaply.
Measuring frustration, development time and bug-hunting time is something that would be expensive to measure (you have to build a real world product in both languages with users and maintain it, which they seem to have done). Yet this part is glossed over and never quantified in the article.
Node.js and go are 7 years old, so no it has not been documented for decades :p But your comment is still right.
Maintainability cost is probably more directly related to how clearly one can express the high level concepts the program must deal with in the language in question. If the concepts are things like manipulating huge arrays of numbers, bit operations, interfacing with the system APIs, or other tasks suited to native langauges, then using a native language may be better. If the concepts are things like manipulating large dynamic data structures like strings and maps, doing a lot of asynchronous operations, and communicating with external services via JSON APIs, then a dynamic high-level language that easily speaks JSON may be better.
Development cost is a mix of the above.
I think this is a better characterization than "high level languages provide better time-to-market, maintainability, and development cost". For example, while this may be true for some projects, I know of many others where the better time-to-market, maintainability, and development cost comes from using a much lower level language.
It's all engineering. Measure. Analyze. Weigh the trade-offs. Make decisions. Monitor.
Also what was the cost in time and man hours of conversion?
> Node.js has been around the block longer it boasts more than double the modules
What about NodeJS + Rust (for the heavy tasks) via Neon? https://github.com/rustbridge/neon
Yes, Node.js has more third-party packages available, but the quality of Go's packages is usually higher. Also, you can do a lot with built-in packages in Go compared to Node.js.
If they needed something better than Node they may simply be doing it wrong and I'd lean that way first before subscribing to Go being the solution. Which has me thinking it's the most convenient band-aid nearby.
There are alternatives to the language/platform hopping. My case would be a decent example. Most of my stuff is in Python2. I have a newer Django project that I'm vetting CPython3 with, but will remain writing long running CPU intensive services in Python2 on PyPy. I see CPython + PyPy as a pretty reasonable approach and have been able to scale quite well for most situations which don't involve battling millions of concurrent users and petabytes of real-time data analysis. There's also the advantage of a single language backend and developer productivity. Node.js callbacks never matched gevent or Python3's async I/O and Go is adding yet another language to the stack where PyPy (or Node in their case) would likely handle it. Difficult to say without looking at how they've implemented everything but I have doubts whatever they're doing couldn't be done with resourceful coding. Most likely, these are people who want an excuse to try something else and they're just excited.
Given the self-professed extremity of their case, Node, Python, Go, et al would not resolve something so seriously memory-bound well enough that it wouldn't be worth calling out to a non-GC'd library. I would steer away from GC-anything and drive a stake through the heart. Writing a small limited-scope Rust extension to do what they needed and called that from Node.
I'll definitely agree here. It's not obvious right away how to write examples that get included in the generated documentation, and definitely unintuitive that it's a completely different process than the comment-based approach.
I started my project as a collection of (micro?) services, all talking to a distributed kv store, containers all orchestrated, etc ... That's just how we build things nowadays right? Well one day I said screw it and rewrote the whole thing as a Go monolith. Including the DB layer, which now runs on BoltDB, an embedded k/v store. So now I have a web analytics system, reverse proxy, rest api, pixel api, license key server, reporting engine, emailing service, and a custom stripped down time series database (uses Bolt as a storage engine) complete with a custom SQL-like language (stripped down version of InfluxQL). It all exists as a single binary, which magically works on Windows, OS X, and linux, with zero run-time dependencies. This has allowed me to provide my customers with a self-hosted version of my software if they don't want to use my cloud service. My cloud service is just a differently configured version of the self hosted binary. Compilation takes just a few seconds.
Regarding performance. A single node handles thousands of requests per second. It's possible for me to optimize that further (I suspect by a lot), but I don't need to. And up to 1TB single node storage. Again, there have been larger BoltDB deployments but I simply don't need to push it very far since I can easily run multiple nodes when the time comes, each handling some subset of user accounts.
Another delightful thing is my dependency situation. I have 8 library dependencies in total, and have never even had to think about which version I'm using past the initial download. They're mostly lean and mean and stay out of the way. Things like CORS middleware, JWT library, request mux, Bolt, and a few others. And they all work together since they all conform to Go's built-in defined interfaces. What I appreciate about the library ecosystem is that most of the libraries you will use are _done_. Go has no dependency versioning, semver, etc... It sounds concerning at first but I was pleasantly surprised about how much of a non-issue that is in Go. I suspect that a project of similar scale would have resulted in many lost hours in Maven, Gemfile, or package.json.
Also, the concurrency makes me happy. I've never been one to multi-thread my programs. But Go makes it easy to build concurrent pipelines and services without needing a team of engineers to review it for correctness.
Anyways I'm rambling, I guess I should list some of the frustrations and trade-offs I made. For one, yes you will miss generics at first. It seems like a weird thing to not have in the language. It kind of takes getting used to, but you will figure out how to write the same functionality in terms of interfaces. It's certainly unique, a bit more typing, but in the end, things still get done. But, yes, you will have to shift your mind to a different programming style. Which some find annoying. On the plus side, at least for me, Go is one of those languages that has changed the way that I think about programming.
Also, not really speaking to the Go language, but if you're considering Bolt as I mentioned, don't expect it to handle replication or distributed fanciness for you. I'd have to say the biggest tradeoff I made is switching to a scheduled data file backup system in the place of an actual database service. With naive scheduled backups, you might lose a few minutes of data in the case of a total node failure. This can be improved on my end, but that starts to get pretty involved.
... I don't know why I just wrote an essay. Hopefully this encourages someone try out Go for their next project.