Caching ist wichtig, um die Leistung und Skalierbarkeit von Webanwendungen zu verbessern – und Caching in Ruby on Rails ist da keine Ausnahme. Durch das Speichern und Wiederverwenden der Ergebnisse teurer Berechnungen oder Datenbankabfragen reduziert das Caching die Zeit und die Ressourcen, die für die Bearbeitung von Nutzeranfragen benötigt werden, erheblich.

Wir zeigen dir, wie du verschiedene Arten von Caching in Rails implementieren kannst, z. B. Fragment Caching und Russian Doll Caching. Außerdem zeigen wir dir, wie du Cache-Abhängigkeiten verwaltest, Cache-Speicher auswählst und Best Practices für den effektiven Einsatz von Caching in einer Rails-Anwendung umreißt.

Dieser Artikel setzt voraus, dass du dich mit Ruby on Rails auskennst, Rails Version 6 oder höher verwendest und mit Rails Views vertraut bist. Die Codebeispiele zeigen, wie du Caching in neuen oder bestehenden View-Templates einsetzen kannst.

Arten von Ruby on Rails-Caching

In Ruby on Rails-Anwendungen gibt es verschiedene Arten des Cachings, die von der Ebene und Granularität des zu cachenden Inhalts abhängen. Die wichtigsten Arten, die in modernen Rails-Anwendungen verwendet werden, sind:

  • Fragment-Caching: Hier werden Teile einer Webseite gecached, die sich nicht häufig ändern, wie Kopf- und Fußzeilen, Seitenleisten oder statische Inhalte. Das Caching von Fragmenten reduziert die Anzahl der Teilbereiche oder Komponenten, die bei jeder Anfrage gerendert werden.
  • Russian-Doll-Caching: Caching verschachtelter Fragmente einer Webseite, die voneinander abhängen, wie z. B. Sammlungen und Verknüpfungen. Das Russian-Doll-Caching verhindert unnötige Datenbankabfragen und erleichtert die Wiederverwendung von unveränderten Fragmenten im Cache.

Zwei weitere Arten des Cachings waren früher Teil von Ruby on Rails, sind aber jetzt als separate Gems verfügbar:

  • Seiten-Caching: Zwischenspeichern ganzer Webseiten als statische Dateien auf dem Server, wobei der gesamte Lebenszyklus des Seiten-Renderings umgangen wird
  • Action-Caching: Zwischenspeichern der Ausgabe ganzer Controller-Aktionen. Es ähnelt dem Seiten-Caching, ermöglicht dir aber die Anwendung von Filtern wie der Authentifizierung.

Seiten- und Aktionscaching werden nur selten verwendet und für die meisten Anwendungsfälle in modernen Rails-Anwendungen nicht mehr empfohlen.

Fragment-Caching in Ruby on Rails

Mit dem Fragment-Caching kannst du Teile einer Seite zwischenspeichern, die sich nur selten ändern. Zum Beispiel könnte eine Seite, die eine Liste von Produkten mit den dazugehörigen Preisen und Bewertungen anzeigt, Details zwischenspeichern, die sich wahrscheinlich nicht ändern werden.

Gleichzeitig kann Rails dynamische Teile der Seite – wie Kommentare oder Bewertungen – bei jedem Laden der Seite neu darstellen. Das Zwischenspeichern von Fragmenten ist weniger nützlich, wenn sich die zugrunde liegenden Daten einer Ansicht häufig ändern, da der Cache häufig aktualisiert werden muss.

Als einfachste Art des Cachings in Rails sollte Fragment-Caching deine erste Wahl sein, wenn du deine Anwendung mit Caching ausstattest, um die Leistung zu verbessern.

Um Fragment-Caching in Rails zu nutzen, verwende die Hilfsmethode cache in deinen Views. Schreibe zum Beispiel den folgenden Code, um ein Teilprodukt in deiner Ansicht zwischenzuspeichern:

<% @products.each do |product| %>
  <% cache product do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

Die Hilfsmethode cache generiert einen Cache-Schlüssel, der auf dem Klassennamen jedes Elements, id, und dem Zeitstempel von updated_at basiert (zum Beispiel products/1-20230501000000). Wenn ein Nutzer das nächste Mal dasselbe Produkt anfordert, holt der cache-Helfer das zwischengespeicherte Fragment aus dem Cache-Speicher und zeigt es an, ohne das Produkt aus der Datenbank zu lesen.

Du kannst den Cache-Schlüssel auch anpassen, indem du dem Helfer cache Optionen übergibst. Wenn du z. B. eine Versionsnummer oder einen Zeitstempel in deinen Cache-Schlüssel einfügen möchtest, schreibe etwas wie das Folgende:

<% @products.each do |product| %>
  <% cache [product, "v1"] do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

Alternativ kannst du auch eine Ablaufzeit festlegen:

<% @products.each do |product| %>
  <% cache product, expires_in: 1.hour do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

Im ersten Beispiel wird v1 an den Cache-Schlüssel angehängt (zum Beispiel products/1-v1). Das ist nützlich, um den Cache ungültig zu machen, wenn du die Teilvorlage oder das Layout änderst. Das zweite Beispiel legt eine Verfallszeit für den Cache-Eintrag fest (1 Stunde), die dabei hilft, veraltete Daten ablaufen zu lassen.

Russian-Doll-Caching in Ruby on Rails

Russian-Doll-Caching ist eine leistungsstarke Caching-Strategie in Ruby on Rails, die die Leistung deiner Anwendung optimiert, indem sie Caches ineinander verschachtelt. Es nutzt das Rails-Fragment-Caching und Cache-Abhängigkeiten, um redundante Arbeit zu minimieren und die Ladezeiten zu verbessern.

In einer typischen Rails-Anwendung renderst du oft eine Sammlung von Elementen, von denen jedes mehrere untergeordnete Komponenten hat. Wenn du ein einzelnes Element aktualisierst, solltest du vermeiden, die gesamte Sammlung oder alle nicht betroffenen Elemente neu zu rendern. Verwende Russian-Doll-Caching, wenn du mit hierarchischen oder verschachtelten Datenstrukturen arbeitest, besonders wenn die verschachtelten Komponenten ihre eigenen Daten haben, die sich unabhängig voneinander ändern können.

Der Nachteil von Russian-Doll-Caching ist, dass es die Komplexität erhöht. Du musst die Beziehungen zwischen den verschachtelten Ebenen der Elemente, die du zwischenspeichern willst, verstehen, um sicherzustellen, dass du die richtigen Elemente zwischenspeicherst. In manchen Fällen musst du Assoziationen zu deinen Active Record-Modellen hinzufügen, damit Rails die Beziehungen zwischen den zwischengespeicherten Datenelementen ableiten kann.

Wie beim normalen Fragment-Caching wird auch beim Russian-Doll-Caching die Hilfsmethode cache verwendet. Wenn du zum Beispiel eine Kategorie mit ihren Unterkategorien und Produkten in deiner Ansicht zwischenspeichern möchtest, schreibst du etwas wie folgendes:

<% @categories.each do |category| %>
  <% cache category do %>
    <h2><%= category.name %></h2>
    <% category.subcategories.each do |subcategory| %>
    <% cache subcategory do %>
    <h3><%= subcategory.name %></h3>
    <% subcategory.products.each do |product| %>
    <% cache product do %>
        <%= render partial: "product", locals: { product: product } %>
        <% end %>
    <% end %>
    <% end %>
    <% end %>
  <% end %>
<% end %>

Der cache-Helfer speichert jede verschachtelte Ebene separat im Cache-Speicher. Wenn die gleiche Kategorie das nächste Mal angefordert wird, holt sie ihr zwischengespeichertes Fragment aus dem Cache-Speicher und zeigt es an, ohne es erneut zu rendern.

Wenn sich jedoch die Details einer Unterkategorie oder eines Produkts ändern – z. B. der Name oder die Beschreibung -, wird das zwischengespeicherte Fragment ungültig und mit den aktualisierten Daten neu gerendert. Das Russian-Doll-Caching sorgt dafür, dass du nicht eine ganze Kategorie ungültig machen musst, wenn sich eine einzelne Unterkategorie oder ein Produkt ändert.

Cache-Abhängigkeitsmanagement in Ruby on Rails

Cache-Abhängigkeiten sind Beziehungen zwischen zwischengespeicherten Daten und den zugrundeliegenden Quellen, deren Verwaltung kompliziert sein kann. Wenn sich die Quelldaten ändern, sollten alle zugehörigen gecachten Daten ablaufen.

Rails kann Zeitstempel verwenden, um die meisten Cache-Abhängigkeiten automatisch zu verwalten. Jedes Active Record-Modell hat die Attribute created_at und updated_at, die angeben, wann der Cache den Datensatz erstellt oder zuletzt aktualisiert hat. Um sicherzustellen, dass Rails das Caching automatisch verwalten kann, definierst du die Beziehungen deiner Active Record-Modelle wie folgt:

class Product < ApplicationRecord
  belongs_to :category
end
class Category < ApplicationRecord
  has_many :products
end

In diesem Beispiel:

  • Wenn du einen Produktdatensatz aktualisierst (z. B. indem du den Preis änderst), ändert sich der Zeitstempel updated_at automatisch.
  • Wenn du diesen Zeitstempel als Teil deines Cache-Schlüssels verwendest (z. B. products/1-20230504000000), wird dadurch auch dein gecachtes Fragment automatisch ungültig.
  • Um das zwischengespeicherte Fragment deiner Kategorie ungültig zu machen, wenn du einen Produktdatensatz aktualisierst – z. B. weil er aggregierte Daten wie den Durchschnittspreis anzeigt -, verwende die Methode touch in deinem Controller (@product.category.touch) oder füge eine Option touch in deiner Modellzuordnung hinzu (belongs_to :category touch: true).

Ein weiterer Mechanismus zur Verwaltung von Cache-Abhängigkeiten ist die Verwendung von Low-Level-Caching-Methoden – wie fetch und write – direkt in deinen Modellen oder Controllern. Mit diesen Methoden kannst du beliebige Daten oder Inhalte in deinem Cache-Speicher mit benutzerdefinierten Schlüsseln und Optionen speichern. Ein Beispiel:

class Product < ApplicationRecord
  def self.average_price
    Rails.cache.fetch("products/average_price", expires_in: 1.hour) do
    average(:price)
    end
  end
end

In diesem Beispiel wird gezeigt, wie du berechnete Daten – z. B. den Durchschnittspreis aller Produkte – mit der Methode fetch mit einem benutzerdefinierten Schlüssel (products/average_price) und einer Ablaufoption (expires_in: 1.hour) eine Stunde lang zwischenspeichern kannst.

Die Methode fetch versucht zunächst, die Daten aus dem Cache-Speicher zu lesen. Wenn sie die Daten nicht finden kann oder die Daten abgelaufen sind, führt sie den Block aus und speichert das Ergebnis im Cache-Speicher.

Um einen Cache-Eintrag vor Ablauf der Frist manuell zu deaktivieren, verwendest du die Methode write mit der Option force:

Rails.cache.write("products/average_price", Product.average(:price), force: true))

Cache-Speicher und Backends in Ruby on Rails

In Rails kannst du verschiedene Cache-Speicher oder Backends auswählen, um deine gecachten Daten und Inhalte zu speichern. Der Rails-Cache-Speicher ist eine Abstraktionsschicht, die eine gemeinsame Schnittstelle für die Interaktion mit verschiedenen Speichersystemen bietet. Ein Cache-Backend implementiert die Cache-Speicher-Schnittstelle für ein bestimmtes Speichersystem.

Rails unterstützt von Haus aus mehrere Arten von Cache-Speichern oder Backends, die im Folgenden näher beschrieben werden.

Memory Store

Der Memory Store verwendet einen In-Memory-Hash als Cache-Speicher. Er ist schnell und einfach, hat aber eine begrenzte Kapazität und Persistenz. Dieser Cache-Speicher ist für Entwicklungs- und Testumgebungen oder kleine, einfache Anwendungen geeignet.

Disk Store

Der Disk Store verwendet Dateien auf der Festplatte als Cache-Speicher. Er ist die langsamste Cache-Option in Rails, verfügt aber über eine große Kapazität und Persistenz. Disk Store eignet sich für Anwendungen, die große Datenmengen zwischenspeichern müssen und nicht die maximale Leistung benötigen.

Redis

Der Redis-Speicher verwendet eine Redis-Instanz für den Cache-Speicher. Redis ist ein In-Memory-Datenspeicher, der verschiedene Datentypen unterstützt. Er ist zwar schnell und flexibel, erfordert aber einen eigenen Server und eine eigene Konfiguration. Er ist für Anwendungen geeignet, die komplexe oder dynamische Daten zwischenspeichern müssen, die sich häufig ändern. Redis ist die ideale Wahl, wenn du Rails-Anwendungen in der Cloud laufen lässt, denn einige Hosting-Anbieter, darunter Kinsta, bieten Redis als persistenten Objekt-Cache an.

Memcached

Der Memcached-Store verwendet eine Memcached-Instanz für die Cache-Speicherung. Memcached ist ein In-Memory-Schlüsselwertspeicher, der einfache Datentypen und Funktionen unterstützt. Er ist schnell und skalierbar, aber wie Redis erfordert er einen eigenen Server und eine eigene Konfiguration. Dieser Speicher ist für Anwendungen geeignet, die einfache oder statische Daten zwischenspeichern müssen, die häufig aktualisiert werden.

Du kannst deinen Cache-Speicher in deinen Rails-Umgebungsdateien (zum Beispiel config/environments/development.rb) mit der Option config.cache_store konfigurieren. Hier erfährst du, wie du die eingebauten Caching-Methoden von Rails verwendest:

# Use memory store
config.cache_store = :memory_store
# Use disk store
config.cache_store = :file_store, "tmp/cache"
# Use Redis
config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
# Use Memcached
config.cache_store = :mem_cache_store, "localhost"

Du solltest nur einen config.cache_store Aufruf pro Umgebungsdatei haben. Wenn du mehr als einen hast, verwendet der Cache-Speicher nur den letzten.

Jeder Cache-Speicher hat einzigartige Vor- und Nachteile, die von den Bedürfnissen und Vorlieben deiner Anwendung abhängen. Wähle den Speicher, der am besten zu deinem Anwendungsfall und deiner Erfahrung passt.

Best Practices für Ruby on Rails Caching

Der Einsatz von Caching in deiner Rails-Anwendung kann ihre Leistung und Skalierbarkeit erheblich steigern, vor allem wenn du die folgenden Best Practices umsetzt:

  • Cache selektiv: Cache nur Daten, auf die häufig zugegriffen wird, die teuer zu generieren sind oder die selten aktualisiert werden. Vermeide übermäßiges Caching, um übermäßigen Speicherverbrauch, das Risiko veralteter Daten und Leistungseinbußen zu vermeiden.
  • Cache-Einträge ablaufen lassen: Verhindere veraltete Daten, indem du ungültige oder irrelevante Einträge verfallen lässt. Verwende Zeitstempel, Ablaufoptionen oder die manuelle Ungültigkeitserklärung.
  • Optimiere die Cache-Leistung: Wähle den Cache-Speicher, der zu den Anforderungen deiner Anwendung passt, und passe seine Parameter – wie Größe, Komprimierung oder Serialisierung – an, um eine optimale Leistung zu erzielen.
  • Überwache und teste die Auswirkungen des Caches: Beurteile das Cache-Verhalten – wie Hit-Rate, Miss-Rate und Latenz – und bewerte ihre jeweiligen Auswirkungen auf die Leistung (Antwortzeit, Durchsatz, Ressourcenverbrauch). Verwende Tools wie New Relic, Rails-Logs, ActiveSupport-Benachrichtigungen oder den Rack Mini Profiler.

Zusammenfassung

Das Caching von Ruby on Rails verbessert die Leistung und Skalierbarkeit von Anwendungen, indem es häufig genutzte Daten oder Inhalte effizient speichert und wiederverwendet. Mit einem tieferen Verständnis der Caching-Techniken bist du besser gerüstet, um deinen Nutzern schnellere Rails-Anwendungen zu liefern.

Wenn du deine optimierte Rails-Anwendung bereitstellen willst, kannst du die Anwenndungs-Hosting-Plattform von Kinsta wenden. Mit einem Hobby-Tier-Konto kannst du kostenlos loslegen und die Plattform mit diesem Ruby on Rails-Schnellstart-Beispiel erkunden.

Steve Bonisteel Kinsta

Steve Bonisteel is Technical Editor bij Kinsta. Hij begon zijn schrijverscarrière als verslaggever en achtervolgde ambulances en brandweerwagens. Sinds eind jaren negentig schrijft hij over internetgerelateerde technologie.