12 ms·
From a Ruby monolith to microservices in Go
- saintfiends 12y agoFrom 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 12y agoSimplicity.
- nexneo 12y agoExactly. (I implemented these services) - I don't see any reason of not using JSON-RPC but JSON over http is more simple for me. At one place "form-urlencoded" is also used, so its not only JSON.
- harshreality 12y agojson-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...
- nexneo 12y agoAfter my talk many people voiced same concerned of overhead. But I don't see it that way, overhead is not much over simplicity and familiarity benefits. JSON has advantage of human readability and for that tooling support is required which probably you will loose with RPC. For example I use many tools to inspect requests that won't support it with RPC
- chralieboy 12y agoThis 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.
- patio11 12y agoIt's easy to understand for developers and "plays well with everything." Don't underestimate the importance of toolchain support. Want to spin up a Ruby microservice which speaks HTTP? Sinatra and you're done. Go? Whatever the Go HTTP library is and you're done. Need to interact with it from the command line? Curl and you're done. How about from an automated testing script written in Ruby? Net:HTTP/HTTParty and you're done. Thinking about how to deploy it vis-a-vis firewall/etc? Port 80 and you're done. Need coarse-grained access logging? Built into Nginx/etc already; you're done. Uptime monitoring? Expose a /monitor endpoint; provide URL to existing monitoring solution; you're done. Deployment orchestration? Use Capistrano/shell scripts/whatever you already use for the app proper and you're done. Encrypted transport? HTTPS and you're done. Auth/auth? A few options that you're very well-acquainted with and are known to be consumable on both ends of the service trivially. Edit to note: I'm assuming, in the above, that one is already sold on the benefits of a microservice architecture for one's particular app/team and is deciding on transport layer for the architecture. FWIW, I run ~4 distinct applications, and most of them are comparatively large monolithic Rails apps. My company's bookkeeping runs in the same memory space as bingo card PDF generation. Things that would tilt me more towards microservices include a very rapid deployment pace, large engineering organizations which multiple distinct teams which each want to be able to control deploys/architecture decisions, particular hotspots in the application which just don't play well with one's main technology stack (as apparently happened in the app featured in this article), etc.
- Animats 12y agoTheir "microservices" are kind of macro. Rendering a PDF file and sending an email are reasonably large operations, so the overhead of local HTTP isn't that bad. For a finer-grained application, such as the way Facebook generates pages from about 20 servers feeding data in to be assembled, that wouldn't scale. Facebook has Thrift and Google has protocol buffers. Both work, but result in a somewhat complex build procedure with a pre-compiler for interface specs. (Serializing and de-serializing data into and out of databases and networks is either interpreted and slow, or compiled through clunky tools. Technology in this important area still sucks. There's a long history of clunky solutions to this problem, from DCOM, CORBA, and ASN.1, to SOAP to REST/JSON. Part of the problem is political. If you expose an API defined in any of those except REST/JSON, there's a formal, checkable definition of the API in machine readable format. If the API doesn't match the spec, the server is broken. With a REST/JSON API, you get to blame the caller for doing it wrong. This is convenient for API developers.)
- chralieboy 12y agoThe "micro" in "microservices" refers more to the size of their conceptual complexity than how large of a task it actually is. PDFs are one conceptual bucket that can have their own datastore and a simple API. Sending email isn't included in that service's contract, as it actually outsources this task to Mailgun. REST/JSON can have formal specification – check out http://json-schema.org/ http://json-schema.org/. Swagger (http://swagger.io/ http://swagger.io/) is another example of this and works really well for automatically generating client libraries in many languages and auto-updating documentation.
- tonyarkles 12y agoOver 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.
- sfeng 12y agomux is probably the most popular routing library: http://www.gorillatoolkit.org/pkg/mux http://www.gorillatoolkit.org/pkg/mux As far as application structure, I'm not really sure what you mean. Go projects are divided into packages which can be any number of files. At some point you'll have a main package which has a main() function which gets called to start your server. It's similar to C, for example. That said, the variety can also be a plus. Some people chose to use something like Martini that does dependency injection. Others think that's not a great idea.
- nexneo 12y agoInitially 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.
- talwai 12y agoWould 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 12y agoI 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)
- sgt 12y agoAfter 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 12y agoWe 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 12y agoExactly, 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.
- Argorak 12y agoNot 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.
- danieldk 12y ago
- Argorak 12y agoEvery 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 12y agoYou 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 12y agoAs 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.
- dkarapetyan 12y agoSo 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.
- deleted 12y ago[deleted]
- dkarapetyan 12y agoYou can begin by addressing all the gaps I found from a cursory glance. Go has many things going for it but you're comparing apples and oranges in your post and confounding the fact that you switched from a monolithic system to non-monolithic system. That alone removes a lot of overhead and blaming that design choice on your tools is just silly. P.S.: There is no aggression. I'm tired of post-hoc rationalizations for rewrites of systems with bad initial design choices. Every single one of the issues you bring up could have been addressed without switching the language by an engineer well-versed in using the right libraries, celluloid and eventmachine being the first ones that come to mind for all the I/O bound stuff. I also believe in critically assessing all hype related to language and design choices and if I let your article go without criticism some impressionable young mind is going to think you're correct.
- bobofettfett 12y agoIf 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 12y agoRuby 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 12y agoWhen Ruby can't solve your business problems -> wrong choice. If it can solve your business problems -> wrong choice to move to Go.
- grey-area 12y agoRequirements change.
- bobofettfett 12y agoYes, couldn't read find that in the article beside "monolitic baeh"
- nexneo 12y agoWithout Ruby we couldn't have build lots of features with such a tiny team. We don't regret and we are not replacing Ruby entirely (just few parts) 85% of our server codebase is still Ruby.
- bobofettfett 12y agoCompanies 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.
- bobofettfett 12y agoRuby 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 12y agoWe 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".
- nexneo 12y agoProblem is I don't know Java. But I know enough Java to know that it will take lot longer for me. And I might not have simple deployment benefits, thats important feature of Go.
- tomohawk 12y agoIt 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.
- deleted 12y ago[deleted]
- deleted 12y ago[deleted]
- matthewmacleod 12y agoRewriting 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.
- pjmlp 12y agoWhat is so magic about a compiler feature generally available in all compilers that target native code?
- matthewmacleod 12y agoMagic in the sense that someone coming from building Ruby apps will find it amazing to skip over dealing with the deployment process for a scripting language. while it's true that there are plenty of projects and services that reduce the pain of that, I'm certain that everyone who's ever built a Ruby app has experienced deployment hell at least once.
- neikos 12y ago> 12GB to 5MB of memory consumption, then you have a design issue and not a language one. That's not Ruby's fault. I thought the same thing, I find that figure also very hard to believe. Perhaps I just lack experience, but if you get 12G of memory usage that drops to 5M after rewriting it (whether another language or not) seems to me as if they did something pretty wrong in their previous iteration.
- bdcravens 12y agoI'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 12y agoOf 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
- pjmlp 12y agoSo 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 12y agoExactly 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 12y agoI 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 12y agoSure. I did :) One question: Why you mentioned RubyMotion as alternative for server application?
- pjmlp 12y agoSadly 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.
- bonn1 12y agoMany 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 12y agoI 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 12y agoBut 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 12y agoAs they say: "Your new boy/girlfriend is better because you are better."
- jackkinsella 12y agoGoing 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 12y agoRuby 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.