Hacker News new | ask | show | jobs
by sgt 4124 days ago
After refactoring the alert feature into a Go microservice, they’ve seen the following improvements

The same number of workers (that required 12 GB in Ruby) now requires just 5 MB of memory.

Not that I don't approve of pro-Go articles, but doesn't this maybe indicate that something was wrong or inefficient in the Ruby implementation?

3 comments

We did some measurements with Padrino applications in the wild and found that all of them could be improved a lot in relatively simple ways. E.g. by not loading multiple JSON parsers. Padrino being a rather slim framework on top of Sinatra, that often meant getting below 80mb memory usage (on a full stack!).

Our takeway was that the (Rails) monolith model tends to put a lot of things into one process space. Controlling library loads can be a chore.

Exactly, Ruby app was monolith so it was consuming much more then this single feature would require. Now there is double benefit Go already uses lot less memory and its microservice. Plus with Ruby we have to run multiple processes so whatever memory single process consume * # of workers.

I could have rebuilt in Ruby and that was my first thought but deploying and maintaining Ruby apps are lot harder then you can imagine. I didn't like idea of maintaining lots of small ruby apps. Once you deploy single Go application, you might not want to deploy another Ruby app.

Not going to argue your decision, the process of splitting up is also a chance to change the platform.

We do a lot of non-Rails Ruby-work and did implement Microservice-Architectures in it and found it rather unproblematic. The trick is not to start with a full-stack and build down, but to start with a small kernel of an app, deploy it, add something, deploy it, iterate, iterate, iterate.

Plus with Ruby we have to run multiple processes so whatever memory single process consume times # of workers.

I don't use the Ruby stack, but if forking happens after loading libraries, this is not true, since most UNIXes (such as Linux) use COW memory pages when forking. So, it may appear that you use N times the memory of a single process, but most of their memory pages are shared.

See fork(2).

Yes, but garbage-collected languages with embedded markers (which Ruby used for a long time) are not at all COW-friendly (because GC runs touch the pages).

Only in recent Ruby versions is that not a problem (and was changed for exactly that reason).

But yes, your memory report might be read wrong.

YMMV, my production system, each unicorn worker consumes about 80M RES, so for 12G that's roughly about 150 workers, not that many.
I've seen a factor of 10 or so reduction in memory use for similar implementations in Go and Ruby, so it's not entirely down to the implementation.
Ruby or Rails?
Rails of course :)

I haven't done a thorough or systematic comparison, but I'd expect slightly slimmer but similar results with Sinatra apps given the 80MB figures you quote above, the rails processes were around 120MB on average, Go 6-10MB and scales without extra processes. Obviously the smaller the framework you load for each process, the less memory you're going to use, but there are also architectural differences in the languages which seem to add up to significant memory usage in the case of Ruby.

Because to scale ruby you'll typically add more processes, and to scale go you'll just let it start another thread (the http server does this for you), that difference adds up to a significant difference when you have a lot of traffic.

That's not to say that Ruby is impossible to scale and I love Ruby the language, but it is a bit slower and a bit more memory intensive than Go.

Then say Rails. Rails doesn't care about memory. There are frameworks of similar targets like Rails that tend to use less (e.g. Padrino) and care. We did have an effort pushing down unnecessary memory use of the base profile and we gained in slabs.

Also, you can move to JRuby if you want a one-process/threaded model, which will get memory-conservative very quick. A JRuby thread will quickly get you into the ranges that you quote for go + the base cost of the JVM.

The figures you quote above are better than Rails but still almost an order of magnitude worse than Go. Clearly there's something else going on. I'm not saying everyone should abandon Ruby and move to Go (and I think neither is the article), but we should be aware of the tradeoffs involved in each tool choice (Ruby, Ruby with JVM, Go etc).
You are still comparing apples with oranges. Go and even Padrino (which is a full-stack in a full blown install) are not even the same category.

Point being that memory comparison even in the Ruby/Rails space are incredibly problematic, often influenced by the runtime (and heavily so). Still, the discussion is incredibly simplistic (for example by assuming that you are running unicorn/fork).

If a discussion about memory issues does not include an exact reference to the runtime and implementation of the system used, I would consider it void immediately.

Same for Go.