Daten für strikte Konsistenz strukturieren

Hinweis: Entwicklern von neuen Anwendungen wird dringend empfohlen, die NDB-Clientbibliothek zu verwenden. Diese bietet im Vergleich zur vorliegenden Clientbibliothek verschiedene Vorteile, z. B. das automatische Caching von Entitäten über die Memcache API. Wenn Sie derzeit die ältere DB-Clientbibliothek verwenden, finden Sie weitere Informationen im Leitfaden zur Migration von DB- zu NDB-Clientbibliotheken.

Datastore bietet hohe Verfügbarkeit, Skalierbarkeit und Langlebigkeit, da die Daten auf viele Rechner verteilt werden und eine synchrone Replikation für einen großen geografischen Bereich verwendet wird. Bei diesem Design müssen jedoch Einschränkungen in Kauf genommen werden. Der Schreibdurchsatz für eine einzelne Entitätengruppe ist auf etwa einen Commit-Vorgang pro Sekunde begrenzt. Weitere Einschränkungen bestehen bei Abfragen oder Transaktionen, die mehrere Entitätengruppen umfassen. Auf dieser Seite erfahren Sie mehr zu den einzelnen Einschränkungen. Außerdem werden Best Practices zur Strukturierung der Daten im Hinblick auf strikte Konsistenz bei gleichzeitiger Einhaltung der Anforderungen an den Schreibdurchsatz der Anwendung vorgestellt.

Strikt konsistente Lesevorgänge liefern immer aktuelle Daten. Werden sie innerhalb derselben Transaktion ausgeführt, sieht es so aus, als stammten sie von einem einzigen, konsistenten Snapshot. Abfragen müssen allerdings einen Ancestor-Filter angeben, um strikte Konsistenz zu bieten oder Bestandteil einer Transaktion zu sein. Transaktionen können höchstens 25 Entitätengruppen umfassen. Für Lesezugriffe mit Eventual Consistency gelten diese Einschränkungen nicht. Sie eignen sich für viele Anwendungsfälle. Mit Lesevorgängen mit Eventual Consistency können Sie die Daten auf eine größere Anzahl von Entitätengruppen verteilen. Durch parallele Commits für die verschiedenen Entitätengruppen erhöhen Sie den Schreibdurchsatz. Sie müssen jedoch die Eigenschaften von Lesevorgängen mit Eventual Consistency verstehen, um zu ermitteln, ob sie für Ihre Anwendung geeignet sind:

  • Die Ergebnisse dieser Lesevorgänge spiegeln eventuell nicht die letzten Transaktionen wider. Dies kann geschehen, weil diese Lesezugriffe nicht gewährleisten, dass die Replikate, mit denen sie ausgeführt werden, auf dem neuesten Stand sind. Stattdessen verwenden sie die Daten, die im Replikat zum Zeitpunkt der Abfrageausführung verfügbar sind. Die Replikationslatenz liegt fast immer bei wenigen Sekunden.
  • Es kann den Anschein haben, dass eine für mehrere Entitäten mit Commit durchgeführte Transaktion nur auf einen Teil der Entitäten angewendet wurde. Eine Transaktion wurde niemals scheinbar teilweise innerhalb einer einzelnen Entität angewendet.
  • Die Abfrageergebnisse können Entitäten enthalten, die gemäß den Filterkriterien nicht hätten einbezogen werden sollen, und Entitäten ausschließen, die hätten einbezogen werden sollen. Dies liegt daran, dass Indexe unter Umständen in einer anderen Version als die Entität gelesen werden.

Zum besseren Verständnis, wie Daten im Hinblick auf strikte Konsistenz strukturiert werden sollen, vergleichen Sie zwei verschiedene Lösungen für eine einfache Gästebuchanwendung. In der ersten Lösung wird für jede erstellte Entität eine neue Stammentität erstellt:

import webapp2
from google.appengine.ext import db

class Guestbook(webapp2.RequestHandler):
  def post(self):
    greeting = Greeting()
    ...

Anschließend werden die zehn letzten Begrüßungen nach dem Entitätstyp Greeting abgefragt.

import webapp2
from google.appengine.ext import db

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    greetings = db.GqlQuery("SELECT * "
                            "FROM Greeting "
                            "ORDER BY date DESC LIMIT 10")

Da Sie jedoch eine Nicht-Ancestor-Abfrage verwenden, wurde der neue Gruß zum Zeitpunkt der Ausführung der Abfrage möglicherweise noch nicht von dem Replikat erfasst, das für die Abfrage in diesem Schema verwendet wurde. Dennoch sind nahezu alle Schreibvorgänge für Nicht-Ancestor-Abfragen innerhalb von ein paar Sekunden nach einem Commit-Vorgang verfügbar. Bei vielen Anwendungen reicht im Allgemeinen eine Lösung, die die Ergebnisse einer Nicht-Ancestor-Abfrage im Kontext der eigenen Änderungen des aktuellen Nutzers bereitstellt, damit derartige Replikationslatenzen völlig akzeptabel sind.

Wenn es für Ihre Anwendung auf Strong Consistency ankommt, können Entitäten mit einem Ancestor-Pfad geschrieben werden, der dieselbe Stammentität für alle Entitäten ermittelt, die in einer einzigen Ancestor-Abfrage mit Strong Consistency gelesen werden müssen:

import webapp2
from google.appengine.ext import db

class Guestbook(webapp2.RequestHandler):
  def post(self):
    guestbook_name=self.request.get('guestbook_name')
    greeting = Greeting(parent=guestbook_key(guestbook_name))
    ...

Danach können Sie eine Ancestor-Abfrage mit Strong Consistency innerhalb der Entitätengruppe ausführen, die von der gemeinsamen Stammentität identifiziert wird:

import webapp2
from google.appengine.ext import db

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    guestbook_name=self.request.get('guestbook_name')

    greetings = db.GqlQuery("SELECT * "
                            "FROM Greeting "
                            "WHERE ANCESTOR IS :1 "
                            "ORDER BY date DESC LIMIT 10",
                            guestbook_key(guestbook_name))

Diese Lösung erzielt strikte Konsistenz, indem in eine einzige Entitätengruppe pro Gästebuch geschrieben wird. Änderungen am Gästebuch werden jedoch auch auf maximal einen Schreibvorgang pro Sekunde beschränkt (den unterstützten Grenzwert für Entitätengruppen). Wenn für Ihre Anwendung eine höhere Schreibnutzung wahrscheinlich ist, müssen Sie möglicherweise andere Mittel verwenden: Sie können z. B. die neuesten Posts in einem Memcache mit einem Ablauf speichern und eine Mischung aus den neuesten Posts aus dem Memcache und Datastore anzeigen oder Sie speichern sie in einem Cookie, setzen einen Status in der URL oder etwas anderes. Das Ziel besteht darin, eine Caching-Lösung zu finden, die dem aktuellen Nutzer die Daten in dem Zeitraum bereitstellt, in dem er Beiträge an Ihre Anwendung postet. Beachten Sie, dass Sie immer die zuletzt geschriebenen Daten sehen, wenn Sie einen get-Vorgang, eine Ancestor-Abfrage oder einen Vorgang innerhalb einer Transaktion ausführen.