DEV Community

Cover image for From Rails to Elixir: Know Your App

From Rails to Elixir: Know Your App

JD Costa on December 17, 2018

This is the first of a series of articles about my journey going from Ruby on Rails to Elixir/Phoenix. \Part1 From Rails to Elixir: Know Your App \...
Collapse
 
elvio profile image
Elvio Vicosa

Hi João, really nice post.

Regarding performance improvements, we are on the process of migrating our Rails monolith to Elixir and Phoenix. It's quite impressive what Phoenix delivers.

So far, without any optimizations, we are experiencing an increase of 7x on the supported traffic, with 5x faster response times and using half of the resources (AWS instances, RDS db, etc).

I will soon write a post about our findings.

Collapse
 
jdcosta profile image
JD Costa

Hi Elvio, thank you!

That's great, I'd love to hear from someone who is doing this in the real world.

There are some articles out there from people who have done similar migrations but they focus primarily on the outcome (like "we had X servers and were able to reduce to Y"). I haven't come across a nice detailed article focusing on the actual pains and gotchas. That would be very welcome.

faster response times and using half of the resources (AWS instances, RDS db, etc)

Interesting. I wouldn't expect databases (RDS in your case) to benefit too, in fact I would expect extra load on data repositories due to Phoenix increasing concurrent accesses. I mean, since you are now capable of handling more requests in the web server layer, shouldn't RDS be receiving an increase in traffic as well?

There might be an hidden factor in there that I'm not considering. I've come across very surprising outcomes in the past that I wasn't expecting at all.

Collapse
 
elvio profile image
Elvio Vicosa

Yes, I think there are many things I could write about "pains and gotchas" :)

Regarding RDS, is an interesting thing. We are migrating from a Rails app where all the servers were connecting to the same database, as also multiple Sidekiq workers. It required a lot of open connections to handle that.

We now have a quite simple setup where a small pool of connections can easily handle that, even with traffic always hitting the database :)

Thread Thread
 
jdcosta profile image
JD Costa

Yeah I guess that's a nice side effect, with Elixir you might get more control over your whole system (including "external" resources like databases) because it's so easy to reason about what is running concurrently.

Pretty cool!

Collapse
 
jherdman profile image
James Herdman

There's a really good chance you don't even need the job queue. Take this with a grain of salt, I'm just beginning my Elixir journey as well, but basically everyone has told me that a lot of what we Rails developers spun out as separate services can be done directly on the VM. Even Reddis can be largely replaced with things like Mnesia.

Collapse
 
jdcosta profile image
JD Costa • Edited

You're exactly right. I'm gonna write a full article about why I chose to use a job queue during the transition but the gist of it is:

1\ I want to migrate bit by bit to Elixir, in an incremental fashion. At a given point in time, I'll have Sidekiq and Elixir running side by side in production, so I'll need to consume jobs from Redis originated by Rails. Exq makes my life easier during the transition.

2\ Once I get all the background jobs into Elixir (both Producers and Consumers), I will be able to think in Elixir terms and start refactoring the app to use Processes and other OTP stuff.

This is the plan, I'll share my findings in a later stage 👍

Collapse
 
rhymes profile image
rhymes

Hi JD, very nice post! Elixir sure seems exciting and what better environment to test new technologies than side projects.

It seems also, by coincidence, the topic of rewrites is recurrent today. I've posted this a few hours ago:

In it there's a description of a rewrite from PHP to Clojure that somebody else did.

I feel like selling Go (because of Google and the insane amount of servers that are popping up written in Go) and Elixir (because of Erlang, consulting businesses and its strong concurrency culture) to companies is going to be easier in the future.

It's also peculiar how many Ruby devs gravitate towards Elixir and how many Python devs gravitate towards Go. It probably has something to do with the fundamental philosophies of the four different languages.

There's always that big tradeoff in productivity that DHH talks about and the concept of good enough for most apps.

I'll happily read the next episodes and learn something :)

Collapse
 
jdcosta profile image
JD Costa

Hi, great feedback, thanks!

Just read your post about rewrites and I can relate, going for a rewrite in the context of a company requires careful analysis and planning, but there are some edge cases where it's in fact necessary. You said it all in there.

I guess Elixir/Phoenix feels familiar for Ruby devs. It inherits some things from the Ruby culture like great open source support (people do care about maintaining their extensions, etc) and a tendency for simplicity. Similarities in syntax also help, but as I'm discovering, it's merely superficial.

Personally, I'm leaning over Elixir because I feel that I'm getting what I usually get from Ruby/Rails (tooling, community, simplicity, convention over configuration) and more. It's very easy to create a project and start playing with it, mix does pretty much everything for you.

I don't feel the same with Go. I can't say much more because my experience with it is negligible, but I felt that I would have to spend more time thinking about setup/configuration and actual code. I love to write code, don't get me wrong, but I prefer a "less bloated" language in terms of syntax. It allows me to prototype faster and spend more time thinking about the workflow.

Yeah, DHH is very bold in his interventions. I do agree with the idea behind his article, but now we have this new thing that might deliver development speed in the same order of magnitude and at the same time puts you in a better position for the future (in case you come across scaling issues).

Great! The next one will be about how I usually approach rewrites in order to reduce friction during the process.

Collapse
 
rhymes profile image
rhymes

Thanks for the explanation, I think you made the right choice as I said and indeed tooling can seem a bit more "unixy" and primitive with Go, but this too is because of its philosophy. Tooling it's getting better though. I love Go but Makefiles feel like a step back.

The main selling point in deployment for Go is that you distribute a single binary exec, that you can cross compile and that takes a short time.

I only disagree with one remark you made or maybe I misunderstood your use of the adjective. When you talk about "less bloated" what do you mean? Go is famous for how compact its syntax is. I'm not implying Go is a simpler language to use (after all Elixir abstractions have tons of value) but I wouldn't say "bloat" either :D

Yeah, DHH is very bold in his interventions

I feel like he has become better at explaining the why behind his bold statements in the last few years. I find myself agreeing more and more with his development world view, even if I might not agree 100% with the solution or something (eg. Rails concerns :D)

Great! The next one will be about how I usually approach rewrites in order to reduce friction during the process.

:-)

Thread Thread
 
jdcosta profile image
JD Costa • Edited

indeed tooling can seem a bit more "unixy" and primitive with Go

Makes sense. If I'm not mistaken, Go was created to be a system's language but I may be wrong

When you talk about "less bloated" what do you mean?

My experience writing Go code is close to zero, I may be saying something silly, but everytime I see a code snippet in Go it looks kind of noisy. Well, "noisy" might not be the right word. Cluttered?
I don't know if it's the amount of symbols, or the type declartions, or even the actual convention of naming things in PascalCase like:

func randomString() string {
  myStr, err := someLib.RandString()

  if err != nil {
    fmt.Println("I don't care")
  }

  return myStr
}

Which looks strange to me because my eye is trained for Ruby:

def random_string
  some_lib.rand_string
rescue
  puts "I don't care"
end

Well, one day I'll find the motivation to write some proper Go and get over it :)

Thread Thread
 
rhymes profile image
rhymes

Makes sense. If I'm not mistaken, Go was created to be a system's language but I may be wrong

I thought so too, but the info I had was incomplete. Go was created for Google's problems. Thousands of developers working with a monorepo and millions of C++ and Java code taking ages to compile. They created a language that was simple to pickup (also for less experienced devs) and had fast compilation time.

From Go at Google: Language Design in the Service of Software Engineering:

The goals of the Go project were to eliminate the slowness and clumsiness of software development at Google, and thereby to make the process more productive and scalable. The language was designed by and for people who write—and read and debug and maintain—large software systems.
Go's purpose is therefore not to do research into programming language design; it is to improve the working environment for its designers and their coworkers. Go is more about software engineering than programming language research. Or to rephrase, it is about language design in the service of software engineering.

So, it was created as a practical working language that was going to be maintanable at large and, initially, would have replaced C++. The thing is C++ programmers outside Google didn't convert in mass, mainly because it's garbage collected and that can be a problem with limited resources and system programming. That's probably why Rust is more interesting to them.

Regarding Go and system programming, Rob Pike says, in the same talk:

For a systems language, garbage collection can be a controversial feature, yet we spent very little time deciding that Go would be a garbage-collected language. Go has no explicit memory-freeing operation: the only way allocated memory returns to the pool is through the garbage collector.

He later backtracked on the idea of Go as a "systems programming language". From a later talk with the C++ creator and D and Rust core developers:

When we first announced Go, we called it a systems programming language, and I slightly regret that because a lot of people assumed it was an operating systems writing language. What we should have called it is a server writing language, which is what we really thought of it as. Now I understand that what we have is a cloud infrastructure language. Another definition of systems programming is the stuff that runs in the cloud.

Indeed Go is mainly used to write networked servers.

I may be saying something silly, but everytime I see a code snippet in Go it looks kind of noisy. Well, "noisy" might not be the right word. Cluttered?

It may have something to do with the explicit error handling. I hated it in the beginning, I still wish there was a better way (I come from exceptions as well) but I understand why they did it. The big advantage is forcing developers to handle error there and then.

Another thing I started loving is the difference between myStr and MyStr, the first is private to the module, the second is public. Very simple. I think simplicity is what they aimed for for many features, sometimes at the expense of features (like generics).

Collapse
 
trinitytakei profile image
Trinity Takei

Hi João,

Great post! I'm REALLY looking forward to these figures (if I understand correctly you'll let us know in the end):

Rails: x hours development time, y $/month spent to monitor z pages [reasonably fine-tuned, eg after the jemalloc tweak mentioned above], w resources (time, money, ...) to keep it going
Elixir: p hours development time, q $/month spent to monitor r pages, s resources to keep it going

I'm working with Ruby/Rails since 2005, enjoying Elixir a LOT (it's the second time I feel like this in my life - the first one was when I found Ruby!), interested in scraping (have been doing it for the past 15 years, professionally, academically, OS and every other way) thinking about (partially) switching to Elixir). Oh yeah, and I feel similar about Go (not debating it's power, but not really excited about the implementation details - it's C-like, for starters, which is a dealbreaker for someone thinking the Elixir way is the best stuff since sliced bread, err, Ruby.)

What I'm secretly hoping is that someone can prove me that for this setup, Elixir/Phoenix is a better choice so much so that I can justify spending time on rewriting my Ruby tooling from scratch -considering that my time is really scarce (If I had plenty of time, I would do it out of joy, even if I knew the answer - however, right now I have 2 jobs, 2 kids, 200 other things to tend to etc).

The thing is that as someone currently working on a Rails codebase that's monitoring 1000s of sites around the clock, I know that the shiny stuff that comes with Elixir will get you only so far, and eventually you are going to fight language-agnostic issues anyway (how to detect which of your scrapes are broken, how to maintain/fix the issues the quickest way possible, how to stay under the radar if you are grabbing massive amounts of data (hint: concurrent hammering, even is relatively cheap, is not it), how to deal with rogue data... and more).

Anyways, this comment is way too long already, I'm wondering if you'd like to move the conversation to email - I hate communicating via web forms ;)

Collapse
 
jdcosta profile image
JD Costa • Edited

Hi Trinity, we are on the same boat then!

it's C-like, for starters, which is a dealbreaker for someone thinking the Elixir way is the best stuff since sliced bread, err, Ruby.

Spot on.

What I'm secretly hoping is that someone can prove me that for this setup (...) considering that my time is really scarce

Well, I hope that you can find some value from what I'm planning to share in future articles - an incremental migration to Elixir by having both systems working together during the transition phase. You can do a partial rewrite of your system by migrating something that you feel would fit Elixir best in a first stage, just to experiment with it and see if you are getting value from it. In my case I'm planning to migrate the Page Downloader first, keeping all the other components in Rails' land, communicating via Redis (using the Sidekiq protocol).

you are going to fight language-agnostic issues anyway

Absolutely. Using Elixir may offer you better control and tooling but the typical scraping/crawling problems won't go away with Ruby.

I'm wondering if you'd like to move the conversation to email

Sure, it sounds like our circumstances are very similar, it would be great to share experiences/thoughts with someone with the same set of challenges in-hands. I'm now following you on dev.to (here) and Twitter, we can get in touch through either and share email addrs.

Thanks for the feedback!

Collapse
 
rossta profile image
Ross Kaffenberger

Great post. I like how you provided context about your app as part of the justification for making the switch in addition to listing some general pros and cons of Elixir that teams may consider. Thoughtful analysis!

Collapse
 
hopsoft profile image
Hopsoft

jemalloc may have helped reach your goals with the Ruby implementation. speedshop.co/2017/12/04/malloc-dou...

Collapse
 
jdcosta profile image
JD Costa • Edited

In theory, jemalloc would have been more conservative in post-boot allocations, keeping memory consumption growing at a lower rate and eventually more stable, due to its focus on handling fragmentation. It wouldn't do much to the baseline memory consumption that Puma/Sidekiq require after boot.

Is this a fair statement? I've never had the chance to try jemalloc myself, so I'm unaware of how it affects the runtime in practice. I've read a few articles here and there.

Collapse
 
hopsoft profile image
Hopsoft • Edited

Sounds like a fair statement. Not sure what we can do about baseline memory requirements on boot.

Though I'm currently using jemalloc on a Standard 1x Heroku dyno (512MB ~ $25/mo) serving about 1.5 million requests/day and it averages 275MB of memory usage with a mean response time of 35ms.

-edit

Note that this is a standard Rails 5+ app using Sidekiq for background jobs. Currently processing about 50k jobs/day.

Thread Thread
 
jdcosta profile image
JD Costa

That's a very efficient Rails app, well done!

Collapse
 
juanbono profile image
Juan Bono

I'm waiting for the next part! Thank you for sharing your experience!

Collapse
 
belgoros profile image
Serguei Cambour

Thank you for sharing, - by the way, the link to the Part-1 (dev.to/jdcosta/from-rails-to-elixi...) is broken :(

Collapse
 
codebeautify profile image
Code Beautify

Still not convinced that elixir has the same use cases as rails

Collapse
 
jdcosta profile image
JD Costa
Collapse
 
maartz profile image
Maartz

A bit late but for a Rubyist Elixiring this kind of articles are very good ! Thanks !

Collapse
 
strzibny profile image
Josef Strzibny

Nice article, thanks. I wonder why you didn't consider JRuby first?