
From a Ruby monolith to microservices in Go - nexneo
https://sourcegraph.com/blog/live/gopherconindia/112656568167
======
matthewmacleod
Rewriting a monolithic platform as a set of microservices will almost always
result in improvements from every perspective. You're replacing legacy code
with new, cruft-free code; you're de-coupling parts of the system; you're
building a more scalable design.

For example, if you're in the situation where you've gone from 12GB to 5MB of
memory consumption, then you have a design issue and not a language one.
That's not Ruby's fault.

We are also in the process of converting several monoliths into a
microservice-driven system, but we are doing so with Ruby. It remains great
language to use for building web apps, and I suspect some of the push back
against it is caused by a dislike of the cliché giant Rails monolith that
we've all come across, with hundreds of gem dependencies and so on. Rubby and
Rails have a habit of encouraging such bad designs primarily through ease of
use, but it's hard to see that as a problem with Ruby.

Go is a good tool for doing the same, and single-binary deployment is pretty
magic. I'm sure it'll be supported for ages, so if you want to re-tool to use
that then go for it! But Ruby's a viable option too, and it's important not to
place blame for bad app design in the wrong place.

~~~
apunic
I feel uncomfortable when reading your post. Passive aggressive, attacking the
OP's architectural decisions, assuming that he didn't have any clue building a
proper Ruby system. I am not a fan of Go, not at all, but I know that Ruby has
significant issues which the OP also outlined quite well and you seem to just
ignore them.

But Paul Graham knows exactly why you are sticking to the past (just replace
'Blub' by 'Ruby'):

 _As long as our hypothetical Blub programmer is looking down the power
continuum, he knows he 's looking down. Languages less powerful than Blub are
obviously less powerful, because they're missing some feature he's used to.
But when our hypothetical Blub programmer looks in the other direction, up the
power continuum, he doesn't realize he's looking up. What he sees are merely
weird languages. He probably considers them about equivalent in power to Blub,
but with all this other hairy stuff thrown in as well. Blub is good enough for
him, because he thinks in Blub.

When we switch to the point of view of a programmer using any of the languages
higher up the power continuum, however, we find that he in turn looks down
upon Blub. How can you get anything done in Blub? It doesn't even have y.

By induction, the only programmers in a position to see all the differences in
power between the various languages are those who understand the most powerful
one. (This is probably what Eric Raymond meant about Lisp making you a better
programmer.) You can't trust the opinions of the others, because of the Blub
paradox: they're satisfied with whatever language they happen to use, because
it dictates the way they think about programs._

Source:
[http://www.paulgraham.com/avg.html](http://www.paulgraham.com/avg.html)

~~~
matthewmacleod
I certainly didn't intend to be passive aggressive or attack, apologies if it
came over like that.

The point remains though; it's not that the poster doesn't know how to build a
'proper' system in Ruby, but that the frequent articles we see with the theme
"we rewrote our system from language X to language Y and it's much better!"
are rarely helpful, because (I can't stress this enough) _architectural design
is vastly more important than what language you are using to implement your
system_.

This leads to a dangerous bandwagon-jumping faddism where developers start
jumping on to the next big thing, because they assume it will solve their
problems. We saw exactly that with Ruby, for example; developers assumed that
they could escape from the verbosity and enterpriseness of Java just by
changing language, ignoring the pitfalls that could be experienced.

PG's 'blub' example is a very useful allegory, but it doesn't apply here. I'm
not at all suggesting that Ruby is the perfect solution to all problems, or
that using Go is wrong – I use both of them! Just that saying 'Go is better
than Ruby for writing web apps because we reimplemented everything and it was
faster' is not helpful.

~~~
tessierashpool
this is a very good point. and this:

 _We saw exactly that with Ruby, for example; developers assumed that they
could escape from the verbosity and enterpriseness of Java just by changing
language, ignoring the pitfalls that could be experienced._

this _worked_ for a lot of people until they got to the architectural stuff.
but going from Java to Ruby (infinitely nicer) is distinct from going to J2EE
to Rails (easier to get started, harder to keep going).

------
bonn1
Many folks in this thread are expressing that it's the OP's fault and not
Ruby's.

The OP made his post as objective as possible, politely written and nobody is
loosing his face, neither Ruby as a language nor the Ruby community. But
people are still resentful and fire back.

I believe it's not a discussion about Ruby anymore, its strengths, its
weaknesses and wether Ruby still fits in 2015—no it's the fear of people that
their core competence might be less worth in future. That all the time and
energy they invested all the years into one language might be not a good
investment anymore. The fear that better tech will replace their tech and that
they have to start at zero again.

I know I am going to be heavily downvoted for this post. Before you downvote
just think a second again why you'll downvote me.

~~~
matthewmacleod
I don't think that's a reasonable view.

The point is that articles about rebuilding entire systems in different
languages conflate two things — an architecture change, and a language
changes.

This often results in the sort of appraisal you see here – 'Go is better for
us than Ruby, because we completely rebuilt a system and it's now better'.
That's got some value, but it's frustrating to see it used as something of an
absolute metric regarding the suitability of a language when it's conflated by
the _far more important_ architectural changes.

So I think your view that there are a bunch of developers who are afraid of
the future is a little shallow; rather I suspect there are a bunch of
developers who saw exactly the same sort of thing happen when Ruby was
becoming popular, and realise that extracting helpful data when there's an
ongoing fad ('we're jumping from X to Go') is rather difficult.

~~~
jbergens
But a change of language might bring you more tools, for example to handle
thread-pools or process pools (service worker pools). Some of these things
might be much easier to do in Go (or Java or C#) than in Ruby and if that part
was a reason the new version works better the language change did help. I am
no saying it was impossible to improve performance with Ruby but they might
believe it was easier with Go. I still think a monolith app can be more
performant than micro services in a lot of situations but you might have to
use things like thread pools/channels.

------
bobofettfett
Ruby is the new Java everyone is bashing. Go is the new Ruby everyone is
hyping.

If you replace every mention of Ruby with Java, and every mention of Go with
Ruby in the comments on HN, then you've invented a time machine to go back 10
years.

And if you do this with C, welcome to 1995.

~~~
sheepmullet
We are a fad driven industry.

They could have rewrote it in java in a similar amount of time, and with the
same results. And they could hire from the large pool of expert java devs. Of
course that's not "cool".

~~~
tomohawk
It would not have solved the deployment issue. It is very nice to only have to
deal with dependencies once, when building (when using go). In java, you have
to deal with both build time and run time dependencies. It's really nice not
having to lug around and package up a jvm, jars, ...

Each java based microservice requires its own packaging of jvm, jars, etc.
Without this it is not easy to replace individual microservices, as the cross
dependencies between the microservices will take away a big advantage of going
that route.

Go provides this level of deployment separation for free by default.

------
dkarapetyan
So many wrongs and confounding variables that I don't even know where to begin
but I'll try anyway.

1) Ruby is a complex language: Ruby is quite simple. Everything is an object,
every method call is a message send that follows the exact same protocol of
dispatch. Also, completely irrelevant to the main thrust of the article.

2) Memory consumption and speed: Can't really refute anything since by their
own admission they were running a monolithic system.

3) Concurrency: Plenty of options. I like celluloid personally and if you
switch to JRuby then you can do all sorts of other stuff as well with
automatically managed thread pools.

4) Backcompat: Ruby is not an enterprise ecosystem. Some people call that
speed and agility. In their case they really should have gone with Java since
they are basically serving an enterprise customer. This is not a knock on
Java. I think the language and its ecosystem is by design made for stuff like
this.

5) PDF generation: I'm guessing the Ruby code was calling some C library to do
the rendering and they swapped out all of it for Go. I don't know what library
Go is using to render PDFs if it is the same library then there is something
interesting happening here. If it is a different library then they are
comparing apples and oranges and there's nothing to see here.

6) Alert service: Completely bound by I/O so doesn't matter what language you
use. Bad design for keeping many processes around because you only need to
spin them up during spikey times which can be easily handled with better
engineering. Also eventmachine and celluloid for the worker internals would
have been another option so again they didn't do their research.

7) Gradebook service: No idea again. Thin on details so nothing to refute
really.

8) Deployment: Sure. Why not. Single binary is easier or JRuby and a single
jar which is just as easy.

In conclusion, they re-wrote everything and I'm sure removed a lot of cruft in
the process and called it a victory for Go. In a year or so they're going to
run into similar issues and Go is not going to save them because from what I
read all I saw was bad engineers blaming their tools.

~~~
patio11
This comment is more aggressive than comments should be on HN.

I would exercise a bit of humility when commenting about other people's
architectural decisions: they have bled all over that codebase and
infrastructure, but I have not. They see it in their dreams, and I do not.
They may from time to time decide to tell the community about their
adventures. That is an excellent opportunity for me to learn from another
engineering team's perspective and experiences, rather than an opportunity for
me to demonstrate my intellectual superiority over them. They do not, as a
function of having written about their experiences, owe me responses to my
off-the-cuff analysis of their engineering tradeoffs.

~~~
dkarapetyan
In that case it's excellent story telling and nothing more. We're an
engineering discipline and use case studies in engineering disciplines are
measured by different standards, humility and hubris aside. You can bleed and
dream about your architecture all you want but as a community we should be
more analytical than that and not fall into the all the usual mental traps
just because we bled for some engineering decisions. Teasing apart confounding
variables and showing pros and cons of the various approaches and adding a bit
more detail about the actual libraries would have made this a much better
post. If he had provided all those details then some of us could have double
checked the numbers with some benchmarks and learned from it but that
opportunity was squandered.

Once again, I'm not aggressive but I am critical. nexneo has since addressed
all the issues people have brought up and if he'd done that initially then I
would have been much less critical.

------
Argorak
Every time I read something like that, I get the feeling that "Monolith to
Microservices" is the pattern and the language doesn't matter.

At the beginning, a Monolith has huge advantages: easy to deploy, all in one
place, smaller operational overhead, easier to manage with unclear
requirements.

Later, it's easier to find out what you can split out and you can learn how to
route and manage things piece by piece.

~~~
nexneo
You exactly captured my sentiment, when I started doing this I don't realized
all benefits. Because of Go's simplicity in deploying and maintaining, many
small apps doesn't add much overhead. Now you can scale individual component.

Only risk is, you break things into many component then you should so balance
is required.

~~~
Argorak
As I said below, if you tread off the full-stack-track, you get a lot of the
go-like things in Ruby as well.

My point being: I usually rate architectural changes as more important as a
change in development details (and the programming language might be a big one
there, but it still is one, IMHO).

I'll tell you why: a lot of the sentiments people bring when they now switch
from Ruby to Go, I've heard before. When people started Ruby - I was already
doing Ruby for ~2 years before Rails even came out at it got me into the
position of saying: "just you wait until you see the bad parts". They
attributed a lot of things to Ruby, while they were really changing their
development model.

------
sgt
_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?

~~~
Argorak
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.

~~~
nexneo
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.

~~~
danieldk
_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)_.

~~~
Argorak
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.

------
tonyarkles
Over the last few years, I've tried implementing a few things like this in Go,
but the problem I keep running into is the tooling. It'd be awesome to know
which modules they used for things like URL routing and application structure.
There seems to be a lot of options with no clear winners.

Rails is opinionated, yes, but I'm generally pretty satisfied with their
opinions, and it's nice to have a full Batteries-included package.

~~~
nexneo
Initially I used [https://revel.github.io](https://revel.github.io) because I
wanted to use many inbuilt things and hot reload.

Later I started use just standard library with
[http://www.gorillatoolkit.org](http://www.gorillatoolkit.org) and used
fswatch for hot reload. Now there are lot of alternative in routers but no
clear winner.(and thats good thing) I will suggest go with standard library
and simple router but be ready to revert back to framework like revel if that
doesn't workout.

------
saintfiends
From the looks of it I assume they are using HTTP/1.x as the communication
protocol. I've always wondered why most use HTTP for micro-services instead of
JSON-RPC (or any other encoding) over a TCP/IP socket. What are the benefits?

~~~
cmelbye
Simplicity.

~~~
harshreality
json-rpc is pretty simple, and if the app uses json in the http bodies anyway,
json-rpc eliminates separate http protocol (header) handling.

Does json-rpc have a standardized crypto wrapper, though? That seems like the
most important reason to use https. Although json-rpc + nacl/sodium might be
better in theory...

~~~
chralieboy
This makes the assumption that their bottleneck is the overhead of HTTP. This
assumption is common on HN, but (in my experience) is a rare problem to have
for most companies.

It looks like they do a good job of tackling performance problems as they see
them, even at the architectural level. My guess is they didn't tackle this
because it wasn't a problem worth prioritizing compared to the compounded
shitshow of a monolithic Rails app.

------
pjmlp
So a monolith application gets ported to microservices from an interpreter
based version of Ruby to a compiled version of Go and the author is surprised
about the gains.

Why do people keep getting amazed about the performance they get when using
native code in their applications instead of a naive interpreter?

Not bashing Go, the title could be "From a Ruby monolith to microservices in
<pick your language with native compiler available>".

Hey, this could probably even be possible in Ruby, if something like
RubyMotion was generally available.

~~~
nexneo
Exactly right any language could have worked, and we didn't replaced Ruby,
just few part of our big application is rewritten.

I did in Go not because of language is better, ecosystem and culture is better
but not language part. I will suggest you to do small real life project in Go.

~~~
pjmlp
I wasn't hitting at Go, just making the point any language that native code
compilers will do.

Thanks for the suggestion, maybe you should check my history.

~~~
nexneo
Sure. I did :) One question: Why you mentioned RubyMotion as alternative for
server application?

~~~
pjmlp
Sadly it is not an alternative, because they only sell it for mobile apps.

RubyMotion as an AOT compiler would bring many of the same benefits in terms
of performance, while staying in Ruby.

Apparently only Lisp, Scheme and Dylan enjoy the existence of AOT compilers in
their toolchains.

------
talwai
Would someone care the elaborate on the following?: "They use nginx to route
requests to either the microservices or the main Ruby app... The Go
microservices treat the Ruby application as an API client or upstream proxy,
which allows authentication to be simple."

What I read this to mean is that the Ruby app accesses the microservices over
HTTP with some kind of token-based authentication. This would make sense if
there some form of shared session store which each microservice could validate
the token against. What I'm confused about is how nginx fits into this? Is it
routing requests directly from the client to microservices? Or is it a proxy
layer in between the Ruby app server and the microservices which allows the
Ruby app to forward auth-token information? In the former scenario, would the
Ruby app server be pinging the same nginx instance that forwards it requests
in the first place?

~~~
nexneo
I agree that line bit confusing, Ruby app doesn't connect with Go service
directly, every requests goes through balancer and then nginx router. Nginx
routes then using simple location directive. Authentication is not shared, its
either token based or Go service act as proxy and send request to Ruby app and
modify response received from Ruby app before sending to client(i.e covering
html to pdf)

------
bdcravens
I'm trying to decompose a Ruby monolith into microservices, but is it
necessary to change languages? For my use case, I'm less concerned with
languages than I am with libraries (AWS library, Sidekiq, Devise, deployment
toolchains, etc).

~~~
grey-area
Of course it's not necessary to change language, but one nice thing about
microservices is they don't have to be the same language.

You might see significant advantages moving to ruby microservices if you have
high traffic and a complex monolithic app which would benefit from being split
up. They are not a panacea of course as they introduce significant complexity,
whatever language they are implemented in - it's a matter of trade-offs and
once your monolith reaches a certain size it might be worth splitting it up.

Re libraries, most of the ones you mention have analogues in go or are pretty
simple to replace, save AWS, which is apparently coming soon:

AWS - [https://aws.amazon.com/blogs/aws/coming-soon-aws-sdk-for-
go/](https://aws.amazon.com/blogs/aws/coming-soon-aws-sdk-for-go/)

Sidekiq - go myFunc()

Devise - bcrypt.CompareHashAndPassword + the mailer functions

Deployment - Rsync or Ansible - this is simpler in go as all you require are
your executable + templates

------
bobofettfett
As they say: "Your new boy/girlfriend is better because you are better."

------
jackkinsella
Going from 12GB to 5MB of memory consumption could probably have been achieved
through moving the alerting components to a dedicated Ruby (non-Rails) / Linux
script configured to read their queue.

Time taken: 1-2 days.

Their high memory consumption was caused by large numbers of unneeded
dependencies (e.g. Rails) included in their alerting components. By
refactoring so drastically into a separate Go service, they may have
squandered tens of thousands of dollars of company/investor money in
retooling, retraining and learning a new environment. Good for the
programmers, toxic for the company.

------
ChikkaChiChi
Ruby enthusiasts seem to get apoplectic at any mention of a posting where
someone chooses another language over Ruby. I'm sure Ruby is just great at
what it does, but aren't we always talking about the right tool for the job?

I don't think anyone disagrees that refactoring and evolving the code would
have resulted in gains even if they stayed in Ruby. I'm sure most of us would
even agree that if you wrote something, then immediately scrapped it and
started over, you might already be able to improve on your original based on
some new knowledge you've gained from the experience.

When a company does this, they not only learn new things about the language
they are converting to, but they are gaining new and valuable insight into the
language they already have used. Posts like this are valuable to all of us
because they are sharing their experiences; whether you agree with their
choices or not.

------
bobofettfett
If you move from Ruby to Go and this works better for you, then choosing Ruby
in the first place might - if it was not about a prototype or getting VC money
or only found Ruby developers - be a failure on your part.

~~~
pmontra
Ruby got them customers within the bounds of their time and money budget so it
did its job. Nothing in the post suggests they have regrets about it but it
would be interesting to hear from them.

~~~
bobofettfett
When Ruby can't solve your business problems -> wrong choice. If it can solve
your business problems -> wrong choice to move to Go.

~~~
bobofettfett
Yes, couldn't read find that in the article beside "monolitic baeh"

------
bobofettfett
Companies that invest in rewriting everything from Ruby into Go instead of
working on the business model seem to have a lot of money and a secure future
and no competition.

