diff options
Diffstat (limited to 'railties/doc')
-rw-r--r-- | railties/doc/guides/caching/caching_with_rails.txt | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/railties/doc/guides/caching/caching_with_rails.txt b/railties/doc/guides/caching/caching_with_rails.txt new file mode 100644 index 0000000000..54b7445f88 --- /dev/null +++ b/railties/doc/guides/caching/caching_with_rails.txt @@ -0,0 +1,350 @@ +Caching with Rails: An overview +=============================== + +Everyone caches. This guide will teach you what you need to know about +avoiding that expensive round-trip to your database and returning what you +need to return to those hungry web clients in the shortest time possible. + +Specifically, this guide is split into two parts: + +- Basic Caching +- Advanced Caching + +- Basic Caching + * Page Caching + * Action Caching + * Fragment Caching + * Cache Sweeping + * SQL Caching + * Cache stores + +- Advanced Caching + * Fragment Caching with interlock and memcached + * Model Caching with cache_fu and memcached + * Things you wish you didn't know + +== Basic Caching + +This is an introduction to the three types of caching techniques that Rails +provides by default without the use of any third party plugins. + +To get started make sure Base.perform_caching is set to true for your +environment. + +[source, ruby] +----------------------------------------------------- +Base.perform_caching = true +----------------------------------------------------- + +=== Page Caching + +Page caching is a Rails mechanism which allows the request for a generated +page to be fulfilled by the webserver, without ever having to go through the +Rails stack at all. Obviously, this is super fast. Unfortunately, it can't be +applied to every situation (such as pages that need authentication) and since +the webserver is literally just serving a file from the filesystem, cache +expiration is an issue that needs to be dealt with. + +So, how do you enable this super-fast cache behavior? Simple, let's say you +have a controller called ProductController and a 'list' action that lists all +the products + +[source, ruby] +----------------------------------------------------- +class ProductController < ActionController + + caches_page :list + + def list; end + +end +----------------------------------------------------- + +The first time anyone requestsion products/list, Rails will generate a file +called list.html and the webserver will then look for that file before it +passes the next request for products/list to your Rails application. + +By default, the page cache directory is set to Rails.public_path (which is +usually set to RAILS_ROOT + "/public") and this can be configured by changing +the configuration setting Base.cache_public_directory + +The page caching mechanism will automatically add a .html exxtension to +requests for pages that do not have an extension to make it easy for the +webserver to find those pages and this can be configured by changing the +configuration setting Base.page_cache_extension + +In order to expire this page when a new product is added we could extend our +example controler like this: + +[source, ruby] +----------------------------------------------------- +class ProductController < ActionController + + caches_page :list + + def list; end + + def create + expires_page :action => :list + end + +end +----------------------------------------------------- + +If you want a more complicated expiration scheme, you can use cache sweepers +to expire cached objects when things change. This is covered in the section on Sweepers. + +[More: caching paginated results? more examples? Walk-through of page caching?] + +=== Action Caching + +The issue with page caching is that you cannot use it for pages that require +to restrict access somehow. This is where Action Caching comes in. Action +Caching works like Page Caching except for the fact that the incoming web +request does go from the webserver to the Rails stack and Action Pack so that +before_filters can be run on it before the cache is served, so that +authentication and other restrictions can be used while still serving the +result of the output from a cached copy. + +Clearing the cache works in the exact same way as with Page Caching. + +Let's say you only wanted authenticated users to edit or create a Product +object, but still cache those pages: + +[source, ruby] +----------------------------------------------------- +class ProductController < ActionController + + before_filter :authenticate, :only => [ :edit, :create ] + caches_page :list + caches_action :edit + + def list; end + + def create + expires_page :action => :list + expire_action :action => :edit + end + + def edit; end + +end +----------------------------------------------------- + +[More: more examples? Walk-through of action caching from request to response? + Description of Rake tasks to clear cached files? Show example of + subdomain caching? Talk about :cache_path, :if and assing blocks/Procs + to expire_action?] + +=== Fragment Caching + +Life would be perfect if we could get away with caching the entire contents of +a page or action and serving it out to the world. Unfortunately, dynamic web +applications usually build pages with a variety of components not all of which +have the same caching characteristics. In order to address such a dynamically +created page where different parts of the page need to be cached and expired +differently Rails provides a mechanism called Fragment caching. + +Fragment caching allows a fragment of view logic to be wrapped in a cache +block and served out of the cache store when the next request comes in. + +As an example, if you wanted to show all the orders placed on your website in +real time and didn't want to cache that part of the page, but did want to +cache the part of the page which lists all products available, you could use +this piece of code: + +[source, ruby] +----------------------------------------------------- +<% Order.find_recent.each do |o| %> + <%= o.buyer.name %> bought <% o.product.name %> +<% end %> + +<% cache do %> + All available products: + <% Product.find(:all).each do |p| %> + <%= link_to p.name, product_url(p) %> + <% end %> +<% end %> +----------------------------------------------------- + +The cache block in our example will bind to the action that called it and is +written out to the same place as the Action Cache, which means that if you +want to cache multiple fragments per action, you should provide an action_path to the cache call: + +[source, ruby] +----------------------------------------------------- +<% cache(:action => 'recent', :action_suffix => 'all_products') do %> + All available products: +----------------------------------------------------- + +and you can expire it using the expire_fragment method, like so: + +[source, ruby] +----------------------------------------------------- +expire_fragment(:controller => 'producst', :action => 'recent', :action_suffix => 'all_products) +----------------------------------------------------- + +[More: more examples? description of fragment keys and expiration, etc? pagination?] + +=== Sweepers + +Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment) calls in your code by moving all the work +required to expire cached content into an asynchronous process that watches +for changes to your models and implements callbacks to expire cached content. + +Continuing with our Product controller example, we could rewrite it with a +sweeper such as the following: + +[source, ruby] +----------------------------------------------------- +class StoreSweeper < ActionController::Caching::Sweeper + observe Product # This sweeper is going to keep an eye on the Post model + + # If our sweeper detects that a Post was created call this + def after_create(product) + expire_cache_for(product) + end + + # If our sweeper detects that a Post was updated call this + def after_update(product) + expire_cache_for(product) + end + + # If our sweeper detects that a Post was deleted call this + def after_destroy(product) + expire_cache_for(product) + end + + private + def expire_cache_for(record) + # Expire the list page now that we added a new product + expire_page(:controller => '#{record}', :action => 'list') + + # Expire a fragment + expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products') + end +end +----------------------------------------------------- + +Then we add it to our controller to tell it to call the sweeper when certain +actions are called. So, if we wanted to expire the cached content for the list +and edit actions when the create action was called, we could do the following: + +[source, ruby] +----------------------------------------------------- +class ProductController < ActionController + + before_filter :authenticate, :only => [ :edit, :create ] + caches_page :list + caches_action :edit + cache_sweeper :store_sweeper, :only => [ :create ] + + def list; end + + def create + expires_page :action => :list + expire_action :action => :edit + end + + def edit; end + +end +----------------------------------------------------- + +[More: more examples? better sweepers?] + +=== SQL Caching + +Query caching is a Rails feature that caches the result set returned by each +query so that if Rails encounters the same query again for that request, it +will used the cached result set as opposed to running the query against the +database again. + +For example: + +[source, ruby] +----------------------------------------------------- +class ProductController < ActionController + + before_filter :authenticate, :only => [ :edit, :create ] + caches_page :list + caches_action :edit + cache_sweeper :store_sweeper, :only => [ :create ] + + def list + # Run a find query + Product.find(:all) + + ... + + # Run the same query again + Product.find(:all) + end + + def create + expires_page :action => :list + expire_action :action => :edit + end + + def edit; end + +end +----------------------------------------------------- + +In the 'list' action above, the result set returned by the first +Product.find(:all) will be cached and will be used to avoid querying the +database again the second time that finder is called. + +Query caches are created at the start of an action and destroyed at the end of +that action and thus persist only for the duration of the action. + +=== Cache stores + +Rails provides different stores for the cached data for action and fragment +caches. Page caches are always stored on disk. + +The cache stores provided include: + +1) Memory store: Cached data is stored in the memory allocated to the Rails + process +[source, ruby] +----------------------------------------------------- +ActionController::Base.cache_store = :memory_store +----------------------------------------------------- + +2) File store: Cached data is stored on the disk, this is the default store + and the default path for this store is: /tmp/cache + +[source, ruby] +----------------------------------------------------- +ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" +----------------------------------------------------- + +3) DRb store: Cached data is stored in a separate DRb process that all servers +communicate with + +[source, ruby] +----------------------------------------------------- +ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" +----------------------------------------------------- + +4) MemCached store: Cached data is stored using a high-speed caching server +called memcached + +[source, ruby] +----------------------------------------------------- +ActionController::Base.cache_store = :mem_cache_store, "localhost" +----------------------------------------------------- + +5) Custom store: You can define your own cache store (new in Rails 2.1) + +[source, ruby] +----------------------------------------------------- +ActionController::Base.cache_store = MyOwnStore.new("parameter") +----------------------------------------------------- + +== Advanced Caching + +=== memcached and cache_fu +=== memcached and interlock |