Rails counter_cache for Model.count without any association, in order to make SELECT COUNT (*) faster
Even more trivial than making a Cache
model is to just use Rails.cache
.
Rails.cache.read("elephant_count") #=> nil
Rails.cache.write("elephant_count", 1) #=> true
Rails.cache.read("elephant_count") #=> 1
Rails uses a file store by default (tmp/cache).
Then you could just place a Rails.cache.write increment and decrement into your model's after_create
and after_destroy
hooks, and override Model.size
with a call to Rails.cache.read.
You could initialize the cache whenever Rails first initializes by placing a file named something like initialize_cache.rb
in config/initializers containing:
Rails.cache.write('elephant_count', 0) if Rails.cache.read('elephant_count').nil?
If you want to have a maintained counter at all, whether using counter_cache
or doing it manually, Rails will maintain your counters using callbacks, which will increase/decrease the counter when a new descendant is created/destroyed.
I am not aware of a means to store a counter_cache
without using the belongs_to
relationship, because only the parent can store the count of the children.
Weighing Performance
If your table is going to get 'large', populate your test database with a large number of rows then start running some SQL queries using EXPLAIN
to get the performance of your database queries. See if the performance hit in doing record creation/destruction with counter_cache
is offset by how often you need to access these counters in the first place.
If the counter does not need to be 100% accurate at all times, you can instead update the caches periodically using a cron
job or background worker.
In summary:
- You should only use counter_cache if you need those counters enough to offset the slightly longer time taken to create/destroy a record.
- Using
counter_cache
vs a manual alternative that uses callbacks is, as far as I am aware, unlikely to result in much of a detriment to performance. - If the cache does not need to be accurate, take advantage of that and perform the calculations less often.
Take a look at http://guides.rubyonrails.org/caching_with_rails.html Specifically, you'll want to take a look at the section regarding cache stores. Using cache stores, you can store values into cache for arbitrary things.
For example, you could have a method called on the Model called get_count which would be filled initially by the count but incremented by 1 with an after_create callback. If it's not necessary to keep it up to date, you can update this every x minutes so that you're mostly accurate.
I personally use memcache as a store for things like this. Just make sure you keep the cache up to date according to your needs.