App Engine Datastore API für gebündelte Legacy-Dienste

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.

In diesem Artikel werden das Datenmodell für in Cloud Datastore gespeicherte Objekte, die Strukturierung von Abfragen mithilfe der API und die Verarbeitung von Transaktionen erläutert.

Entities

Objekte in Datastore werden als Entitäten bezeichnet. Eine Entität hat ein oder mehrere benannte Attribute, von denen jedes einen oder mehrere Werte haben kann. Die Property-Werte unterstützen eine Vielzahl von Datentypen, darunter Ganzzahlen, Gleitkommazahlen, Strings, Datumsangaben und Binärdaten. Mit einer Abfrage für eine Property mit mehreren Werten wird getestet, ob einer der Werte die Abfragekriterien erfüllt. Diese Attribute eignen sich daher zum Testen von Mitgliedschaften.

Typen, Schlüssel und Kennungen

Jede Datastore-Entität ist von einer bestimmten Art. Mit dieser kann die Entität für Abfragen kategorisiert werden. In einer Personalanwendung kann beispielsweise jeder Mitarbeiter eines Unternehmens mit einer Entität der Art Employee dargestellt werden. Darüber hinaus wird jede Entität durch einen eigenen Schlüssel eindeutig identifiziert. Der Schlüssel besteht aus den folgenden Komponenten:

  • Dem Typ der Entität
  • Einer Kennung, die Folgendes sein kann:
    • ein Schlüsselnamen-String
    • eine ganzzahlige ID
  • Optionaler Ancestor-Pfad zur Entität innerhalb der Datastore-Hierarchie

Die Kennung wird beim Erstellen der Entität zugewiesen. Weil die Kennung Teil des Schlüssels der Entität ist, wird sie permanent mit der Entität verknüpft und kann nicht geändert werden. Es gibt zwei Möglichkeiten für die Zuweisung:

  • Die Anwendung kann ihren eigenen Schlüsselnamen-String für die Entität angeben.
  • Datastore kann der Entität automatisch eine ganzzahlige numerische ID zuweisen.

Ancestor-Pfade

Entitäten in Cloud Datastore sind hierarchisch organisiert, ähnlich der Verzeichnisstruktur eines Dateisystems. Wenn Sie eine Entität erstellen, können Sie optional eine weitere Entität als übergeordnetes Element angeben. Die neue Entität ist dann ein untergeordnetes Element der übergeordneten Entität. Im Gegensatz zu einem Dateisystem muss die übergeordnete Entität nicht tatsächlich vorhanden sein. Eine Entität ohne übergeordnetes Element wird als Stammentität bezeichnet. Die Verknüpfung zwischen einer Entität und ihrer übergeordneten Entität ist dauerhaft und kann nicht geändert werden, nachdem die Entität erstellt wurde. Cloud Datastore weist zwei Entitäten mit derselben übergeordneten Entität oder zwei Stammentitäten (Entitäten ohne übergeordnete Entität) niemals dieselbe numerische ID zu.

Alle übergeordneten Elemente einer Entität werden als ihre Ancestors bezeichnet und alle untergeordneten Entitäten sind ihre Nachfolger. Eine Stammentität und alle ihre Nachfolger gehören zu derselben Entitätengruppe. Die Abfolge der Entitäten, von einer Stammentität über die untergeordneten Elemente bis zu einer bestimmten Entität, bildet den Ancestor-Pfad. Der vollständige Schlüssel, der die Entität identifiziert, besteht aus einer Abfolge von Art/Kennungs-Paaren, die den Ancestor-Pfad angeben und mit dem Paar der Entität selbst enden:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Bei einer Stammentität ist der Ancestor-Pfad leer und der Schlüssel besteht ausschließlich aus der eigenen Art und der eigenen Kennung der Entität:

[Person:GreatGrandpa]

Dieses Konzept wird anhand des folgenden Diagramms veranschaulicht:

Zeigt die Beziehung der Stammentität zu den untergeordneten Entitäten in der Entitätengruppe an

Abfragen und Indexe

Neben dem direkten Abrufen von Entitäten aus Datastore über die entsprechenden Schlüssel kann eine Anwendung die Entitäten auch über eine Abfrage nach den Werten ihrer Attribute abrufen. Die Abfrage wird für Entitäten einer bestimmten Art ausgeführt. Sie kann Filter für die Attributwerte, Schlüssel und Ancestors der Entitäten angeben sowie null oder mehr Entitäten als Ergebnisse zurückgeben. Außerdem können mit einer Abfrage Sortierfolgen festgelegt werden, um die Ergebnisse nach ihren Attributwerten zu sortieren. Die Ergebnisse umfassen alle Entitäten mit mindestens einem Wert (ggf. auch null) für jedes Attribut, das in den Filtern und Sortierfolgen angegeben wird, und deren Attributwerte alle angegebenen Filterkriterien erfüllen. Die Abfrage kann ganze Entitäten, projizierte Entitäten oder einfach Schlüssel von Entitäten zurückgeben.

Eine typische Abfrage umfasst

  • Eine Entitätsart, auf die die Abfrage angewendet wird
  • Null oder mehr Filter, die auf Attributwerten, Schlüsseln und Ancestors der Entitäten basieren
  • Null oder mehr Sortierfolgen zum Anordnen der Ergebnisse
Bei der Ausführung ruft die Abfrage alle Entitäten der angegebenen Art ab, die allen in der angegebenen Reihenfolge sortierten Filtern entsprechen. Abfragen werden schreibgeschützt ausgeführt.

Hinweis: Um Arbeitsspeicher zu sparen und die Leistung zu verbessern, sollte eine Abfrage wenn möglich einen Grenzwert für die Anzahl von zurückgegebenen Ergebnissen angeben.

Eine Abfrage kann auch einen Ancestor-Filter enthalten, der die Ergebnisse auf die Entitätengruppe beschränkt, die aus Nachfolgern eines angegebenen Ancestors besteht. Eine solche Abfrage wird als Ancestor-Abfrage bezeichnet. Standardmäßig liefern Ancestor-Abfragen Ergebnisse mit strikter Konsistenz, die verlässlich die neuesten Änderungen an den Daten widerspiegeln. Nicht-Ancestor-Abfragen können sich im Gegensatz dazu auf den gesamten Datastore und nicht nur auf eine einzelne Entitätengruppe beziehen. Sie bieten jedoch nur Eventual Consistency und können veraltete Ergebnisse liefern. Wenn Strong Consistency für Ihre Anwendung wichtig ist, sollten Sie dies beim Strukturieren der Daten gegebenenfalls berücksichtigen und verwandte Entitäten in derselben Entitätengruppe platzieren. Sie können dadurch mit einer Ancestor-Abfrage anstatt einer Nicht-Ancestor-Abfrage abgerufen werden. Weitere Informationen finden Sie unter Daten für Strong Consistency strukturieren.

App Engine gibt für jede Property einer Entität einen einfachen Index vor. Eine App Engine-Anwendung kann weitere benutzerdefinierte Indexe in der Indexkonfigurationsdatei index.yaml definieren. Der Entwicklungsserver fügt dieser Datei automatisch Vorschläge hinzu, wenn er Abfragen ermittelt, die mit den vorhandenen Indexen nicht ausgeführt werden können. Sie können Indexe vor dem Hochladen der Anwendung manuell optimieren, indem Sie die Datei bearbeiten.

Hinweis: Der indexbasierte Abfragemechanismus unterstützt ein breites Spektrum an Abfragen und ist für die meisten Anwendungen geeignet. Allerdings werden einige Arten von Abfragen, die in anderen Datenbanktechnologien üblich sind, vom Abfragemodul in Datastore nicht unterstützt, insbesondere Join- und Aggregatabfragen. Weitere Informationen zu Einschränkungen bei Datastore-Abfragen finden Sie auf der Seite Datastore-Abfragen.

Transaktionen

Jeder Versuch, eine Entität zu erstellen, zu aktualisieren oder zu löschen, findet im Kontext einer Transaktion statt. Eine einzelne Transaktion kann eine beliebige Anzahl solcher Vorgänge umfassen. Um die Konsistenz der Daten zu wahren, sorgt die Transaktion dafür, dass entweder alle enthaltenen Vorgänge auf Datastore als Einheit oder überhaupt nicht angewendet werden, sofern einer der Vorgänge fehlgeschlagen ist.

Sie können innerhalb einer einzelnen Transaktion mehrere Aktionen für eine Entität ausführen. Wenn Sie beispielsweise den Wert eines Zählerfelds in einem Objekt erhöhen möchten, lesen Sie den Wert des Zählers ab, berechnen den neuen Wert und speichern diesen. Ohne eine Transaktion kann es passieren, dass ein anderer Prozess den Zähler zwischen dem Zeitpunkt, zu dem Sie den Wert ablesen, und dem Zeitpunkt, zu dem Sie ihn aktualisieren, erhöht. Der aktualisierte Wert wird in diesem Fall von Ihrer Anwendung überschrieben. Wenn das Lesen, Berechnen und Schreiben in einer einzigen Transaktion erfolgt, kann die Inkrementierung von keinem anderen Prozess gestört werden.

Transaktionen und Entitätengruppen

Innerhalb einer Transaktion sind nur Ancestor-Abfragen zulässig. Jede Transaktionsabfrage muss somit auf eine einzelne Entitätengruppe beschränkt sein. Die Transaktion selbst lässt sich auf mehrere Entitäten anwenden. Diese können entweder zu einer einzelnen Entitätengruppe oder, bei einer gruppenübergreifenden Transaktion, zu maximal 25 verschiedenen Entitätengruppen gehören.

Datastore verwendet zur Verwaltung von Transaktionen Optimistic Concurrency. Wenn durch zwei oder mehr Transaktionen gleichzeitig versucht wird, eine Entitätengruppe zu ändern (indem vorhandene Entitäten aktualisiert oder neue Entitäten erstellt werden), ist nur die erste mit Commit gespeicherte Transaktion erfolgreich. Bei allen anderen schlägt der Commit fehl. Diese anderen Transaktionen können dann mit den aktualisierten Daten wiederholt werden. Beachten Sie, dass dadurch die Anzahl gleichzeitiger Schreibvorgänge begrenzt wird, die Sie für eine Entität in einer bestimmten Entitätengruppe ausführen können.

Gruppenübergreifende Transaktionen

Eine Transaktion für Entitäten, die zu verschiedenen Entitätengruppen gehören, wird als gruppenübergreifende (XG-)Transaktion bezeichnet. Die Transaktion kann auf maximal 25 Entitätengruppen angewendet werden und ist erfolgreich, solange sich keine weitere gleichzeitige Transaktion auf eine der relevanten Entitätengruppen bezieht. So erhalten Sie mehr Flexibilität bei der Organisation Ihrer Daten: Sie sind nicht gezwungen, verschiedenartige Daten unter demselben Ancestor zu gruppieren, nur um atomare Schreibvorgänge für sie durchzuführen.

In XG-Transaktionen ist es ebenso wenig wie in Einzelgruppentransaktionen möglich, Nicht-Ancestor-Abfragen auszuführen. Sie können jedoch Ancestor-Abfragen für separate Entitätengruppen durchführen. Bei nicht-transaktionalen Abfragen (Nicht-Ancestor-Abfragen) können alle, einige oder gar keine Ergebnisse einer zuvor mit Commit durchgeführten Transaktion angezeigt werden. Weitere Informationen zu diesem Thema finden Sie unter Datastore-Schreibvorgänge und Datentransparenz. Bei solchen nicht-transaktionalen Abfragen ist es jedoch wahrscheinlicher, dass die Ergebnisse einer teilweise mit Commit ausgeführten XG-Transaktion angezeigt werden, als die einer teilweise mit Commit ausgeführten Einzelgruppentransaktion.

Die Leistung und Kosten einer XG-Transaktion, die nur eine einzige Entitätengruppe betrifft, sind genauso hoch wie bei einer Nicht-XG-Einzelgruppentransaktion. Bei einer XG-Transaktion, die mehrere Entitätengruppen betrifft, sind die Betriebskosten genauso hoch wie bei einer Nicht-XG-Transaktion, die Latenz kann jedoch höher sein.

Datastore-Lesevorgänge und Datensichtbarkeit

Daten werden in zwei Phasen in Datastore geschrieben:

  1. In der Commit-Phase werden die Entitätsdaten in den Transaktions-Logs einer Mehrzahl von Replikaten erfasst. Alle Replikate, in denen sie nicht erfasst wurden, werden markiert, damit erkennbar ist, dass ihre Logs nicht aktuell sind.
  2. Die Apply-Phase findet in jedem Replikat unabhängig statt und besteht aus zwei parallel ausgeführten Aktionen:
    • Die Entitätsdaten werden in dieses Replikat geschrieben.
    • Die Indexzeilen für die Entität werden in dieses Replikat geschrieben. Hinweis: Dies kann länger dauern als das Schreiben der Daten selbst.

Der Schreibvorgang wird unmittelbar nach der Commit-Phase wieder aufgenommen. Die Apply-Phase findet dann asynchron statt, möglicherweise zu verschiedenen Zeiten in jedem Replikat und möglicherweise mit Verzögerungen von ein paar hundert Millisekunden oder mehr nach Abschluss der Commit-Phase. Tritt während der Commit-Phase ein Fehler auf, werden automatische Wiederholungen durchgeführt. Wenn Fehler jedoch weiterhin auftreten, gibt Datastore eine Fehlermeldung zurück, die von Ihrer Anwendung als Ausnahme empfangen wird. Wenn die Commit-Phase erfolgreich ist, die Apply-Phase jedoch bei einem bestimmten Replikat fehlschlägt, wird in diesem Replikat ein Rollforward für den Apply-Vorgang bis zum Abschluss ausgeführt, wenn eine der beiden folgenden Situationen eintritt:

  • Nicht abgeschlossene "Commit"-Jobs werden im Rahmen von regelmäßigen Datastore-Prüfungen identifiziert und angewendet.
  • Bestimmte Vorgänge (get, put, delete und Ancestor-Abfragen), die die betroffene Entitätengruppe nutzen, bewirken die Übernahme von mit Commit bestätigten, aber noch nicht angewendeten Änderungen in dem Replikat, in dem sie ausgeführt werden. Erst dann wird mit dem nächsten Vorgang fortgefahren.

Dieses Schreibverhalten kann verschiedene Auswirkungen darauf haben, wie und wann Daten für Ihre Anwendung bei verschiedenen Teilen der Commit- und Apply-Phasen sichtbar sind:

  • Wenn ein Schreibvorgang einen Zeitüberschreitungsfehler meldet, kann (ohne zu versuchen, die Daten zu lesen) nicht ermittelt werden, ob der Vorgang erfolgreich war oder fehlgeschlagen ist.
  • Da Datastore ausstehende Änderungen abruft und Ancestor-Abfragen die Änderungen auf das Replikat anwenden, auf dem sie ausgeführt werden, erhalten diese Vorgänge immer eine konsistente Übersicht aller vorherigen erfolgreichen Transaktionen. Das bedeutet, dass bei einem get-Vorgang (Suchen einer aktualisierten Entität anhand ihres Schlüssels) garantiert die neueste Version dieser Entität gefunden wird.
  • Nicht-Ancestor-Abfragen können veraltete Ergebnisse liefern, da sie möglicherweise auf einem Replikat ausgeführt werden, auf dem die neuesten Transaktionen noch nicht angewendet wurden. Das kann auch beim Ausführen eines Vorgangs auftreten, bei dem ausstehende Transaktionen garantiert angewendet werden, da die Abfrage möglicherweise auf einem anderen Replikat als der vorherige Vorgang ausgeführt wird.
  • Das Timing von gleichzeitigen Änderungen kann sich auf die Ergebnisse von Nicht-Ancestor-Abfragen auswirken. Wenn eine Entität anfangs eine Abfrage erfüllt, dies aber aufgrund einer Änderung später nicht mehr der Fall ist, kann die Entität dennoch in den Ergebnissen der Abfrage enthalten sein, wenn die Änderungen noch nicht auf die Indexe des Replikats angewendet wurden, auf dem die Abfrage ausgeführt wurde.

Datastore-Statistiken

Datastore erfasst Statistiken zu den gespeicherten Daten einer Anwendung, wie die Anzahl der Entitäten pro Dateityp oder wie viel Speicherplatz für Attributwerte eines bestimmten Typs verwendet wird. Sie können diese Statistiken auf der Seite Datastore-Dashboard der Google Cloud Console einsehen. Sie können mit der Cloud Datastore API auch programmgesteuert innerhalb der Anwendung auf diese Werte zugreifen, indem Sie Abfragen nach bestimmten Entitäten ausführen. Weitere Informationen finden Sie unter Datastore-Statistiken in Python 2.

Datastore-Beispiel für Python 2

In der Python API wird anhand eines Modells ein Entitätenart einschließlich der Typen und Konfiguration der zugehörigen Attribute beschrieben. Eine Anwendung definiert ein Modell mithilfe einer Python-Klasse. Zum Beschreiben der Properties werden Klassenattribute verwendet. Der Entitätstyp erhält den Namen der Klasse. Entitäten des angegebenen Typs werden durch Instanzen der Modellklasse dargestellt, wobei Instanzattribute die Property-Werte darstellen. Wenn Sie eine neue Entität erstellen möchten, konstruieren Sie ein Objekt der gewünschten Klasse, legen dessen Attribute fest und speichern das Objekt, beispielsweise durch Aufrufen der Methode put():

import datetime
from google.appengine.ext import db
from google.appengine.api import users

class Employee(db.Model):
  name = db.StringProperty(required=True)
  role = db.StringProperty(required=True,
                           choices=set(["executive", "manager", "producer"]))
  hire_date = db.DateProperty()
  new_hire_training_completed = db.BooleanProperty(indexed=False)
  email = db.StringProperty()

e = Employee(name="John",
             role="manager",
             email=users.get_current_user().email())
e.hire_date = datetime.datetime.now().date()
e.put()

Die Datastore API bietet zwei Schnittstellen für Abfragen: eine Abfrageobjektschnittstelle und die SQL-ähnliche Abfragesprache GQL. Eine Abfrage gibt Entitäten in Form von Instanzen der Modellklassen zurück:

training_registration_list = ["Alfred.Smith@example.com",
                              "jharrison@example.com",
                              "budnelson@example.com"]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE email IN :1",
                                training_registration_list)
for e in employees_trained:
  e.new_hire_training_completed = True
  db.put(e)