Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails caching a paginated collection

Just doing some research on the best way to cache a paginated collection of items. Currently using jbuilder to output JSON and have been playing with various cache_key options.

The best example I've seen is by using the latest record's updated_at plus the amount of items in the collection.

def cache_key
      pluck("COUNT(*)", "MAX(updated_at)").flatten.map(&:to_i).join("-")
end

defined here: https://gist.github.com/aaronjensen/6062912

However this won't work for paginated items, where I always have 10 items in my collection.

Are there any workarounds for this?

like image 832
Jamsi Avatar asked Feb 13 '14 23:02

Jamsi


2 Answers

With a paginated collection, you're just getting an array. Any attempt to monkey patch Array to include a cache key would be a bit convoluted. Your best bet it just to use the cache method to generate a key on a collection-to-collection basis.

You can pass plenty of things to the cache method to generate a key. If you always have 10 items per page, I don't think the count is very valuable. However, the page number, and the last updated item would be.

cache ["v1/items_list/page-#{params[:page]}", @items.maximum('updated_at')] do

would generate a cache key like

v1/items_list/page-3/20140124164356774568000

With russian doll caching you should also cache each item in the list

# index.html.erb
<%= cache ["v1/items_list/page-#{params[:page]}", @items.maximum('updated_at')] do %>
  <!-- v1/items_list/page-3/20140124164356774568000 -->
  <%= render @items %>
<% end %>

# _item.html.erb
<%= cache ['v1', item] do %>
  <!-- v1/items/15-20140124164356774568000 -->
  <!-- render item -->
<% end %>
like image 160
Jacob Evan Shreve Avatar answered Nov 15 '22 05:11

Jacob Evan Shreve


Caching pagination collections is tricky. The usual trick of using the collection count and max updated_at does mostly not apply!

As you said, the collection count is a given so kind of useless, unless you allow dynamic per_page values.

The latest updated_at is totally dependent on the sorting of your collection.

Imagine than a new record is added and ends up in page one. This means that one record, previously page 1, now enters page 2. One previous page 2 record now becomes page 3. If the new page 2 record is not updated more recently than the previous max, the cache key stays the same but the collection is not! The same happens when a record is deleted.

Only if you can guarantee that new records always end up on the last page and no records will ever be deleted, using the max updated_at is a solid way to go.

As a solution, you could include the total record count and the total max updated_at in the cache key, in addition to page number and the per page value. This will require extra queries, but can be worth it, depending on your database configuration and record count.

Another solution is using a key that takes into account some reduced form of the actual collection content. For example, also taking into account all record id's.

If you are using postgres as a database, this gem might help you, though I've never used it myself. https://github.com/cmer/scope_cache_key

And the rails 4 fork: https://github.com/joshblour/scope_cache_key

like image 32
kogre Avatar answered Nov 15 '22 04:11

kogre