Thanks for the screencast. Funny think that rubygems was just updated, I installed it on a dev-server this morning and didn't get 1.2. Thanks for the heads up :)
great one again,
since rails 2.1 caching gets a bit more funny to code then before ;)
@David:
you can define views or user-defined-functions in your database which behave like caching a query or create indices on columns (b-tree, ...) to improve your db-performance,
but,
to store your cached data, retrieved from a database into a database to store the data for caching makes little sense to me.
maybe you lookup the definition of caching and its advantages and disadvantages.
@David, good question! Actually it's very easy to write your own custom cache store to use the database or whatever else you want. See this post by Ryan Daigle on how to do this:
I can't seem to get the memcached version of this running in script/console when using a namespace.
If I create a new rails app, then edit config/environment.rb, adding the line:
config.cache_store = :mem_cache_store, 'localhost', :namespace => 'test'
it will fail to load script/console with the following error:
syntax error, unexpected tASSOC, expecting kEND (SyntaxError)
...re, 'localhost', :namespace => 'test'
^
from /opt/local/lib/ruby/1.8/irb/init.rb:252:in `load_modules'
from /opt/local/lib/ruby/1.8/irb/init.rb:250:in `each'
from /opt/local/lib/ruby/1.8/irb/init.rb:250:in `load_modules'
from /opt/local/lib/ruby/1.8/irb/init.rb:21:in `setup'
from /opt/local/lib/ruby/1.8/irb.rb:54:in `start'
from /opt/local/bin/irb:13
If I remove all of the options ('localhost' and :namespace) it will load correctly.
Isn't the whole point of caching to get the overhead of Ruby and Rails out of the way for unchanging content so that Apache (or Nginx or whatever) can server the content directly? Wouldn't interacting with the DB ruin that?
Caching has a couple of main purposes. First, it can be used to take the load off the database, since retrieving items from the cache is much faster than loading them from the database. Storing the cached objects in the database would defeat much of the benefits of this purpose. (Although, if you are caching complicated joins the cached version would likely be faster)
Second, caching is used to prevent Rails from performing expensive operations. Take for example, the the display of a list of your friends and their recent activities in a social application. This list would require Rails to do complicated joins and load all of the relevant objects. Caching this in memory or in the database will be faster than requiring Rails to regenerate the results. Caching is a critical tool for any high volume site whether you use memcache or the database store. Some people might prefer the database store because it is very easy to share the cache among multiple sites and it is persistent. Personally, memcache is the way to go.
@Carl, that is one form of caching, yes. However, sometimes you do not have the luxury to cache the entire page. Sometimes you can only cache particular queries or results of calculations (or rendering of views). Depending on the speed of things involved it may make perfect sense to store your cache in the database (though this is probably an uncommon case).
How do I put the associations of a db query into the cache for later usage?
Refering to the screencast example: If I try to access a subcategory of the previously cached category query "all_cached.subcategory.name" wouldn't work as the subcategory istn't stored in the cache.
Nice screencast as always, but maybe you should specify one key difference with :file_store, it stores only Strings not Objects.
So caching your Category.all query with cache store set as :file_store will results in an unusable String. Worth mentioning it I think.
When you use this stuff in development, be mindful that when you've got config.cache_classes = false in your environment/development.rb file, then your model classes will be reloaded upon every request. Which means that the classes of the things in your cache don't actually exist anymore at the next request. This can lead to all sorts of errors.
I had a look at the MemoryStore implementation and it's really only a simple hash. So any change made to an object from the cache will be visible to the next request. This is different to memcached where every object is (naturally) duplicated when being sent over the wire. Shouldn't perform the MemoryStore a "dup" on checkout to have the same semantics?
... when initially calling MyClassName.all_cached and the next time it thinks the data is available in the cache, but retrieves nothing, breaking the view.
As many have mentioned, looks like there's some problems with MemoryStore and FileStore. I encourage someone who has detailed this problem to submit a ticket/patch about it at rails.lighthouseapp.com. Looks like there's still a some work to do on them.
@JGeiger, looks like the hash brackets need to be specified when setting the namespace. I'll fix the code example.
@Carl, bypassing Rails through page caching is certainly the fastest caching technique out there, however it is not always practical depending on the app requirements. When deciding a caching technique, I usually go down this list and try the first one that seems like it will work well.
1. page caching
2. action caching
3. render caching (see link in show notes)
4. fragment caching
5. custom caching with Rails.cache
@Jochen, you can fetch the subcategories before putting it into the cache which will result in the subcategories being stored in the cache as well. Storing associations can get a little tricky because it can easily lead to stale data if it's not properly expired in each case.
@Jason, I think you'll have to build the "bypass cache in development" logic into your application itself. The Rails.cache method is so low level that Rails wouldn't know how to bypass it on its own. How would it know what to return on a cache.read? Maybe a config setting to flush the cache between each request in development would be sufficient?
Regarding your problem with it failing on the second request, this has to do with how memory_store keeps the classes which breaks with the "cache_classes = false" setting. I recommend using mem_cache_store until this is resolved.
@sibannac, you can use that all_cached method I created in this episode.
@categories = Category.all_cached
Just watch out as this will break with memory store and file store. You'll need to use mem_cache_store until those problems are resolved.
Hey Ryan - thanks for the additional feedback. I'll look into building the development/non-development checks, but I wanted to make sure I hadn't missed anything obvious first.
Thanx for creating a screencast for this gem of a feature. I otherwise would have missed it in the named_scope-and-friends frenzy of the rails 2.1 launch.
Arni : Yes I didn't meant it was not possible, just it was not documented anywhere and as the different cache stores does not provides the same behavior, maybe it should be mentioned.
okay, maybe I'm stupid, but I don't get how to properly use cache_key: how can I figure out its cache key without accessing the DB and getting the object itself? Here's an example:
An app has rather complicated, large objects, which should be cached. The first time a controller asks for the object with the id 1, it is read from the DB and then saved by its cache key. Now the next time the controller asks for the object with the id 1, the controller itself has no clue about its cache_key, so it can't find the object.
Is it really necessary to do a find(:select => [id, updated_at]) for that object, then generate the cache_key from the timestamp we now have, then check the cache if the object with this cache_key exists?
Somehow this feels ugly and completely wrong. But maybe I just don't get how people normally use the rails cache_key (and no, I'm not referring to the custom cache key from the rather old nubyonrails page that's linked above - that's something else).
@aleco - one usage that immediately occurs to me is loading a blog entry and its comments, where there's a counter cache recording the number of comments. With the new cache_key you can do:
@James: hmm, as the cache_key will only bet updated when the BlogEntry object is saved, you'l have the same cache_key no matter if people added additional comments.
Now I know one could use a sweeper to clean the cache when a comment is added, but I thought the main reason for introducing cache_key is to shift from the paradigm of cleaning caches to updated object keys and have memcache clear the old data itself.
In other words, I'm still hoping for a blog entry/railscast with the title "5 smart ways of using cache_key", as I still can't clearly see its benefit.
If you're using file_store (such as in development mode), you can serialize to and from YAML easily.
Instead of:
def self.all_cached
Rails.cache.fetch('Thing.all') { all }
end
Use this:
def self.all_cached
yml = Rails.cache.fetch('Thing.all') { all.to_yaml }
YAML::load(yml)
end
There are ways to make it smart enough to only serialize to YAML if the cache is configured to use file_store, to avoid the overhead of serialization when using memory_store or mem_cache_store.
Ryan, great screencast as always, Im having some trouble when I store an array of active records in memcached store using this new method. Am I allowed to store active records in memcached? The behavior is really weird, I get the array out of memcached, loop through it, and when I try to access a member it works fine, but when I try to access a method it barfs with method doesnt exist or stack too deep. Using the debugger I see the object.. any ideas?
Hi,
I still struggle to understand how the cached values can be processed later.
Accesing a cached model object immediately after caching works as expected. I can do something like "cached_model.id" to access the corresponding cached value.
But once I try to do the same from another action it's not possible anymore.
I get an error message "stack level too deep".
However, accessing other methods still works fine.
My bad, I never bothered to look at how the cookie store actually saved the data in the browser. Since it seems to be encrypted, my privilege escalation point was totally wrong. Thanks for clearing this up, and keep up the good work.
For those who are having stack overflow problems. Setting
config.cache_classes = false
to true fixed it for me. Unfortunately this has the side effect of making it that models do not reload every time you use them.
Has anyone done any benchmarking of action caching v. render caching (assuming there's fairly little controller work being done)? I might do some if I can't find any, since action caching is meaningfully more difficult to manage.
One question - where does the query cache fit in all of this? The example in the Railscast seems to be taken care of by the query cache, for example. Obviously you could modify things in a controller and then cache them, but for this common case, why do anything at all?
thank you for the nice screencast. i've enabled memcache on 2 high-traffic projects with many db querys and it enhance the speed of these sites conspicuously.
any reason a cache sweeper would work in development, but not in production? I'm running passenger and I've tried with both memory store and file store and my sweepers aren't working. They work great in development though.
Great Cast! Thanks :)
I have the same problem @Ronald stated (http://pastie.org/221873).
The simplest workaround I found was config.cache_classes=true.
Is there another way to use Models in development?
Dont forget to mention that you need to install memcached in your system. It wasnt obvious to me and nothing worked until I ran
apt-get install memcached
appreciated as always :)
Great cast, thanks!
Is there a way to store the cache data in the database?
Thanks for the screencast. Funny think that rubygems was just updated, I installed it on a dev-server this morning and didn't get 1.2. Thanks for the heads up :)
great one again,
since rails 2.1 caching gets a bit more funny to code then before ;)
@David:
you can define views or user-defined-functions in your database which behave like caching a query or create indices on columns (b-tree, ...) to improve your db-performance,
but,
to store your cached data, retrieved from a database into a database to store the data for caching makes little sense to me.
maybe you lookup the definition of caching and its advantages and disadvantages.
mind = blown
@David, good question! Actually it's very easy to write your own custom cache store to use the database or whatever else you want. See this post by Ryan Daigle on how to do this:
http://tinyurl.com/366763
I can't seem to get the memcached version of this running in script/console when using a namespace.
If I create a new rails app, then edit config/environment.rb, adding the line:
config.cache_store = :mem_cache_store, 'localhost', :namespace => 'test'
it will fail to load script/console with the following error:
syntax error, unexpected tASSOC, expecting kEND (SyntaxError)
...re, 'localhost', :namespace => 'test'
^
from /opt/local/lib/ruby/1.8/irb/init.rb:252:in `load_modules'
from /opt/local/lib/ruby/1.8/irb/init.rb:250:in `each'
from /opt/local/lib/ruby/1.8/irb/init.rb:250:in `load_modules'
from /opt/local/lib/ruby/1.8/irb/init.rb:21:in `setup'
from /opt/local/lib/ruby/1.8/irb.rb:54:in `start'
from /opt/local/bin/irb:13
If I remove all of the options ('localhost' and :namespace) it will load correctly.
On OSX using macports ruby 1.8.7 p22.
Isn't the whole point of caching to get the overhead of Ruby and Rails out of the way for unchanging content so that Apache (or Nginx or whatever) can server the content directly? Wouldn't interacting with the DB ruin that?
@Carl,
Caching has a couple of main purposes. First, it can be used to take the load off the database, since retrieving items from the cache is much faster than loading them from the database. Storing the cached objects in the database would defeat much of the benefits of this purpose. (Although, if you are caching complicated joins the cached version would likely be faster)
Second, caching is used to prevent Rails from performing expensive operations. Take for example, the the display of a list of your friends and their recent activities in a social application. This list would require Rails to do complicated joins and load all of the relevant objects. Caching this in memory or in the database will be faster than requiring Rails to regenerate the results. Caching is a critical tool for any high volume site whether you use memcache or the database store. Some people might prefer the database store because it is very easy to share the cache among multiple sites and it is persistent. Personally, memcache is the way to go.
@Carl, that is one form of caching, yes. However, sometimes you do not have the luxury to cache the entire page. Sometimes you can only cache particular queries or results of calculations (or rendering of views). Depending on the speed of things involved it may make perfect sense to store your cache in the database (though this is probably an uncommon case).
How do I put the associations of a db query into the cache for later usage?
Refering to the screencast example: If I try to access a subcategory of the previously cached category query "all_cached.subcategory.name" wouldn't work as the subcategory istn't stored in the cache.
Nice screencast as always, but maybe you should specify one key difference with :file_store, it stores only Strings not Objects.
So caching your Category.all query with cache store set as :file_store will results in an unusable String. Worth mentioning it I think.
@Jonathan: Objects can easily be converted to and from strings (YAML and XML pop into mind).
When you use this stuff in development, be mindful that when you've got config.cache_classes = false in your environment/development.rb file, then your model classes will be reloaded upon every request. Which means that the classes of the things in your cache don't actually exist anymore at the next request. This can lead to all sorts of errors.
@Sam: This can be used with memcached as the caching "engine", so it can hardly be "better than memcached§ when it used memcached ;)
I had a look at the MemoryStore implementation and it's really only a simple hash. So any change made to an object from the cache will be visible to the next request. This is different to memcached where every object is (naturally) duplicated when being sent over the wire. Shouldn't perform the MemoryStore a "dup" on checkout to have the same semantics?
Is there something you need to do to bypass the cache write attempt in development mode? I've got:
config.cache_classes = false
config.action_controller.perform_caching = false
... set in my development.rb, but it still logs
Cache write (will save 0.00154): MyClassName.all
... when initially calling MyClassName.all_cached and the next time it thinks the data is available in the cache, but retrieves nothing, breaking the view.
Anyone else experiencing this?
Great tool...
how can i cache an instance variable in controller?
like @products = Product.find(:all)
I've run into a strange issue, while defining a local to_param method in my models for SEO purpose.
in short terms:
on cached model instances the "to_param" method is missing.
see: http://pastie.org/221873
Looks my previous problems were caused by the :memory_store backend.
:file_cache also has some weird issue returning a string instead of an array
Finally it looks to work with :mem_cache_store.
As many have mentioned, looks like there's some problems with MemoryStore and FileStore. I encourage someone who has detailed this problem to submit a ticket/patch about it at rails.lighthouseapp.com. Looks like there's still a some work to do on them.
@JGeiger, looks like the hash brackets need to be specified when setting the namespace. I'll fix the code example.
@Carl, bypassing Rails through page caching is certainly the fastest caching technique out there, however it is not always practical depending on the app requirements. When deciding a caching technique, I usually go down this list and try the first one that seems like it will work well.
1. page caching
2. action caching
3. render caching (see link in show notes)
4. fragment caching
5. custom caching with Rails.cache
@Jochen, you can fetch the subcategories before putting it into the cache which will result in the subcategories being stored in the cache as well. Storing associations can get a little tricky because it can easily lead to stale data if it's not properly expired in each case.
@Jason, I think you'll have to build the "bypass cache in development" logic into your application itself. The Rails.cache method is so low level that Rails wouldn't know how to bypass it on its own. How would it know what to return on a cache.read? Maybe a config setting to flush the cache between each request in development would be sufficient?
Regarding your problem with it failing on the second request, this has to do with how memory_store keeps the classes which breaks with the "cache_classes = false" setting. I recommend using mem_cache_store until this is resolved.
@sibannac, you can use that all_cached method I created in this episode.
@categories = Category.all_cached
Just watch out as this will break with memory store and file store. You'll need to use mem_cache_store until those problems are resolved.
Hey Ryan - thanks for the additional feedback. I'll look into building the development/non-development checks, but I wanted to make sure I hadn't missed anything obvious first.
Thanx for creating a screencast for this gem of a feature. I otherwise would have missed it in the named_scope-and-friends frenzy of the rails 2.1 launch.
Arni : Yes I didn't meant it was not possible, just it was not documented anywhere and as the different cache stores does not provides the same behavior, maybe it should be mentioned.
okay, maybe I'm stupid, but I don't get how to properly use cache_key: how can I figure out its cache key without accessing the DB and getting the object itself? Here's an example:
An app has rather complicated, large objects, which should be cached. The first time a controller asks for the object with the id 1, it is read from the DB and then saved by its cache key. Now the next time the controller asks for the object with the id 1, the controller itself has no clue about its cache_key, so it can't find the object.
Is it really necessary to do a find(:select => [id, updated_at]) for that object, then generate the cache_key from the timestamp we now have, then check the cache if the object with this cache_key exists?
Somehow this feels ugly and completely wrong. But maybe I just don't get how people normally use the rails cache_key (and no, I'm not referring to the custom cache key from the rather old nubyonrails page that's linked above - that's something else).
@aleco - one usage that immediately occurs to me is loading a blog entry and its comments, where there's a counter cache recording the number of comments. With the new cache_key you can do:
@entry = BlogEntry.find(params[:id])
@comments = Rails.cache.fetch(@entry.cache_key) { @entry.comments }
So you only need to fetch the one record from the database and then you know whether the cache will be good for all the dependent records.
@James: hmm, as the cache_key will only bet updated when the BlogEntry object is saved, you'l have the same cache_key no matter if people added additional comments.
Now I know one could use a sweeper to clean the cache when a comment is added, but I thought the main reason for introducing cache_key is to shift from the paradigm of cleaning caches to updated object keys and have memcache clear the old data itself.
In other words, I'm still hoping for a blog entry/railscast with the title "5 smart ways of using cache_key", as I still can't clearly see its benefit.
Thanks for another excellent Railscast Ryan.
And thanks of course for the link :)
If you're using file_store (such as in development mode), you can serialize to and from YAML easily.
Instead of:
def self.all_cached
Rails.cache.fetch('Thing.all') { all }
end
Use this:
def self.all_cached
yml = Rails.cache.fetch('Thing.all') { all.to_yaml }
YAML::load(yml)
end
There are ways to make it smart enough to only serialize to YAML if the cache is configured to use file_store, to avoid the overhead of serialization when using memory_store or mem_cache_store.
Great screencast, lots of info. Thanks for the heads up on the update.
Ryan, great screencast as always, Im having some trouble when I store an array of active records in memcached store using this new method. Am I allowed to store active records in memcached? The behavior is really weird, I get the array out of memcached, loop through it, and when I try to access a member it works fine, but when I try to access a method it barfs with method doesnt exist or stack too deep. Using the debugger I see the object.. any ideas?
Hi,
I still struggle to understand how the cached values can be processed later.
Accesing a cached model object immediately after caching works as expected. I can do something like "cached_model.id" to access the corresponding cached value.
But once I try to do the same from another action it's not possible anymore.
I get an error message "stack level too deep".
However, accessing other methods still works fine.
What am I doing or thinking wrong?
My bad, I never bothered to look at how the cookie store actually saved the data in the browser. Since it seems to be encrypted, my privilege escalation point was totally wrong. Thanks for clearing this up, and keep up the good work.
For those who are having stack overflow problems. Setting
config.cache_classes = false
to true fixed it for me. Unfortunately this has the side effect of making it that models do not reload every time you use them.
Regarding the stack errors -
The official ticket is at
http://dev.rubyonrails.org/ticket/10896
and the solution that worked best for me (since I'm not using mem_cached) was -
change line 244 of "attribute_methods.rb" to read
if false and self.class.primary_key.to_s == method_name
Has anyone done any benchmarking of action caching v. render caching (assuming there's fairly little controller work being done)? I might do some if I can't find any, since action caching is meaningfully more difficult to manage.
One question - where does the query cache fit in all of this? The example in the Railscast seems to be taken care of by the query cache, for example. Obviously you could modify things in a controller and then cache them, but for this common case, why do anything at all?
>> Rails.cache.write('date', Date.today)
=> false
Why could this be happening?
I have memcached running and in my development.rb: config.cache_store = :mem_cache_store
All other caching works (fragment etc.) Using rails 2.1
Thanks for any help,
Justus
Thanks Ryan,
great cast as always.
thank you for the nice screencast. i've enabled memcache on 2 high-traffic projects with many db querys and it enhance the speed of these sites conspicuously.
i think i use it for all projects now ;-)
thanks a lot ryan
any reason a cache sweeper would work in development, but not in production? I'm running passenger and I've tried with both memory store and file store and my sweepers aren't working. They work great in development though.
The development mode issue some people were having seems to be a rails bug. See: https://rails.lighthouseapp.com/projects/8994/tickets/1339
This patch seems to fix the problem (I haven't tested it personally).
https://rails.lighthouseapp.com/attachments/77318/rebased-20090112-prevented-AR.reset_subclasses-from-killing-existing.patch
If i'm using file_store caching, where the database record cached with Rails.fetch get stored? In the memory aniway?
Thanks
Great Cast! Thanks :)
I have the same problem @Ronald stated (http://pastie.org/221873).
The simplest workaround I found was config.cache_classes=true.
Is there another way to use Models in development?
What is the best way to set up a rails environment that has two cache stores? One is for MemcacheD and the other MemcacheDB.
Dont forget to mention that you need to install memcached in your system. It wasnt obvious to me and nothing worked until I ran
apt-get install memcached
great! just what I needed, thanks again
May I ask how I can get the cache monitor at the bottom of the screen?