I just learned about a feature in Rails that I've been wanting for a long time.
In the past, if you were to run two identical database queries in a row, it would hit the database twice
produces the following logging
If you freeze to the Rails Edge the previous code will now produce this
So the second call is being pulled from the cache and takes 0.000000 seconds to retrieve By default this is available when running in a controller or .rhtml
This is by no means a replacement for memcache, but I like it because it increases performance without having to do any additional coding or configuration. Another thing to remember is that the cache only lasts as long as the request, so you don't need to worry about stale data or cache expiration. Now... Why would any programmer do a find for the same id multiple times in one request? You probably won't, but ActiveRecord will. If you have a highly denormalized database with many lookup tables, this can be a huge performance improvement. Let's say you have 5 stores, 200 products and 5 product_types
p = Person.find 1
p = Person.find 1
produces the following logging
Person Load (0.010000)?[0m ?[0;1mSELECT * FROM people WHERE (people.`id` = 1)
Person Load (0.010000)?[0m ?[0;1mSELECT * FROM people WHERE (people.`id` = 1)
If you freeze to the Rails Edge the previous code will now produce this
Person Load (0.010000)?[0m ?[0;1mSELECT * FROM people WHERE (people.`id` = 1)
CACHE (0.000000)?[0m ?[0mSELECT * FROM people WHERE (people.`id` = 1)
So the second call is being pulled from the cache and takes 0.000000 seconds to retrieve By default this is available when running in a controller or .rhtml
If you want to see this work on the console you can use the cache method
Person.cache do
p = Person.find 1
p = Person.find 1
end
This is by no means a replacement for memcache, but I like it because it increases performance without having to do any additional coding or configuration. Another thing to remember is that the cache only lasts as long as the request, so you don't need to worry about stale data or cache expiration. Now... Why would any programmer do a find for the same id multiple times in one request? You probably won't, but ActiveRecord will. If you have a highly denormalized database with many lookup tables, this can be a huge performance improvement. Let's say you have 5 stores, 200 products and 5 product_types
s = Store.find 1, :include=>:products
s.products.each { |p| puts "#{p.name} is of type #{p.product_type.name}" }
Doing a puts is obviously a contrived example, but I often traverse model relationships like this in my views.
Before this feature, these two lines would have produced 201 database queries
Now with Query Caching this will be between 2 and 6 queries.
You might think that you could just add a :include => :product_type in your product model. This works while you're loading products, but not if you've already included products in store. In other words, it appears that ActiveRecord can only do an :include one level deep
This is a great way to keep from having to roll your own solution or monkey patch ActiveRecord, I look forward to this feature moving from edge to production.6 Comments
Leave a comment
0 TrackBacks
Listed below are links to blogs that reference this entry: Rails edge finally supports Query Caching.
TrackBack URL for this entry: http://www.nearinfinity.com/mt/mt-tb.cgi/498



So is this type of caching analogous to a Hibernate Session and/or JPA Persistence Context? In other words, do you get the exact same object instance back in your first example of finding the Person with id 1 twice, such that you might have to start dealing with potential side effects? Does ActiveRecord do any automatic dirty-checking like Hibernate? If not it probably won't create any other issues, e.g. if all it does is cache the values in an entity and not the entity itself, that I can think of at the moment (which certainly doesn't mean there aren't any).
In my testing, I found that rails does not return the same object twice. If you call find twice, you will two two objects just as before. Rails has always had the problem of changing a value in the p1 object and not having it reflect in the p2 object. This does not change that.
In reality, this is not a problem very often.
Also, ActiveRecord does not do dirty checking (there are plugins that add it.) There should not be any new side effects caused by this behavior.
Actually, I don't understand how this feature might be useful. Actually most DB (incl. MySQL) performs caching.
After having discussed with Mike Clark about this point, he told me the major pro of this feature is to avoid a call to the DB server. But I am not sure this will optimize the code since the connection is already done by AR.
Anyway, I really prefer to catch a query stored in my DB than growing the part of the memory used by Ruby, which is already.... important!
I think you might be mistaken about ActiveRecord only doing an :include one level deep. Shouldn't you be able to:
:include => [{:product => :product_type}]
Not that it really matters, but it might not be the best example for where AR caching is of use :)
i second Josh's comment. I've had no problem doing arbitrarily deep eager loading by :include.
Josh,
Thank You, I wasn't aware that :include could take an array of hashes.
That probably would fix the particular problem that I was having.
I'll have to think about other cases where caching would be useful.