
How to Fix Slow Code in Ruby - gmcabrita
https://engineering.shopify.com/blogs/engineering/how-fix-slow-code-ruby
======
benkuhn
Really surprised how many commenters are talking about using a faster language
when the example of slow code _in the post_ is failing to cache the results of
an expensive database query.

In my experience, even in "slow" languages, that type of thing is the
predominant source of major performance problems, and the supposedly "slow"
language would be perfectly adequate if you an stop shooting yourself in the
foot (and if you can't, a faster language will not help you).

I'm sure there are extremely high-performance or high-scale points where
language choice starts to matter more, but I'm also not surprised if Shopify
is correct not to think they're there yet.

~~~
pdimitar
It is surprising until you realize that Rails' ActiveRecord consistently
wastes 100-200ms on every request just to serialize / deserialize data from/to
the database.

So yes, a language that's both faster and has less overhead in its ORM /
DataMapper library definitely will help you.

~~~
jordanthoms
Our data-heavy API returns JSON responses from our DB in ~40ms using
ActiveRecord. So no, it doesn't waste 100ms on every request! We're serving
around 6000 requests like that per second.

~~~
pdimitar
Well, you likely have a dedicated (or pretty strong) server.

The Heroku dynos I've tested with some years ago performed quite horribly.
Only a proper Xeon server was able to achieve sub-100ms responses.

But hey, if Ruby improved in the meantime, cool.

~~~
toomanybeersies
Rails on Heroku is about the most inefficient combination you can get for a
web server.

I've always thought of it as the platform you use for running toy apps with
maybe a couple of dozen users at most and a low load. It's incredibly quick
and easy to get a Rails app running on Heroku, a couple of hours at most. But
you pay (in dollars and performance) for this.

~~~
pdimitar
You'd be surprised. I know companies paying $2000+ a month for Heroku and are
still having performance problems with Rails.

(Then again, they have them with Phoenix as well but to a lesser extent.)

------
rdoherty
First thing I tell anyone when they say "this code is slow because of X" is to
profile it. Profile, profile, profile! More often than not your assumptions
are wrong.

There's a myriad of tools out there for profiling, some language specific,
some not. Learn at least one of them well, how to read flamegraphs and how to
benchmark properly (warmup code, synthetic vs real traffic, etc). There's
definitely a jump between making guesses and hoping you improve performance vs
truly understanding what your code is doing.

~~~
ykevinator
Second this, more often than not

------
alberth
What's current state on GraalVM as it relates to running production Ruby code?

From what I understand, it's the fastest VM out there at the moment for Ruby.

And yes, it's from Oracle but they have GPL'd the code [2]

[1] [https://www.graalvm.org](https://www.graalvm.org)

[2] [https://www.graalvm.org/docs/faq/](https://www.graalvm.org/docs/faq/)

Edit: looks like TruffleRuby is built onto of Graal.

[https://github.com/oracle/truffleruby](https://github.com/oracle/truffleruby)

~~~
yxhuvud
Last time I tried to run Truffle on the company test suite it spent an hour
processing 1/8th of the suite. Then it was killed by the OOM killer. On a 32
GB machine. To be fair there was only one or two errors during that, so
compatibility is at least getting there.

Meanwhile Ruby 2.6.5 chugs along and finishes the whole suite in 8 minutes.
Never hitting any unreasonable amounts of memory.

~~~
chrisseaton
Yes TruffleRuby struggles to run test code because it works against the
optimisations we add to make production code fast. For example when you add
more profiling to better optimise the hot code it makes the cold code slower,
and tests are almost all cold code.

Also our C extension emulation layer used to be extraordinarily slow while we
made it work correctly, and it's still rather slow.

It's a challenge but we're working on it.

But TruffleRuby is the only alternative Ruby implementation to even run major
applications that I've tried at Shopify.

~~~
pvsukale3
Unrelated: But thank you for the work you and your team are doing. I wish you
all the success.

~~~
ksec
Agreed. I often felt people give little credit and appreciation to the Truffle
Ruby teams.

------
llamataboot
I even have a little method in my .pryrc that lets me easily benchmark on the
fly in the console

def benchmark_time(repetitions = 100, &block) require 'benchmark'
Benchmark.bm{ |b| b.report{ repetitions.times(&block) } } end

------
praveenperera
I wonder what would be more efficient, constantly trying to eke out some
performance out of a inherently slow language? Or writing/rewriting
new/critical paths of the codebase in a faster language.

Ruby used to be able to say we sacrifice performance for developer
productivity.

I don’t think this is any longer true, there’s plenty of languages out there
that developers can be just as productive with, while producing wildly more
performant code.

~~~
pcmoney
If you want the luxury of scale problems down the road you do RoR until you
lock down that first couple billion dollars of valuation. Just ask Stripe,
Github, AirBnB, Gusto, Shopify, Coinbase, Dropbox, Twitter, Door Dash etc.
Then you can have the "problem" of picking the wrong language. Massive
survivorship bias in the panning of Ruby/RoR IMO...

Also the Ruby/RoR community and culture is better than most other
languages/framework. I think culture is a totally valid performance reason to
pick a language. Sure there are faster languages and there are jerks
everywhere but on average it seems RoR devs are on average nicer and more
collaborative people relative to peers.

There is a just a mindset for wanting to write in a language optimized for
developer happiness that dovetails with wanting to be happy and work happily
with other people. Ya sure those C++ guys can write more performant code but I
know who I want to work next to 8 hrs a day. And who I will be more productive
working with.

Also I don't think there are plenty of languages that are Ruby/RoR peers.
Django doesn't come close. JS ecosystem is a dumpster fire. Some functional
and JVM languages maybe but they often come with a corporate culture that
kills their benefits. Kotlin would be my bet I guess?

Also if scale is your issue and rails isn't cutting it then you need to go to
a proven language with a proven and hirable developer base. That rules out a
lot of new and promising languages. Sure they might be just as productive
languages but they are resource constrained at the people level. It is really
hard to hire Elixir/Phoenix devs etc. You need a talent pool of thousands.
Also you need to KNOW the language will be there with a community in 10 years
and that it has a history of evolving without screwing over the community. I
guess Twitter going to Scala would be an example of this, from my
understanding it doesn't solve all their problems.

Total aside: plenty of language need massive tuning to work at scale but it
seems like Ruby is unfairly singled out if someone does "exotic" tuning of it
but if someone tunes a JVM language or invent their own it is supported.

~~~
leetrout
Edit: downvote away for disagreeing. I don’t see anyone presenting a counter
argument.

This has not been my experience.

It has been “do it THIS way in ruby” even though either way is perfectly valid
(eg list of Literal strings / symbols vs %i or %w.

Lots of bike shedding type discussions on which way is better and
unsurprisingly no consistency across the codebase.

There’s also a lot of hidden things you can only learn from years of usage and
no clear documentation of when / where features came and went.

All in all every Ruby dev I’ve worked with across 4 companies have all had the
same sort of elitist / pretentious attitude compared to Python, Go or Java
devs. Ruby has only been second to Scala and Rust devs (so far).

~~~
kenhwang
Seems like you're comparing between languages that end up being the default
choice due to their low learning curve (Python/Go/Java) and languages chosen
because the default choice wasn't good enough (Ruby/Rust/Scala). The latter
tends to be much less restrictive or more featureful which necessitates
governance by convention/culture/guidelines since its significantly more
difficult to be exactly prescriptive over significantly more variance.

For example, most Ruby found in the wild follows some variation found in the
style guide: [https://github.com/rubocop-hq/ruby-style-
guide](https://github.com/rubocop-hq/ruby-style-guide) (same with Scala:
[https://docs.scala-lang.org/style/](https://docs.scala-lang.org/style/) and
Rust: [https://github.com/rust-dev-tools/fmt-
rfcs/blob/master/guide...](https://github.com/rust-dev-tools/fmt-
rfcs/blob/master/guide/guide.md)) and their communities generally care enough
to try to enforce it. Most languages have an universally agreeable style guide
these days.

I personally find the first set of languages to be more consistent
syntactically (typically because they're simpler languages that have auto-
formaters) but the latter set of languages are extremely consistent in
patterns/ideologies. It seems like you care more about the former and didn't
care to learn about the latter (which I'll admit, does present a higher
learning curve and tends to be acquired through experience with the community
than through a doc).

------
stevebmark
Benchmarks are subjective, but _all_ benchmarks show Ruby as slower than
compared dynamic languages. The relative speed difference is different per
benchmark, but across the board Ruby is slower.

It fundamentally _has_ to be slower. Ruby is the most dynamic of the dynamic
programming languages. And the community has embraced metaprogramming, making
it every more dynamic. Especially on webservers, you'll be executing hundreds,
sometimes thousands, more lines of code than other servers, especially in a
mature system.

Is it "slow" enough to matter? Probably not until you get to a medium scale.
Everywhere I've worked, we've had to on average double the hardware specs for
Ruby servers to make them as performant as other dynamic language applications
we run. Not the most expensive thing in the grand scheme of things, but there
are entire cottage industries of magically tuning Ruby and Rails that you
don't have to worry about with other systems until much larger scales.

~~~
FanaHOVA
> Is it "slow" enough to matter? Probably not until you get to a medium scale.

Phew, I'm glad GitHub and Shopify's scale is still small.

~~~
dijit
Pedantry aside, we've reached a point in our industry where we can do a lot
with horizontal scalability.

I mean, every programmer who funnels through university understands map
reduce, and that helps on multi-core threading up to system job running.

But there is a limit, usually in the persistence and caching layers. What
you'll find is that those "large scale deployments" are going to have a -lot-
of internal cache systems and I can pretty much guarantee that the services
running those caches and persistence will not be written in ruby.

You can make anything* scale, but how many CPU cycles you need to burn to get
the functionality you want is a matter for the finance department.

If you're running in a lossy business, you can bet that those CPU cycles will
begin to cost more than developer velocity is worth, because servers are an
eternal and ongoing cost.

On the flip side if you make more money than the infra+devs cost, then nobody
is going to hound you for wasting 2x 3x the cost. Because "it's the cost of
doing business" is easier to justify when you're cash positive.

~~~
joelbluminator
> services running those caches and persistence will not be written in ruby

So what? What's wrong with using software like redis for cache, for a very
small (but important) part of your business? I bet java apps use redis as
well, and redis isn't written in java. So?

~~~
pdimitar
Is this an honest question? I honestly can't tell and I am not saying it to
show disrespect -- just wondering if you are sarcastic.

Erlang/Elixir have built-in caches that respond in the matter of 30-150
nanoseconds.

Why would you need an external service for that? It's adding complexity -- and
likely hosting costs.

Isn't it self-evident to you that adding Redis as a caching layer to your
stack is a bandaid to a deeper problem?

~~~
antirez
Local caches and local node caches are both very useful. (That's why Redis 6
introduces this [https://redis.io/topics/client-side-
caching](https://redis.io/topics/client-side-caching)), but anyway from what I
saw in the past, the major speedup of using Redis in such a context is that
you want to use a shared very fast view that is global in nature. A simple to
understand, but good example, is the leaderboard problem in multiplayer games
that have million of users (Facebook games and such). Even if you have a local
cache, and even if you have an additional store where you record the high
score of each user, you need a global and very fast to update view of all the
sorted scores, to tell the user its rank, users nearby, the rank of their
friends. There are a number of problems like that that require to use
different data structures and a _global_ view. The problem is that using Redis
with the Memcached mindset, will always severely limit the potential benefits.

~~~
pdimitar
Yes. Redis has very valid use cases.

You're quite right: people using it as a mere cache don't get most of its
benefits.

------
jbverschoor
Tuby is almost the only language which has a really really good, clear and
well thoughout standard library.

~~~
jbverschoor
Ruby ofc

------
lidHanteyk
Interesting; Shopify doesn't use TruffleRuby, but instead prefers MRI?

~~~
chrisseaton
> Interesting; Shopify doesn't use TruffleRuby, but instead prefers MRI?

Shopify's investing in TruffleRuby as well as MRI - they employ me to work on
it. We have it able to run one major app, but still working on making it as
fast as we'd like.

~~~
AndyMaleh
Idiotic. That’s what using the right tool for the job is all about. Ruby isn’t
for everything and never will be. Just stick to CRuby (or JRuby if integrating
with the JVM). They’re already stable. No point diluting the space with more
rubies for superficial reasons like lessening the performance trade off in
Ruby when there are many many other languages better suited for high
performance algorithms to apply where needed. That’s what good engineering is
all about after all. Knowing trade-offs and choosing the right tool for the
job, not denying them or trying to fight them like TruffleRuby idiotically
does. Shopify is like Canada’s Groupon. The only reason they are profiting is
because they have a simple business idea despite their mediocre engineering
skills.

------
The_rationalist
Any performance benchmarck of jruby vs truffleruby vs ruby?

~~~
kipply
[https://github.com/mame/optcarrot](https://github.com/mame/optcarrot)

Includes some rare, bonus ruby implementations too <3

~~~
imhoguy
Nice, JIT in 2.6 makes a significant performance jump
[https://rubybench.org/ruby/ruby/releases?result_type=Optcarr...](https://rubybench.org/ruby/ruby/releases?result_type=Optcarrot%20Lan_Master.nes)

------
fxtentacle
C++

It's that easy :)

~~~
donretag
Basically do not use Ruby

~~~
karmakaze
How is it that Python is able to do so many things quickly (even excluding
NumPy/SciPy)? Is it more native code libraries? Could Ruby follow this path as
effectively?

~~~
goatlover
Are the Python web frameworks that much faster?

~~~
karmakaze
I wasn't specifically thinking web frameworks but a number of projects that I
found surprising and performant. One example I can recall is Graphite
(Whisper, Carbon).

------
StreamBright
Seriously, if you need high performance out of the box, you should probably
just skip Ruby. I love that language to death but most of the time you cannot
use of it for a decent scale.

~~~
pdimitar
Same for me with Elixir. I love that little thing so much but there are
workloads where it objectively shouldn't be used -- for web apps it's
definitely one of the best picks out there but that's a different topic.

I've lately been writing several Rust tools and mini apps and have admitted to
myself that even if a language stack clicks with you almost perfectly, you
should still reach out to other tools when appropriate.

Even us the senior devs forget that, and it pays to get reminded of it every
now and then.

~~~
lostcolony
Here's the official reminder not to use the BEAM for all things -
[http://erlang.org/faq/introduction.html#idp32557584](http://erlang.org/faq/introduction.html#idp32557584)

------
timwis
There’s a much easier way: just increase `Rails.application.speed` from 5 to
10 in the application config.

------
pdimitar
...Rewrite in almost anything else and you'll have fast code. :)

It's quite amazing the lengths the companies will go to just to avoid changing
their status quo. But for them it makes sense.

\---

EDIT: Downvoters, calm down. Ruby on Rails is objectively quite a slow
framework and this is proven in many public benchmarks (Techempower included).
And Ruby isn't the fastest among the dynamic languages either.

Less cargo culting and more facts, please.

~~~
BubRoss
Stuff like this always confuses me. It's like asking how to exercise without
sweating. I would think the first sign of performance problems would mean some
profiling and some porting of sections to a native language. Instead some
companies go down a crazy rabbit hole instead of just making some shared
libraries.

~~~
pdimitar
I thought the same as you but then I realized that the investment in a tech
stack is basically done only once and almost never revisited.

Then people go on all sorts of crazy journeys to justify their investments in
pain and burned money.

Even though I get downvoted at places in this thread (and upvoted generously
on others), I will never tell to people "you should always just rewrite to
Elixir". Meh. If your app works fine, have it be in COBOL or PL/1 if that
helps you do your job better.

But as you said, when your app/hosting starts struggling you should start
rethinking your choices if all the lower-hanging fruit has been already
collected.

