ar-resque-counter-cache (formerly ar-async-counter-cache)

This gem allows you to update ActiveRecord (2.3.x) counter cache columns asynchronously using Resque (http://github.com/defunkt/resque). You may want to do this in situations where you want really speedy inserts and have models that "belong_to" many different parents; that is, you want the request making the INSERT to return before waiting for the many UPDATE...SET counter cache SQL queries to finish. You may also want to use this gem to avoid "Mysql::Error: Lock wait timeout exceeded" issues: if you have a lot of children being created at a time for a single parent row, MySQL can run into lock timeouts while waiting for parent row to update its counter cache over and over. A while ago, I remember seeing that Thoughtbot was having a similar issue in its Hoptoad service...

How does ar-resque-counter-cache address these issues? It uses Redis as a temporary counter cache and Resque to actually update the counter cache column sometime in the future. For example, let's say a single Post gets 1000 comments very quickly. This will set a key in Redis indicating that there is a delta of +1000 for that Post's comments_count column. Previously (in versions 3.0.2 and below), it would also queue 1000 Resque jobs. This is where resque-lock-timeout came in. Only one of those jobs will be allowed to run at a time. Once a job acquires the lock it removes all other instances of that job from the queue (see IncrementCountersWorker.around_perform_lock1) using Redis's lrem command. Unfortunately we ran into some giant Redis cpu overload with lrems during traffic spikes and decided to switch to the simpler resque-loner gem. This gem instead uses a key to determine whether or not to enqueue a given job in the first place.

You use it like such:

class User < ActiveRecord::Base
  has_many :comments
  has_many :posts
end

class Post < ActiveRecord::Base
  belongs_to :user, :async_counter_cache => true
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :user, :async_counter_cache => true
  belongs_to :post, :async_counter_cache => "count_of_comments"
end

Notice, you may specify the name of the counter cache column just as you can with the normal belongs_to :counter_cache option. You also may not use both the :async_counter_cache and :counter_cache options in the same belongs_to call.

All you should need to do is require this gem in your project that uses ActiveRecord and you should be good to go;

e.g. In your Gemfile:

gem 'ar-resque-counter-cache', 'x.x.x'

and then in RAILS_ROOT/config/environment.rb somewhere:

require 'ar-resque-counter-cache'

By default, the Resque job is placed on the :counter_caches queue:

@queue = :counter_caches

However, you can change this:

in RAILS_ROOT/config/environment.rb somewhere:

ArAsyncCounterCache.resque_job_queue = :low_priority

ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue can also be used to increment/decrement arbitrary counter cache columns (outside of belongs_to associations):

ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(klass, id, column, direction)

Where:

  • klass is the Class of the ActiveRecord object
  • id is the id of the object
  • column is the counter cache column
  • direction is either :increment or :decrement