Dieser Artikel beschreibt, wie Sie die Leistung Ihrer Symfony-App durch die Einführung des Doctrine-Caches der zweiten Ebene verbessern können. Alle Schlussfolgerungen wurden nach Tests mit Redis 3.0 und MySQL 5.7 gezogen.
Cache der ersten Ebene
Mit der neuen Symfony-Anwendung und Doctrine, die standardmäßig aktiviert ist, wird ein sehr einfacher Cache in Symfony zur Verfügung gestellt, der verhindert, dass die Datenbank mehrfach abgefragt werden muss, indem nach einer Entität anhand ihres Primärschlüssels gesucht wird. Er ist sehr nützlich, weil er sofort funktioniert und nicht deaktiviert werden kann, d.h. er erfordert keine zusätzliche Arbeit.
Die traditionelle Herangehensweise
Beim traditionellen Ansatz mit einem Cache-System wie Memcached, Redis oder Varnish müssen wir die Daten direkt im Cache speichern und aus ihm lesen. Wir haben die volle Kontrolle über den Cache, was uns die Möglichkeit gibt, ihn zu verbessern und anzupassen, aber die volle Kontrolle kann auch ein Feld für Fehler erzeugen.
Was ist ein Second Level Cache?
Mindestens seit Januar 2014 ist ein Cache der zweiten Ebene als "experimentell"gekennzeichnet. Seitdem wurde jedoch daran gearbeitet (der letzte Stable-Tag des ORM von Doctrine unterscheidet sich in Bezug auf den Cache der zweiten Ebene deutlich vom Master).
Zwischengespeicherte Daten werden in drei Typen unterteilt:
- Entitätsdaten - so einfach wie es nur geht - Entitätsdaten, die anhand ihrer Kennung zwischengespeichert werden. Es funktioniert sehr gut und ist eigentlich die einzige Art von Daten, die in einer Produktionsumgebung sicher verwendet werden kann.
- Sammlungsdaten - alle Entitäten werden durch einen Fremdschlüsselbezeichner zwischengespeichert (z.B. für die Abfrage
SELECT * FROM city WHERE country_id = 3). Dies führt zu zwei Problemen, die beide leicht als Problemlöser angesehen werden können.
Wir können zwei Strategien wählen, um sie zu entkräften:
- Leeren Sie die gesamte Cache-Sammlung. Dies ist relativ einfach, nachdem wir die Entität aktualisiert und die Klassenmetadaten verwendet haben. Wenn wir das getan haben, erhalten wir Assoziationszuordnungen und verwenden für jede dieser Zuordnungen die Werte
targetEntityundinversedBy. Von nun an können wir die gesamte Cache-Sammlung auslagern. In vielen Fällen ist dies jedoch eine undurchführbare und inakzeptable Option, da dadurch ein großer Teil des Caches auf einmal ungültig wird. - Leeren Sie die Cache-Sammlung durch Filtern unter Verwendung der aktualisierten Entität. Diese Strategie kann die Verknüpfung von Entitäten auf die ursprüngliche Entität beschränken, aber wenn die Sammlungen groß sind, kann dies auch die Leistung beeinträchtigen.
Außerdem speichert die Redis-Caching-Sammlung eine Liste von IDs mit einem Schlüssel und jede Entität wird separat gespeichert. Wenn Sie also davon ausgehen, dass Redis 3.0 zehnmal schneller ist als MySQL 5.7, wird eine Sammlung, die mehr als 10 Entitäten enthält, die Leistung verringern.
- Abfragedaten, die einen Cache von Abfragen enthalten, die von benutzerdefinierten Repositories ausgeführt wurden. Neben ähnlichen Problemen wie bei der gesamten Cache-Sammlung stoßen wir auf ein neues Problem, nämlich dass der Cache der zweiten Ebene keine skalaren Ergebnisse unterstützt. Das bedeutet, dass alle Abfragen, die sich auf Gruppierungs- und Aggregatfunktionen beziehen, nicht zwischengespeichert werden können, was eine echte Enttäuschung ist, da es sich dabei in der Regel um die umfangreichsten Abfragen handelt.
Ein Cache der zweiten Ebene führt Caching-Regionen ein, er speichert keine Entitätsdaten - er speichert nur seine eigenen Bezeichner und Werte. Jeder Datentyp kann frei einer festen Region zugewiesen werden - wenn wir keine Region definieren, wird sie für uns "hinter den Kulissen" erstellt. Wir können eine solche Region definieren und alle Datenbankabfragen mit einer festen Entität verknüpfen, die mit dieser Region verbunden ist, so dass das Ungültigmachen des Caches auf diese Region beschränkt wird - allerdings erfordert dies eine manuelle Konfiguration. Jede Region kann ihre eigene Lebensdauer haben, die wir definieren und dann vergessen.
Caching-Modi und Persister
Der Cache der zweiten Ebene verfügt über drei Caching-Modi:
READ_ONLY- Standard; die schnellste und einfachste, aber auch nicht in der Lage, Aktualisierungen und Sperren durchzuführenNONSTRICT_READ_WRITE- Aktualisierungen durchführen können, aber keine SperrenREAD_WRITE- die langsamste, die in der Lage ist, Aktualisierungen und Sperren durchzuführen und die einzige, die wir bei der Implementierung einer benutzerdefinierten Region verwenden können
Wie Sie es konfigurieren
Lassen Sie uns mit der Installation der erforderlichen Bibliotheken beginnen:
composer require doctrine/doctrine-bundle doctrine/orm snc/redis-bundle predis/predis
Wir müssen den Cache der zweiten Ebene aktivieren:
# app/config.yml
Doktrin:
orm:
second_level_cache:
aktiviert: true
Wir können hier auch eine Region definieren:
# app/config.yml
Doktrin:
orm:
second_level_cache:
Regionen:
Entität_die_selten_wechselt:
Lebensdauer: 86400 # 1 Tag
cache_driver:
Typ: Dienstleistung
id: snc_second_level_cache
Wir müssen dafür einen Cache-Anbieter als Dienst erstellen - z.B. Predis:
# app/services.yml
Dienstleistungen:
snc_second_level_cache:
Klasse: '%snc_redis.doctrine_cache_predis.class%'
Argumente:
- '@snc_redis.default'
Jetzt müssen Sie nur noch die Entitäten und Sammlungen konfigurieren:
# src/AppBundle/Resources/config/doctrine/MyEntity.orm.yml
AppBundleEntityMyEntity:
Cache:
Verwendung: NONSTRICT_READ_WRITE # - dies scheint die sicherste Wahl zu sein
region: entity_that_rarely_changes # - dies ist optional, wenn wir keinen Regionsnamen definieren, wird einer aus einem Klassennamen generiert
# ...
oneToMany:
otherEntities:
Cache:
Verwendung: NONSTRICT_READ_WRITE
Und Rückfragen:
class MyEntityRepository erweitert EntityRepository
{
public function getEvenOnes()
{
return $this->createQueryBuilder('MyEntity')
->andWhere("mod(MyEntity.id,2) = 0")
->getQuery()
->setCacheable(true)
->setCacheMode(ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE)
->setCacheRegion('entity_that_rarely_changes')
->getResult();
}
}
Schlussfolgerungen:
- Die Einrichtung eines Second Level Cache ist sehr einfach und die unterstützenden Bibliotheken sind sehr stabil.
- Wenn unsere Anwendung häufig Entitäten anhand ihres Bezeichners abruft, können wir mit dem Cache der zweiten Ebene sehr viel anfangen.
- Das Zwischenspeichern von Sammlungen oder Abfragen kann schnell sehr ineffizient werden - wir bräuchten eine sehr spezielle Anwendung, um davon zu profitieren.
- Der Cache der zweiten Ebene scheint ein enormes Potenzial für die Entwicklungsgeschwindigkeit zu haben, was die Frage aufwirft, ob er in Symfony-Projekten von Nutzen sein wird.
Unsere Ideen
Weitere Blogs
Contact




