Reduce ‘N’ DB Updates to 1 When Using `touch: true`

TL;DR Use the activerecord-delay_touching gem to speed up writes in your rails app by consolidating touch calls into a single UPDATE round-trip to the database.


Did you know that a single call to ActiveRecord#save can result in hundreds of database round-trips?

In this post I’ll show you how to fix it without using no_touching. no_touching is fine for when you don’t care about tracking when your records were updated, but frequently you do need to track that. For example, when you are using fragment caching or Russian doll caching, you need updated_at to be correct.

In the middle of last year, when I was consulting on a Spree project, I found that ActiveRecord’s touch: true can result in an enormous number of redundant UPDATE calls to the database. In my case there was potential to update hundreds of child objects in a single action, which resulted in hundreds of touch calls to a single parent object—and this caused hundreds more cascading touch calls to the parent’s parent. There was a noticeable effect on performance.

Although I encountered this problem in Spree, it is not Spree-specific. It is a general problem any time you use touch: true and your controller can update many child objects in a single action.

A good example of this is accepts_nested_attributes_for, which lets you mass-update attributes of child records in a has_many relationship by making a single call to ParentObject.save. But if the child class has touch: true on the belongs_to statement (e.g. for fragment caching), Rails calls touch on the parent object N times—where N is the number of child objects. Another example is when giving the user a bunch of checkboxes so they can indicate which objects are related. (See HABTM Checkboxes and HABTM Checkboxes (revised)).

In all these cases, the parent object only needs to be touched once. Touching it lots of times slows down response time (yes I measured it) and puts unnecessary load on the database.

To solve it, I created a small gem called activerecord-delay_touching. Its API is modeled after the no_touching method that was introduced in Rails 4.1. Except that instead of not touching, it batches up the touches into the smallest possible number of touch calls.

Usage:

ActiveRecord::Base.delay_touching do
  # ...code that results in `touch` being called...
end

When the end of the block is reached, all the batched-up touches are reduced and executed. They occur synchronously. It does not run in a background processor. This is by design—running in the background would screw up subsequent requests that depend on updated_at, such as in cache keys.

In addition, the gem consolidates touches across multiple rows in each table. For example, if touch was called on two Person objects with ids 1 and 2, it will make a single call to update updated_at where people.id in (1,2).

And, finally, it captures and reduces cascading touches. In other words if, while running through all the after_touch calls, more touch calls occur, it batches them up and runs them together in a second pass.

It keeps doing this until there are no more touches, or until the sun swallows up the earth. Whichever comes first.

There are a couple of things to be aware of when using the gem. Read the “Gotchas” section of the README.

The gem is well-tested. It has thorough specs, and it has been used in production for six months.

If you’d like to use it or contribute, please do. Here’s the source. Check out the README for more examples.

2 thoughts on “Reduce ‘N’ DB Updates to 1 When Using `touch: true`”

    1. Yes, I have planned to submit a pull request but I was giving it more time in the wild. Thank you for supporting the idea. I’ll go ahead and make a pull request.

Leave a Reply

Your email address will not be published. Required fields are marked *

Feel free to use <a>, <b>, <i>, <strong>, <em>, <strike>, <code>.

Code blocks:
[code language="ruby/javascript/html/css/sass/bash"]
[/code]