NDB-Caching

NDB verwaltet Caches für Sie. Es gibt zwei Caching-Ebenen: einen Kontextcache und ein Gateway für den standardmäßig genutzten Caching-Dienst von App Engine, Memcache. Beide Caches sind standardmäßig für alle Entitätstypen aktiviert, lassen sich jedoch für erweiterte Anforderungen konfigurieren. Darüber hinaus besitzt NDB eine Funktion zur automatischen Stapelverarbeitung. Diese versucht, Operationen zu gruppieren und dadurch die Umlaufzeiten von Servern zu minimieren.

Einführung

Caching unterstützt die meisten Anwendungsarten. NDB speichert automatisch Daten im Cache, die geschrieben oder gelesen werden (außer eine Anwendung deaktiviert Caching). Das Lesen aus dem Cache geht schneller als aus dem Datenspeicher.

Sie können das Caching-Verhalten vieler NDB-Funktionen ändern, indem Sie Argumente für Kontextoptionen weitergeben. Sie können beispielsweise key.get(use_cache=False, use_memcache=False) aufrufen, um Caching zu umgehen. Sie können auch die Standardrichtlinie für das Caching im NDB-Kontext wie unten beschrieben ändern.

Vorsicht: Wenn Sie den Inhalt des Datenspeichers über den Datenspeicher-Betrachter der Verwaltungskonsole ändern, werden die zwischengespeicherten Werte nicht aktualisiert. Daher ist der Cache möglicherweise inkonsistent. Für den Kontextcache ist das in der Regel kein Problem. Für Memcache empfehlen wir, den Cache über die Verwaltungskonsole zu leeren.

Kontextobjekte

Die Cache-Verwaltung nutzt eine Klasse namens Context: Jeder Thread und jede Transaktion werden in einem neuen Kontext ausgeführt. Da jeder eingehende HTTP-Request einen neuen Thread startet, wird jeder Request auch in einem neuen Kontext ausgeführt. Über die Funktion ndb.get_context() können Sie den aktuellen Kontext aufrufen.

Achtung: Die gemeinsame Nutzung von Context-Objekten durch mehrere Threads oder Requests ist kein sinnvolles Vorgehen. Speichern Sie Kontext nicht als globale Variablen! Stattdessen sollten Sie ihn in einer lokalen oder für den Thread lokalen Variable speichern.

Für Kontextobjekte gibt es Methoden, mit denen sie die Caching-Richtlinien festlegen sowie den Cache anderweitig bearbeiten können.

Der Kontextcache

Der Kontextcache bleibt nur für die Dauer eines einzelnen Threads bestehen. Dadurch erhält jeder eingehende HTTP-Request neuen Kontextcache. Dieser ist nur für den Code sichtbar, der diesen Request verarbeitet. Wenn die Anwendung während der Verarbeitung eines Requests zusätzliche Threads generiert, wird für diese Threads auch neuer, separater Kontextcache verwendet.

Der Kontextcache ist schnell; er befindet sich im Arbeitsspeicher. Schreibt eine NDB-Funktion in den Datenspeicher, wird auch in den Kontextcache geschrieben. Beim Auslesen einer Entität prüft die NDB-Funktion zuerst den Kontextcache. Wird die Entität dort gefunden, erfolgt keine Interaktion mit dem Datenspeicher.

Beim Abfragen des Datenspeichers durch eine NDB-Funktion wird die Ergebnisliste aus dem Datenspeicher abgerufen. Befindet sich jedoch ein einzelnes Ergebnis im Kontextcache, wird es anstelle des Werts der Datenspeicherabfrage verwendet. Abfrageergebnisse werden in den Kontextcache zurückgeschrieben, wenn die Caching-Richtlinie das vorsieht (aber niemals in Memcache).

Werden Abfragen mit langer Laufzeit im Hintergrund ausgeführt, kann der Kontextcache große Speichermengen verbrauchen. Dies liegt daran, dass der Cache eine Kopie aller Entitäten speichert, die im aktuellen Kontext abgerufen oder gespeichert werden. Damit es bei lange laufenden Aufgaben nicht zu Ausnahmefehlern aufgrund des Speichers kommt, können Sie den Cache deaktivieren oder eine Richtlinie festlegen, die Entitäten mit dem größten Speicherverbrauch ausschließt.

Memcache

Memcache wird von App Engine standardmäßig als Caching-Dienst verwendet. Memcache ist viel schneller als der Datenspeicher, aber langsamer als der Kontextcache (Millisekunden vs. Mikrosekunden).

Standardmäßig speichert ein nicht transaktionaler Kontext alle Entitäten im Memcache zwischen. Alle Kontexte einer Anwendung verwenden denselben Memcache-Server und arbeiten mit konsistenten zwischengespeicherten Werten.

Memcache unterstützt keine Transaktionen. Daher könnte es sein, dass ein Update, das sowohl auf den Datenspeicher als auch Memcache angewendet werden soll, nur auf einem der beiden Speicher übernommen wird. Für die Konsistenz wird in solchen Fällen (möglicherweise auf Kosten der Leistung) die aktualisierte Entität aus Memcache gelöscht und dann in den Datenspeicher geschrieben. Bei einem darauffolgenden Lesevorgang fehlt die Entität in Memcache, wird aus dem Datenspeicher abgerufen und dann in Memcache als Nebeneffekt des Lesevorgangs aktualisiert. Außerdem ignorieren NDB-Lesevorgänge innerhalb von Transaktionen den Memcache.

Beim Schreiben von Entitäten innerhalb einer Transaktion wird Memcache nicht verwendet. Während des Commit-Vorgangs der Transaktion versucht der Kontext, alle diese Entitäten aus Memcache zu löschen. Beachten Sie jedoch, dass diese Löschvorgänge möglicherweise durch bestimmte Fehler verhindert werden.

Richtlinienfunktionen

Automatisches Caching ist für die meisten Anwendungen praktisch. Bei bestimmten außergewöhnlichen Anwendungen kann es besser sein, das automatische Caching für bestimmte oder alle Entitäten zu deaktivieren. Sie können das Verhalten der Caches über Richtlinienfunktionen steuern. Es gibt eine Richtlinienfunktion für den Prozesscache, festgelegt mit

context = ndb.get_context()
context.set_cache_policy(func)

und eine weitere für Memcache, festgelegt mit

context = ndb.get_context()
context.set_memcache_policy(func)

Jede Richtlinienfunktion akzeptiert einen Schlüssel und liefert ein boolesches Ergebnis. Bei Rückgabe von False wird die durch diesen Schlüssel identifizierte Entität nicht im entsprechenden Cache gespeichert. Wenn Sie beispielsweise den Prozesscache für alle Account-Entitäten umgehen möchten, könnten Sie Folgendes schreiben:

context = ndb.get_context()
context.set_cache_policy(lambda key: key.kind() != 'Account')

(Nachfolgend zeigen wir Ihnen jedoch noch einen einfacheren Weg zum selben Ergebnis.) Zur Vereinfachung können Sie anstelle einer Funktion, die immer denselben Wert liefert, True oder False ausgeben lassen. Die Standardrichtlinien speichern alle Entitäten zwischen.

Es gibt auch eine Datenspeicherrichtlinie, die festlegt, welche Entitäten direkt in den Datenspeicher geschrieben werden:

context = ndb.get_context()
context.set_datastore_policy(func)

Das funktioniert wie bei den kontextabhängigen Cache- und Memcache-Richtlinien: Wenn die Datenspeicherrichtlinie für einen bestimmten Schlüssel False zurückgibt, wird die entsprechende Entität nicht in den Datenspeicher geschrieben. (Der Prozesscache oder Memcache kann beschrieben werden, wenn die Richtlinienfunktionen das zulassen.) Das ist ggf. bei Daten nützlich, die Entitäten ähneln und die Sie zwischenspeichern möchten, aber nicht im Datenspeicher abgelegt werden müssen. Wie bei den Cacherichtlinien können Sie anstelle einer Funktion, die immer denselben Wert liefert, True oder False ausgeben lassen.

Memcache lässt Elemente automatisch ablaufen, wenn ansonsten Speicherprobleme auftreten würden. Sie können für Memcache über eine Richtlinienfunktion die maximale Lebensdauer einer Entität im Cache festlegen.

context = ndb.get_context()
context.set_memcache_timeout_policy(func)

Diese Funktion wird mit einem Schlüsselargument aufgerufen und liefert eine ganze Zahl. Diese gibt die maximale Lebensdauer in Sekunden an. 0 oder None bedeutet unbegrenzt (solange auf dem Memcache-Server genügend Speicher frei ist). Der Einfachheit halber können Sie eine Ganzzahlkonstante anstelle einer Funktion weitergeben, die immer den gleichen Wert liefert. Weitere Informationen zu Zeitüberschreitungen finden Sie in der Memcache-Dokumentation.

Hinweis: Es gibt keine eigene Lebensdauerrichtlinie für den Kontext-Cache. Der Cache und sein Kontext haben die gleiche Lebensdauer, nämlich die eines einzelnen eingehenden HTTP-Requests. Sie können den Prozess-Cache jedoch durch folgenden Aufruf löschen:
context = ndb.get_context()
context.clear_cache()

Bei einem ganz neuen Kontext ist der Prozesscache leer.

Richtlinienfunktionen können zwar sehr flexibel eingesetzt werden, in der Praxis sind die meisten Richtlinien jedoch sehr einfach gehalten. Beispiel:

  • Kein Zwischenspeichern von Entitäten einer bestimmten Modellklasse
  • Zeitüberschreitung für Memcache bei Entitäten in dieser Modellklasse 30 Sekunden
  • Entitäten in dieser Modellklasse nicht in den Datenspeicher schreiben

Damit Sie sich die Arbeit des Schreibens und fortlaufenden Aktualisierens trivialer Richtlinienfunktionen sparen können (oder noch schlimmer: das Überschreiben der Richtlinien bei jedem Vorgang mit Kontextoptionen), beziehen die Standardrichtlinien die Modellklasse durch den an sie übergebenen Schlüssel und suchen dann in der Modellklasse nach spezifische Klassenvariablen:

Klassenvariable Typ Beschreibung
_use_cache bool Gibt an, ob Entitäten im Prozesscache gespeichert werden sollen. Überschreibt die Standardrichtlinie für den Prozesscache.
_use_memcache bool Gibt an, ob Entitäten in Memcache gespeichert werden sollen, und überschreibt die Standardrichtlinie für Memcache.
_use_datastore bool Gibt an, ob Entitäten im Datenspeicher gespeichert werden sollen, und überschreibt die Standardrichtlinie für den Datenspeicher.
_memcache_timeout int Maximale Lebensdauer von Entitäten in Memcache; überschreibt die Standardrichtlinie für die Zeitüberschreitung von Memcache

Hinweis: Hierbei handelt es sich um eine Standardfunktion für jede Richtlinie. Wenn Sie eine eigene Richtlinienfunktion festlegen, aber auch auf die Standardrichtlinie zurückgreifen möchten, rufen Sie die Funktionen für die Standardrichtlinie explizit als statische Methoden der Klasse Context auf:

  • default_cache_policy(key)
  • default_memcache_policy(key)
  • default_datastore_policy(key)
  • default_memcache_timeout_policy(key)