NDB-Transaktionen

Eine Transaktion ist ein Vorgang oder eine Reihe von Vorgängen, die garantiert atomar sind. Dies bedeutet, dass Transaktionen niemals nur teilweise angewendet werden. Es werden entweder alle oder gar keine Vorgänge der Transaktion angewendet. Transaktionen haben eine maximale Dauer von 60 Sekunden. Nach 30 Sekunden Inaktivität werden sie 10 Sekunden später beendet.

Mit der asynchronen NDB API kann eine Anwendung mehrere Transaktionen gleichzeitig verwalten, sofern diese voneinander unabhängig sind. Das synchrone API bietet eine vereinfachte API mit dem Decorator @ndb.transactional(). Die dekorierte Funktion wird im Kontext der Transaktion ausgeführt.

@ndb.transactional
def insert_if_absent(note_key, note):
    fetch = note_key.get()
    if fetch is None:
        note.put()
        return True
    return False
note_key = ndb.Key(Note, note_title, parent=parent)
note = Note(key=note_key, content=note_text)
inserted = insert_if_absent(note_key, note)

Wenn die Transaktion mit einer anderen Transaktion "kollidiert", schlägt sie fehl. NDB wiederholt solche fehlgeschlagene Transaktionen mehrmals. Die Funktion kann bei wiederholter Transaktion mehrmals aufgerufen werden. Die Anzahl der Wiederholungsversuche ist beschränkt (Standard: drei Versuche). Wenn die Transaktion weiterhin nicht ausgeführt werden kann, löst NDB TransactionFailedError aus. Sie können die Anzahl der Wiederholungen auch ändern. Dazu übergeben Sie retries=N an den Decorator transactional(). Wenn Sie für die Anzahl der Wiederholversuche 0 festlegen, wird die Transaktion nach einem Fehler nicht wiederholt. Mit dem Wert N wird die Transaktion insgesamt N+1-mal wiederholt. Beispiel:

@ndb.transactional(retries=1)
def insert_if_absent_2_retries(note_key, note):
    # do insert

In Transaktionen sind nur Ancestor-Anfragen zulässig. Standardmäßig funktioniert eine Transaktion nur mit Entitäten in derselben Entitätengruppe, d. h., mit Entitäten, deren Schlüssel denselben "Ancestor" haben.

Sie können durch Übergabe von xg=True gruppenübergreifende Transaktionen (XG) für bis zu 25 Entitätengruppen festlegen:

@ndb.transactional(xg=True)
def insert_if_absent_xg(note_key, note):
    # do insert

Gruppenübergreifende Transaktionen werden über mehrere Entitätengruppen hinweg ausgeführt. Sie verhalten sich wie Transaktionen für nur eine Gruppe, schlagen jedoch nicht fehl, wenn aufgrund des Codes Entitäten aus mehr als einer Entitätengruppe aktualisiert werden sollen.

Wenn die Funktion eine Ausnahme auslöst, wird die Transaktion sofort abgebrochen. Die Ausnahme wird daraufhin noch einmal von NDB ausgelöst, damit der aufrufende Code sie sieht. Sie können erzwingen, dass eine Transaktion im Hintergrund fehlschlägt. Dazu müssen Sie die Ausnahme ndb.Rollback auslösen. In diesem Fall gibt der Funktionsaufruf None zurück. Es gibt keinen Mechanismus, um eine Wiederholung zu erzwingen.

Sie haben möglicherweise eine Funktion, die nicht bei jeder Transaktion ausgeführt werden soll. Anstatt eine solche Funktion mit @ndb.transactional zu dekorieren, übergeben Sie sie als Callback-Funktion an ndb.transaction().

def insert_if_absent_sometimes(note_key, note):
    # do insert
inserted = ndb.transaction(lambda:
                           insert_if_absent_sometimes(note_key, note))

Zum Testen, ob Code in einer Transaktion ausgeführt wird, verwenden Sie die Funktion in_transaction().

Sie können angeben, wie sich eine "transaktionale" Funktion verhalten soll, wenn sie von Code aufgerufen wird, der sich bereits in einer Transaktion befindet. Der Decorator @ndb.non_transactional legt fest, dass eine Funktion nicht innerhalb einer Transaktion ausgeführt werden soll. Wenn sie in einer Transaktion aufgerufen wird, wird sie außerhalb der Transaktion ausgeführt. Der Decorator @ndb.transactional und die Funktion ndb.transaction verwenden ein propagation-Schlüsselwortargument. Wenn beispielsweise eine Funktion eine neue, unabhängige Transaktion starten soll, können Sie so vorgehen:

@ndb.transactional(propagation=ndb.TransactionOptions.INDEPENDENT)
def insert_if_absent_indep(note_key, note):
    # do insert

Die Verteilungstypen sind mit den anderen Kontext- und Transaktionsoptionen aufgelistet.

Das Transaktionsverhalten und das Caching-Verhalten der NDB können zusammen verwirrend erscheinen, wenn Sie nicht wissen, was passiert. Wenn Sie eine Entität in einer Transaktion ändern, die Transaktion jedoch noch nicht per Commit übergeben haben, enthält der Kontextcache der NDB den geänderten Wert, aber der zugrunde liegende Datenspeicher weiterhin den unveränderten Wert.

Transaktionsaufgabe in eine Warteschlange stellen

Sie können eine Aufgabe im Rahmen einer Datenspeichertransaktion in die Warteschlange stellen. Die Aufgabe wird in diesem Fall nur bei einem erfolgreichen Commit der Transaktion in die Warteschlange gestellt. Wenn der Commit der Transaktion fehlschlägt, wird die Aufgabe nicht in die Warteschlange gestellt. Bei einem erfolgreichen Commit der Transaktion wird die Aufgabe in die Warteschlange gestellt. Da die Aufgabe in der Warteschlange nicht sofort ausgeführt wird, ist sie nicht mit der Transaktion atomar. Dennoch wird die Aufgabe in der Warteschlange so lange wiederholt, bis sie erfolgreich ist. Dies gilt für alle Aufgaben, die im Rahmen einer dekorierten Funktion in die Warteschlange gestellt werden.

Transaktionsaufgaben sind hilfreich, da Sie damit Nicht-Datenspeicheraktionen mit einer Transaktion verbinden können. Sie erreichen dadurch, dass Nicht-Datenspeicheraktionen nur ausgeführt werden, wenn die Transaktion erfolgreich ist. Dies eignet sich beispielsweise zum Senden einer E-Mail-Bestätigung nach einem Kauf. Sie können auch Datenspeicheraktionen mit der Transaktion verbinden, um beispielsweise Änderungen an Entitätengruppen außerhalb der Transaktion per Commit festzuschreiben – aber nur, wenn die Transaktion erfolgreich ist.

Von einer Anwendung können während einer einzelnen Transaktion nicht mehr als fünf Transaktionsaufgaben in Aufgabenwarteschlangen eingefügt werden. Transaktionsaufgaben dürfen keine benutzerdefinierten Namen haben.

from google.appengine.api import taskqueue
from google.appengine.ext import ndb
@ndb.transactional
def insert_if_absent_taskq(note_key, note):
    taskqueue.add(url=flask.url_for('taskq_worker'), transactional=True)
    # do insert