14.1. Einleitung

Im Allgemeinen wird mit Caching bei Web-Applikationen immer so lange gewartet, bis man an Performanceprobleme kommt. Als Erstes schaut sich der Admin dann die Datenbank an und fügt hier und dort noch einen Index hinzu. Wenn das alles nicht mehr hilft, schaut man sich dann die Views an und fügt Fragment-Caching hinzu. Das ist aber der suboptimale Ansatz für die Arbeit mit Caches. Ziel dieses Kapitels ist es, Ihnen ein Verständnis für die Funktionsweise von Key-Based-Cache-Expiration zu geben. Damit können Sie dann neue Applikationen schon auf Datenbank-Struktur-Ebene so planen, dass Sie bei der Entwicklung optimal cachen können.
Dabei gibt es zwei Hauptargumente für die Benutzung von Caching:
  • Die Applikation wird für den Anwender schneller. Eine schnellere Webseite resultiert in glücklicheren Anwendern.
  • Sie brauchen weniger Hardware für den Webserver, weil Sie weniger Resourcen zum Abarbeiten der Anfragen benötigen. Im Durchschnitt braucht ein gut gecachetes System nur ein fünftel der Rechenleistung eines nicht gecacheten Systems. Meistens ist die Ersparnis noch höher.
Wenn beide Punkte für Sie irrelevant sind, dann brauchen Sie dieses Kapitel nicht zu lesen.
Wir betrachten drei verschiedene Caching-Methoden:
  • HTTP-Caching
    Das ist der Vorschlaghammer unter den Cache-Methoden und ist die ultimative Performance-Waffe. Gerade Webseiten, die für mobile Endgeräte gedacht sind (z. B. iPhone), sollten versuchen, HTTP-Caching maximal auszunutzen. Bei der Kombination aus Key-Based-Cache-Expiration mit HTTP-Caching sparen Sie massiv Rechenzeit auf dem Server und Bandbreite.
  • Page-Caching
    Das ist der Schraubenzieher unter den Cache-Methoden. Man kann sehr viel Performance herausholen, aber es ist nicht so gut wie HTTP-Caching.
  • Fragment-Caching
    Quasi die Pinzette unter den Cache-Methoden. Aber nicht zu unterschätzen. Kleinvieh macht auch Mist.

Tipp

Das Ziel ist es, alle drei Methoden optimal miteinander zu verknüpfen.

Eine einfache Beispielapplikation

Um die Caching-Methoden auszuprobieren, brauchen wir eine Beispielapplikation. Wir verwenden ein einfaches Telefonbuch mit einem Model für die Firma und ein Model für die Mitarbeiter der Firmen.

Wichtig

Bitte bedenken: Wenn die später aufgezeigten Einsparungen schon bei einer solch einfachen Applikation so groß sind, dann werden sie bei komplexeren Applikationen mit komplexeren Views um so größer sein.
Wir erstellen die neue Rails-App:
MacBook:~ xyz$ rails new phone_book
[...]
MacBook:~ xyz$ cd phone_book 
MacBook:phone_book xyz$ rails generate scaffold company name
[...]
MacBook:phone_book xyz$ rails generate scaffold employee company_id:integer last_name first_name phone_number
[...]
MacBook:phone_book xyz$ rake db:migrate
[...]
MacBook:phone_book xyz$

Models

Wir fügen ein paar rudimentäre Regeln in die beiden Models ein.
app/models/company.rb
class Company < ActiveRecord::Base
  attr_accessible :name

  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
  attr_accessible :company_id, :first_name, :last_name, :phone_number

  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

Die folgenden zwei Company Views verändern wir, um im Index-View die Anzahl der Mitarbeiter und im Show-View alle Mitarbeiter aufzulisten.
app/views/companies/index.html.erb
<h1>Listing companies</h1>

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

<% @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 %>
</table>

<br />

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

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


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

<h2>Listing employees</h2>

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

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

Beispieldaten

Für das einfache Befüllen der Datenbank benutzen wir das Faker Gem (siehe http://faker.rubyforge.org/). Mit Faker kann man zufällige Namen und Telefonnummern generieren. Bitte fügen Sie in der Gemfile-Datei diese Zeile hinzu:
gem 'faker'
Danach ein bundle install starten:
MacBook:phone_book xyz$ bundle install
[...]
MacBook:phone_book xyz$
In der db/seeds.rb lassen wir 30 Firmen mit einer jeweils zufälligen Anzahl an Mitarbeitern erstellen:
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
Das Einspielen erfolgt mit rake db:seed:
MacBook:phone_book xyz$ rake db:seed
MacBook:phone_book xyz$
Sie können die Applikation mit rails server starten und mit einem Webbrowser die Beispieldaten unter den URLs http://0.0.0.0:3000/companies und http://0.0.0.0:3000/companies/1 abrufen.

Normale Geschwindigkeit der zu optimierenden Seiten

Wir optimieren in diesem Kapitel die folgenden Webseiten. Die Rails-Applikation wird mit rails server im Development-Modus gestartet. Die jeweiligen Zahlen hängen natürlich immer von der eingesetzten Hardware ab.
MacBook:phone_book xyz$ rails server
=> Booting WEBrick
=> Rails 3.2.6 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2012-07-13 14:31:45] INFO  WEBrick 1.3.1
[2012-07-13 14:31:45] INFO  ruby 1.9.3 (2012-04-20) [x86_64-darwin11.4.0]
[2012-07-13 14:31:45] INFO  WEBrick::HTTPServer#start: pid=14357 port=3000
Zum Aufruf der Webseiten benutzen wir das Kommandozeilentool curl (http://curl.haxx.se/). Natürlich können Sie die Webseiten auch mit anderen Webbrowsern aufrufen. Wir betrachten die im Rails-Log angezeigte Zeit zum Erstellen der Seite. Dazu muss man in der Realität noch das Ausliefern der Seite an den Webbrowser hinzurechnen.

Liste aller Firmen (Index-View)

Unter der URL http://0.0.0.0:3000/companies bekommt der Anwender eine Liste aller gespeicherten Firmen mit der jeweiligen Anzahl von Mitarbeitern angezeigt.
Das Generieren der Seite dauert 85 ms.
Completed 200 OK in 85ms (Views: 71.9ms | ActiveRecord: 12.5ms)

Detailansicht einer einzelnen Firma (Show-View)

Unter der URL http://0.0.0.0:3000/companies/1 bekommt der Anwender die Detailansicht der ersten Firma mit allen Mitarbeitern angezeigt.
Das Generieren der Seite dauert 21 ms.
Completed 200 OK in 21ms (Views: 19.1ms | ActiveRecord: 1.1ms)

Autor

Stefan Wintermeyer