
A Complete Guide to Rails Caching - nateberkopec
http://www.nateberkopec.com/2015/07/15/the-complete-guide-to-rails-caching.html
======
varmais
> Developers, by our nature, are very different from end-users. We're used to
> the idea that when you interact with a computer, it takes a little while for
> the computer to come back with an answer.

I don't agree. End users are used to shitty systems which take some time to
load, but we developers should know better. We should be able to recognise
performance bottlenecks during the architecture design and development, also
we should be able to measure the layers where optimisation is necessary and
useful. And when developing web app, caching should always be in mind as one
tool to improve system performance in one way or another.

Otherwise fantastic piece of information.

~~~
dmitrig01
Taking this further, as a developer, I find I have even less patience for slow
loading times than a 'regular end-user'.

I don't know exactly, but I assume this is because I know what goes on behind
the scenes, and 99% of the time, the website is doing way more work than it
really needs to in order to deliver the experience that I'm asking for.

------
douglasfshearer
Fantastic resource.

No mention of HTTP caching though, which for a whole class of applications is
a great way to minimise rendering time.

Rails has great support[1] for etag based content expiry.

[1]
[http://api.rubyonrails.org/classes/ActionController/Conditio...](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-
i-stale-3F)

------
blizkreeg
Does anyone here use compression on all of their cached content? Our Redis
cache store has ballooned up to about 8G (we store a lot of html/json
fragments) and is unwieldy to ship around when we want to debug bad data bugs
on our dev machines. We are experimenting with lz4 compression now and the
speed-compression ratio tradeoff looks pretty good with it.

What has been your experience with Rails caching + compression?

~~~
pselbert
If you're using Readthis[0] for Redis caching you can use an alternate
marshaller to avoid the size and performance overhead of marshalling all
objects through Ruby. If you aren't using Readthis you really should, it's
faster than RedisStore, more customizable, and actually maintained!

Mandatory disclaimer, I wrote the gem.

0:
[https://github.com/sorentwo/readthis](https://github.com/sorentwo/readthis)

~~~
blizkreeg
Does marshaling pose a problem (leading to cache flushes) when you upgrade to
a new Ruby version?

~~~
pselbert
No, that hasn't posed a problem in my experience. Note that all of the stores
rely on Ruby marshalling underneath. To my knowledge Readthis is the only
cache that lets you choose something else like pass through, JSON, Oj, etc.

You will definitely have to flush before switching from no compression to
compression or changing marshallers though.

~~~
sandGorgon
I wrote a small snippet to share cache (particularly session) between a php
webapp and rails using Dalli long back. It might be completely broken by now,
but worked brilliantly back then.

it was based roughly on this
[https://gist.github.com/watsonbox/3170415](https://gist.github.com/watsonbox/3170415)

~~~
pselbert
That sounds like a job for phuby!
[https://github.com/tenderlove/phuby](https://github.com/tenderlove/phuby)

No, not really.

------
beefsack
I found the animated GIF next to the article to be incredibly distracting
while reading. I had to remove it using the inspector to be able to get
through the article.

------
driverdan
This is a great guide but it's not complete. One of the biggest problems with
all of these guides is that they focus solely on view caching.

As far as I can tell the Rails ecosystem completely lacks a good model / data
caching system. There are a few gems that do model caching but they all have
major flaws. I'd _love_ to see a good guide on Rails data caching and gems
that eliminate the mess of calls to Rails.cache.fetch.

~~~
mperham
This is because caching should be done as coarsely as possible. Caching views
and entire pages is the common case.

~~~
halostatue
So what’s the best approach for caching API responses?

~~~
karmajunkie
An api response isn't any different from a web page insofar as infrastructure
is concerned. HTTP caching is usually my first choice when I have the
wherewithal to do that.

[Edit: conditional responses are probably the best way to go – save the
bandwidth.]

------
arohner
Great article. You should additionally measure page speed as experienced by
your users, because other pesky things like network congestion, the user's
browser & hardware and the speed of light all affect website performance. If
you measure from _every_ user's browser, you'll get very detailed performance
info. A chart from a recent blog post of mine:
[https://s3.amazonaws.com/static.rasterize.com/cljs-module-
bl...](https://s3.amazonaws.com/static.rasterize.com/cljs-module-blog-
map.jpeg)

Just because the page loads quickly on your laptop doesn't mean it loads
quickly for everyone. I'm working on a tool to measure this stuff:
[https://rasterize.io/blog/speed-
objections.html](https://rasterize.io/blog/speed-objections.html), contact me
if you're interested in early access.

Contact me if you're interested in an early preview.

------
resca79
this is a great article

> The most often-used method of all Rails cache stores is fetch

It's true, but I think you should add performance tests while the app
write/read because the big problem of db/cache is the write that influence
also the read(fetch). Another big problem is the expiration vs garbage
collection after the memory is full.

~~~
pselbert
A request with dozens or hundreds of `fetch` calls will always be slower than
a few `fetch_multi` or `read_multi` calls. All of the current ActiveSupport
compliant libraries support both calls.

~~~
ghiculescu
We've found
[https://github.com/n8/multi_fetch_fragments](https://github.com/n8/multi_fetch_fragments)
to be quite handy for this.

------
chrismorgan
> First, figure about 50 milliseconds for network latency (this is on desktop,
> latency on mobile is a whole other discussion).

And outside the USA, add on another 200ms more. I, an Australian, visited the
USA last year and was surprised, although I had expected it, at how much
faster the Internet was.

I get the general feeling that people in the USA end up much more picky about
performance than their Australian counterparts, because they’re used to a
result which is quite a bit faster anyway.

It’s rather a pity that we as an industry don’t care more about performance,
because on average we’re utterly abysmally appallingly atrocious at it.
There’s simply no good reason for things to be as slow as they are.

------
why-el
> but usually virtualization is a mostly unnecessary step in achieving
> production-like behavior. Mostly, we just need to make sure we're running
> the Rails server in production mode.

Isn't this assuming your development mode has the same memory/cpu as your
production? I can't tell you how many times I get questions from clients who
ask why their 16GB dev box is running fine while their 512MB dyno is slow. The
point of a docker image is to limit these resources, which `rails s
production` does not do.

~~~
nateberkopec
Sorry, I should have qualified that sentence: "a mostly unnecessary step in
achieving production-like behavior *when trying to determine a page's average
response time". That's just been my experience with this. I haven't noticed
significant differences in my Macbook vs Heroku when measuring response times.

It's a fast-and-loose measurement, to be sure. Virtualization/dockerization is
the only way to get a super-accurate measurement.

~~~
why-el
Yeah I have been thinking about this as well. I guess it depends on how wide
the difference is (when I said 512MB, I really meant it. People are still
comparing that to their dev machines).

Somewhat related: How has your experience been setting up docker with Rails? I
have an idea for a project that seamlessly integrate docker into one's Rails
dev workflow, but so far I have found its setup (let alone working with it) to
be cumbersome to say the least.

------
derwiki
Why is `caches_action` not considered a best practice? Using that for popular
landing pages, I'm able to serve over half of my requests in < 10ms.

~~~
nateberkopec
Oh, I hope I didn't give that impression that it's no longer a "best
practice". But you can, of course, accomplish exactly the same thing as an
action cache with fragment caching. The cases where you can use action caching
are so limited (like you said - mostly landing pages) that I feel it's hardly
even worth bothering with when you can just wrap the entire view in a cache
method. That's all I was trying to get across.

~~~
gingerlime
Or `caches_page` and then it won't even hit rack / rails and can be served
directly by nginx. But I agree that the usage scope is quite limited. We still
use it for pages served to not-logged-in users where it makes sense, and both
the performance boost and the reduction of load on the system is noticeable.

------
mosselman
Don't forget to divide the result from `ab` by `x` where `x` is the
concurrency amount (`-c x`).

The 'time per request' that has 'mean, across all concurrent requests' behind
it is the mean of every single request. All other ms values show you the
response time per single request * concurrency. So the percentile table is
only interesting when you take this into account.

------
drm237
In the example where you cache the todo list and use an array of the todo item
ids plus the max updated time, why is that approach better than just updating
the list updated_at time whenever a child record is changed? With your
approach, each todo item needs to be pulled from the db which should be slower
than just checking the parent's updated at time for a cache hit.

------
rubiquity
> _Why don 't we cache as much as we should?_

An alternative question:

 _Why do we have to cache so damn much in Ruby and complicate the hell out of
our infrastructure and code?_

Because Ruby is slow.

~~~
nateberkopec
What about caching in Ruby is complicated to you? It seems pretty simple to
me.

What production website _doesn 't_ cache? You make it sound like it's a Ruby-
only issue.

~~~
swanson
Caching involves adding generally untested code to your production
environment. Caching can bring your production environment out of sync with
your development environment. Caching introduces state between generally
state-less requests - increasing the mental overhead of understanding why you
are seeing certain behavior. Subtle bugs can result in disproportionately bad
behavior (wrong cache key could accidentally show one user's private data to
another user).

I don't think you should never do caching - just that is is non-trivial,
especially once you move past a 15min-blog use case.

~~~
Bluestrike2
Caching makes things more complex, but unfortunately, that's the nature of the
game and there's no escaping it. If you plan things out and break your caching
strategy down into more manageable pieces, you'll be able to work out a way to
make it work as a cohesive whole. It's really easy to just ignore caching
until you're almost done with a project, only to be stuck in the painful
position of trying to make it work with a shoehorn. Just thinking about the
basics in the beginning will help make it a lot easier for you later on.

