15.1. Introduction

Where caching of web applications is concerned, most people tend to wait until they encounter performance problems. Then the admin first looks at the database and adds an index here and there. If that does not help, he has a look at the views and adds fragment caching. But this is not the best approach for working with caches. The aim of this chapter is to help you understand how key based cache expiration works. You can then use this approach to plan new applications already on the database structure level in such a way that you can cache optimally during development.
There are two main arguments for using caching:
  • The application becomes faster for the user. A faster web page results in happier users.
  • You need less hardware for the web server, because you require less resources for processing the queries. On average, a well cached system only needs a fifth of the processing power of a non-cached system. Quite often, it is even less.
If these two arguments are irrelevant for you, then you do not need to read this chapter.
We are going to look at three different caching methods:
  • HTTP caching
    This is the sledge hammer among the caching methods and the ultimate performance weapon. In particular, web pages that are intended for mobile devices (for example iPhone) should try to make the most of HTTP caching. If you use a combination of key based cache expiration and HTTP caching, you save a huge amount of processing time on the server and also bandwidth.
  • Page caching
    This is the screwdriver among the caching methods. You can get a lot of performance out of the system, but it is not as good as HTTP caching.
  • Fragment caching
    The tweezers among the caching methods, so to speak. But don't underestimate it. Every little helps.

Buy the new Rails 5.1 version of this book.

The aim is to optimally combine all three methods.

A Simple Example Application

To try out the caching methods, we need an example application. We are going to use a simple phone book with a model for the company and a model for the employees of the company.

Buy the new Rails 5.1 version of this book.

Please consider: if the processing power you save (shown later) is already so significant in such a simple application, it will be even more significant in a more complex application with more complex views.
We create the new Rails app:
$ rails new phone_book
  [...]
$ cd phone_book 
$ rails generate scaffold company name
  [...]
$ rails generate scaffold employee company_id:integer last_name first_name phone_number
  [...]
$ rake db:migrate
  [...]
$

Models

We insert a few rudimentary rules in the two models.
app/models/company.rb
class Company < ActiveRecord::Base
  validates :name,
            presence: true,
            uniqueness: true

  has_many :employees, dependent: :destroy

  def to_s
    name
  end
end
app/models/employee.rb
class Employee < ActiveRecord::Base
  belongs_to :company, touch: true

  validates :first_name,
            presence: true

  validates :last_name,
            presence: true

  validates :company,
            presence: true

  def to_s
    "#{first_name} #{last_name}"
  end
end

Views

We change the following two company views to list the number of employees in the Index view and all the employees in the Show view.
app/views/companies/index.html.erb
<h1>Listing companies</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Number of employees</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>

  <tbody>
    <% @companies.each do |company| %>
      <tr>
        <td><%= company.name %></td>
        <td><%= company.employees.count %></td>
        <td><%= link_to 'Show', company %></td>
        <td><%= link_to 'Edit', edit_company_path(company) %></td>
        <td><%= link_to 'Destroy', company, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Company', new_company_path %>
app/views/companies/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @company.name %>
</p>

<%= link_to 'Edit', edit_company_path(@company) %> |
<%= link_to 'Back', companies_path %>

<h1>Listing employees</h1>

<table>
  <thead>
    <tr>
      <th>Last name</th>
      <th>First name</th>
      <th>Phone number</th>
    </tr>
  </thead>

  <tbody>
    <% @company.employees.each do |employee| %>
      <tr>
        <td><%= employee.last_name %></td>
        <td><%= employee.first_name %></td>
        <td><%= employee.phone_number %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Example Data

To easily populate the database, we use the Faker gem (see http://faker.rubyforge.org/). With Faker, you can generate random names and phone numbers. Please add the following line in the Gemfile:
gem 'faker'
Then start a bundle install:
$ bundle install
[...]
$
In the db/seeds.rb we create 30 companies with a random number of employees in each case:
30.times do
  company = Company.new(:name => Faker::Company.name)
  if company.save
    SecureRandom.random_number(100).times do
      company.employees.create(
                               first_name:   Faker::Name.first_name, 
                               last_name:    Faker::Name.last_name, 
                               phone_number: Faker::PhoneNumber.phone_number
                              )
    end
  end
end
We populate it via rake db:seed
$ rake db:seed
$
You can start the application with rails server and retrieve the example data with a web browser by going to the URLs http://0.0.0.0:3000/companies and http://0.0.0.0:3000/companies/1.

Normal Speed of the Pages to Optimize

In this chapter, we optimize the following web pages. Start the Rails application in development mode with rails server. The relevant numbers of course depend on the hardware you are using.
$ rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2013-07-17 23:50:11] INFO  WEBrick 1.3.1
[2013-07-17 23:50:11] INFO  ruby 2.0.0 (2013-06-27) [x86_64-darwin12.4.0]
[2013-07-17 23:50:11] INFO  WEBrick::HTTPServer#start: pid=43210 port=3000
To access the web pages, we use the command line tool curl (http://curl.haxx.se/). Of course you can also access the web pages with other web browsers. We look at the time shown in the Rails log for creating the page. In reality, you need to add the time it takes for the page to be delivered to the web browser.

List of All Companies (Index View)

At the URL http://0.0.0.0:3000/companies the user can see a list of all saved companies with the relevant number of employees.
Generating the page takes 48ms.
Completed 200 OK in 48ms (Views: 38.9ms | ActiveRecord: 8.3ms)

Detailled View of a Single Company (Show View)

At the URL http://0.0.0.0:3000/companies/1 the user can see the details of the first company with all employees.
Generating the page takes 11ms.
Completed 200 OK in 11ms (Views: 8.6ms | ActiveRecord: 0.8ms)

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!