A deep understanding of the caching mechanism in Ruby on Rails, rubyrails

Source: Internet
Author: User

A deep understanding of the caching mechanism in Ruby on Rails, rubyrails

Scenarios

First, let me show you several pages in ChangingThePresent.org. I will display several locations on the site that need to be cached. Then, we will point out the choices made for each of them and the code or policies used to implement these pages. In particular, we will focus on the following:

  • Full Static Page
  • Almost unchanged, dynamic pages
  • Dynamic page Fragment
  • Application Data

Let's take a look at the static page. Almost every site has a static page, as shown in 1, which also contains our terms and conditions. You can click register and then select whether to accept the user protocol to browse the corresponding page. For ChangingThePresent, we delete all dynamic content from this page so that Apache can cache it. According to the rules configured in Apache, these contents will never be generated by the Rails server. Therefore, I don't need to consider Rails caching at all.
Figure 1. User Protocol

Next, let's take a look at the dynamic page. Theoretically, ChangingThePresent can have some dynamically constructed pages, but these pages are rarely changed. Since almost all pages will show whether users are logged on, we don't pay much attention to this cache.

Next, let's take a look at the page segment cache. As shown in figure 2, the home page is completely static. Now, some elements have become dynamic. Every day, a series of gifts are displayed on the page. Some of these gifts are randomly selected, and some are selected by our administrator. Pay attention to the Gifts under the section "A Few of our Special Gifts for Mother's Day", and the "login.. This link depends on whether the user logs on. We cannot cache the entire page. The page can only be changed once a day.
Figure 2. Home Page

Finally, consider the application. Unless you surf the Internet more than 15 years ago, all the interesting websites you encounter are dynamic. Most modern applications are layered, and these layers can be more effective by adding caches between layers. ChangingThePresent uses some caching at the database layer. Next, I will discuss in depth the different types of cache, and introduce what kind of cache we use for ChangingThePresent.
Cache static content

Mongrel is a Web server written by Zed Shaw using 2500 lines of Ruby and C. This small server occupies very little memory and is very suitable for Ruby Web applications, such as Rails, Nitro, and Iowa. Mongrel can run on UNIX? And Linux? Can also run on Win32. Mongrel can also be used as a proxy to run on the backend of another Web Server (such as Apache or Litespeed), but this is not necessary-Because Mongrel is an HTTP server, it can be used with all your preferred HTTP tools.

In addition to images, there is not much content about cached static data. Because our website is a charitable portal, it means that we need to pay more attention to user feelings, such as adding images or videos. However, our Web server Mongrel does not serve static data well, so we use Apache to serve image content.

We are now switching to using the graphic accelerator Panther Express to cache the most frequently used images so that they can be accessed more quickly by our customers. To adopt this strategy, we need a subdomain images.changingThePresent.org. Panther Express provides image services directly in the image's local cache, and then sends a request to us. Since the Panther service does not know when we will change the image, we use the HTTP header to make it expire, as shown below:
HTTP cache failure Header

HTTP/1.1 200 OKCache-Control: max-age=86400, must-revalidateExpires: Tues, 17 Apr 2007 11:43:51 GMTLast-Modified: Mon, 16 Apr 2007 11:43:51 GMT

Note that these are not HTML headers. They are built independently of the Web page content. The Web server will be responsible for building these HTTP headers. If a series of such articles on Rails details the Web server configuration, it is not necessary, therefore, I will directly switch to the cache content that can be controlled by the Rails framework. (For more information about Web server configuration, see references ).
Page Cache

If the dynamic page does not change frequently, you can use the page-level cache. For example, blogs and billboards use this cache. Through page caching, Rails can be used to build dynamic HTML pages and store this page in a public directory. In this way, the application server can serve this dynamic page just like other static pages.

If the page has been cached, you do not need to introduce Rails. The page cache is the fastest cache in Rails. At the bottom layer, page caching is actually very easy to implement in Rails. Both page and segment cache occur at the Controller level. You need to inform Rails of the following content:

  • What pages do you Want to cache?
  • When the page content changes, how can you invalidate the page expiration in the cache?

You can use the caches_page command in the Controller class to enable page caching. For example, to cache the privacy_policy and user_agreement pages in about_us_controller, enter the following code:
List 2. Enable page caching

class AboutController < ApplicationController caches_page :privacy_policy, :user_agreement end

If the page expires, the expire_page command can be used. To invalidate the above page when Rails calls the new_pages action, use the following code:
Listing 3. Making the page invalid

class AboutController < ApplicationController caches_page :privacy_policy, :user_agreement   def new_pages  expire_page :action => :privacy_policy  expire_page :action => :user_agreement end end

In addition, there are several small issues that need attention, such as URL. The URL cannot depend on the URL parameter. For example, gifts/water/1 should be used instead of gifts/water? Page = 1. It is very easy to use this type of URL in routes. rb. For example, there is always a tab parameter on our page to show which tab is currently selected. To use this tab as part of a URL, we will have the following routing rules:
Listing 4. Routing rules on the tab

Copy codeThe Code is as follows: map. connect 'Member/: id/: tab',: controller => 'profiles',: action => 'show'

The same method is also required for lists with page parameters and other pages that depend on URL parameters. In addition, security issues need to be considered.

If the page is already in the cache, the Rails framework is not used, and the server cannot manage security for you. The Web server is more willing to present any page in the cache, regardless of whether the user has the permission to view it. Therefore, if you are very concerned about who can view the page, do not use the page cache.

If you only want to cache a simple static page, it is enough to understand the above content. As long as the content is simple, it is not difficult to implement it.

When you want to cache more complex content, you need to make some trade-offs. Because the pages to be cached are highly dynamic, the expiration logic becomes more complex. To handle complex expiration logic, you will need to write and configure a custom cleaner (sweeper ). When some Controllers initiate attacks, these classes will delete the selected elements from the cache.

Most custom cleaner observes some model objects and invalidates one or more cache pages based on the change trigger logic. Listing 5 shows a typical cache cleaner. In this cleaner, developers can define an activity record event, such as after_save. When this event is triggered, the cleaner also sends a message and invalidates a specific page in the cache. The expiration result shown in this example is based on the expire_page method. Many strict applications directly use Ruby's excellent file system utility to explicitly Delete cached pages.
Listing 5. A typical observer

class CauseController < ApplicationController  cache_sweeper :cause_sweeper...class CauseSweeper < ActionController::Caching::Sweeper observe Cause   def after_save(record)  expire_page(:controller => 'causes', :action => 'show',         :id => record.id)  cause.nonprofits.each do |nonprofit|   expire_page(:controller => 'nonprofits', :action => 'show',          :id => nonprofit.id)   end  endend

Now, you may begin to feel the disadvantages of page caching: complexity. Although you can perform page-level caching well, the inherent complexity makes it difficult for applications to test, which increases the possibility of bugs in the system. In addition, if the page is different for each user or you want to cache a page that has been authenticated, you will need to use a method other than page cache. For ChangingThePresent, we have to deal with two situations, because we have to change the link on the basic layout based on whether the user logs in. For most pages, we do not even consider using page-level cache. In order to give you a better understanding of page-level caching, I specifically provided links to a series of excellent articles on page-level caching in the reference section of this article. Next, we will explore another form of full-page caching-Action caching in the future.
Action Cache

Now, you have learned the main advantages and disadvantages of page cache: For most page searches, you do not need to consider using Rails. The advantage of page cache is that it is fast. The disadvantage is the lack of flexibility. If you want to cache the entire page based on conditions in the application, such as identity authentication, you can use action caching.

The action cache works in the same way as the page cache, but the process is slightly different. Rails actually calls the controller before rendering the action. If the page displayed by this action already exists in the cache, Rails will render the page in the cache instead of re-rendering it. Because Rails is used, the speed of action caching is slower than that of page caching, but its advantages are obvious. Almost all Rails authentication modes use the before filter on the controller. Action caching allows you to use authentication and any filters on the controller.

From the perspective of statement composition, the action cache and the page cache should be very similar, only commands are not the same. Listing 6 shows how to use the caches_action command.
Listing 6. Enable action caching

  class AboutController < ApplicationController   caches_action :secret_page, :secret_list   end

The cache expiration and cleanup methods should also be the same. The reason we do not use the action cache is the same as the reason we do not use the page cache, but the segment cache is more important to us.
Cache page segments

With some caching, You Can cache part of the page. The cached content is usually layout. To use the segment cache, developers must first determine the segment by placing the rhtml command on the Web page to enclose a piece of content, as shown in listing 7. On ChangingThePresent.org, we use segment cache to cache the homepage and several other pages. All these pages use database-intensive access and are mostly our most popular pages.
Listing 7. Determining cache segments

<% cache 'gifts_index' do %>  

In listing 7, the cache help program identifies the partition to be cached. The first parameter is the unique name that identifies the cache partition. The second parameter contains the code block-that is, the code between the first do and the last end-This code block accurately identifies the RHTML partition to be cached.

Our website has only one home page, so it is very easy to name this page. Elsewhere, we use a specific method to determine the URL of the webpage so as to uniquely identify the cache segment. For example, when we cache code for specific content (such as world peace or poverty reduction), we need to use the code in listing 8. The code will find a permanent url for it, also known as permalink.
Listing 8. Identify cache segments by URL

<% cache @cause.permalink(params[:id]) do %>

Generally, when caching a separate page, you need to use a cleaner to make it expire. Sometimes, it is easier and easier to use a simple time-based object to expire. By default, Rails does not provide such a mechanism, but there is a plug-in named timed_fragment_cache that can achieve this purpose. With this plug-in, I can specify timeout in the cached content, or in the Controller code that provides dynamic data for this page. For example, the code shown in listing 9 can build dynamic data for this page. The when_fragment_expired method is executed only when the related cache segment expires. This method accepts parameters to specify the timeout duration. It also accepts a code block to specify which content needs to be rebuilt when the content expires. I can also specify the timeout and cache methods on the rhtml page, but I prefer the controller-based method.
Listing 9. Time-based Cache expiration

def index when_fragment_expired 'causes_list', 15.minutes.from_now do   @causes = Cause.find_all_ordered endend

If you can tolerate a little stale data, you can use the timed expiration mechanism to greatly simplify the cache policy. For each cached element, you only need to specify the content to be cached, any controller action that can generate dynamic content, and timeout. Similar to page cache, you can also use the expire_fragment: controller => controller,: action => action,: id => id method to explicitly set the content to expire. This method works in the same way as the cache action and the expiration of the cache page. Next, I will introduce how to configure the later end.
Memcached

So far, I have introduced the page and segment cache model of Ruby on Rails. After reading the API, you can now define the location of the cached data. By default, Rails puts cached pages into the file system. The cached pages and actions all go to the public directory. You can configure the storage location of cached segments. Therefore, memory storage, file system (in the defined directory), database, or memcached services are required. For ChangingThePresent.org, we use memcached.

You can think of Memcached as a large hash graph, which can be obtained through the network. Memory-based Cache is faster, while network-based Cache is more scalable. With plug-in support, Rails can use memcached to cache segmentation and ActiveRecord models. To use memcached, install memcached (for more information, see references) and configure it in environment. rb (or another environment configuration file, such as production. rb.
Listing 10. configuring Cache

config.action_controller.perform_caching = truememcache_options = { :c_threshold => 10_000, :compression => false, :debug => false, :readonly => false, :urlencode => false, :ttl => 300, :namespace => 'igprod', :disabled => false}CACHE = MemCache.new memcache_options

Listing 10 shows a typical configuration. The first line of config. action_controller.perform_caching = true enables caching. Cache options will be prepared for the next row. Note that many options here are used to allow you to obtain more debugging data, disable caching, and define the cache namespace. You can find more information about the configuration options on the memcached site provided in the references section.
Model Cache

The last cache we use is the model-based cache. We use a custom version of the cache plug-in called CachedModel. Model cache is actually a limited form of database cache. Caching is easy to enable by model.

To enable the model to use the cache solution, you only need to extend the CachedModel class instead of the extended ActiveRecord, as shown in listing 11. CachedModel extends ActiveRecord: Base. ActiveRecord is not a full object relational ing layer. This Framework relies heavily on SQL to execute complex features, and users can easily drop to SQL if needed. Using SQL directly causes cache problems because the cache layer must process the complete result set instead of a single database row. Processing the complete result set is often problematic, and it is almost impossible if deep logic of the application is not supported. For this reason, the focus of the CachedModel is placed on the cache of a single model object, and only the query that returns a single row of results is accelerated.
Listing 11. Using CachedModel

Class Cause < CachedModel

Most Rails Applications repeatedly access multiple entries, such as user objects. Model caching can significantly speed up in many cases. For ChangingThePresent, we just started to accelerate the model-based cache.
Conclusion

Although Ruby is a highly productive language, the explanatory feature of Ruby makes it less ideal from the performance perspective. Most major Rails Applications will make up for some shortcomings by effectively using the cache. For ChangingThePresent.org, we mainly use the segment cache and use the time-based method by the Controller to make the segment expiration of the cache invalid. This method is suitable for our website, even if some pages are changed based on the logged-in users.

We also studied the impact of using the CachedModel class supported by memcached. Although our research is limited to the impact of caching on database performance, early results are still promising. In the next article, I will introduce some practical skills that you can use to optimize databases for the Rails example in another real world.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.