Transazioni NDB

Una transazione è un'operazione o un insieme di operazioni di cui è garantita l'atomica, il che significa che le transazioni non vengono mai applicate parzialmente. Vengono applicate tutte le operazioni nella transazione o nessuna. Le transazioni hanno una durata massima di 60 secondi con una scadenza di inattività di 10 secondi dopo 30 secondi.

Utilizzando l'API asincrona NDB, un'applicazione può gestire più transazioni contemporaneamente, se indipendenti. L'API sincrona offre un'API semplificata utilizzando il decorator @ndb.transactional(). La funzione decorata viene eseguita nel contesto della transazione.

@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)

Se la transazione "si scontra" con un'altra, non va a buon fine; NDB riprova automaticamente alcune volte a queste transazioni non riuscite. La funzione può essere chiamata più volte se si esegue un nuovo tentativo di transazione. Esiste un limite (predefinito 3) al numero di nuovi tentativi tentati; se la transazione continua a non andare a buon fine, NDB solleva TransactionFailedError. Puoi modificare il conteggio dei nuovi tentativi passando retries=N al decorator transactional(). Un conteggio di nuovi tentativi pari a 0 indica che la transazione è stata tentata una volta ma non se ha esito negativo; un conteggio dei nuovi tentativi pari a N significa che la transazione può essere tentata per un totale di N+1 volte. Esempio:

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

Nelle transazioni sono consentite solo le query sui predecessori. Per impostazione predefinita, una transazione può funzionare solo con entità nello stesso gruppo di entità (entità le cui chiavi hanno lo stesso "predecessore").

Puoi specificare transazioni cross-group ("XG") (che consentono fino a venticinque gruppi di entità), trasmettendo xg=True:

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

Le transazioni tra gruppi operano su più gruppi di entità e si comportano come le transazioni di un solo gruppo, ma non vengono completate se il codice tenta di aggiornare le entità da più gruppi di entità.

Se la funzione genera un'eccezione, la transazione viene immediatamente interrotta e NDB genera nuovamente l'eccezione in modo che il codice chiamante possa vederla. Puoi forzare l'esecuzione silenziosa di una transazione senza riuscire ad andare a buon fine aumentando l'eccezione ndb.Rollback (la chiamata di funzione restituisce None in questo caso). Non esiste alcun meccanismo per forzare un nuovo tentativo.

Potrebbe non essere sempre opportuno eseguire una funzione in una transazione. Anziché decorare una funzione di questo tipo con @ndb.transactional, passala come funzione di callback a ndb.transaction()

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

Per verificare se del codice è in esecuzione all'interno di una transazione, utilizza la funzione in_transaction().

Puoi specificare il comportamento di una funzione "transazionale" se richiamata da un codice già presente in una transazione. Il decorator @ndb.non_transactional specifica che una funzione non deve essere eseguita in una transazione; se chiamata in una transazione, viene eseguita al di fuori della transazione. Il decorator @ndb.transactional e la funzione ndb.transaction utilizzano un argomento parola chiave propagation. Ad esempio, se una funzione deve avviare una nuova transazione indipendente, decorala in questo modo:

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

I tipi di propagazione sono elencati con le altre opzioni di contesto e le opzioni di transazione

Il comportamento della transazione e quello della memorizzazione nella cache di NDB possono combinarsi per creare confusione se non sai cosa sta succedendo. Se modifichi un'entità all'interno di una transazione ma non hai ancora eseguito il commit della transazione, la cache di contesto di NDB avrà il valore modificato, ma il datastore sottostante rimarrà il valore non modificato.

Attività transazionale in coda

Puoi accodare un'attività come parte di una transazione Datastore, in modo che venga accodata solo se il commit della transazione viene eseguito correttamente. Se non viene eseguito il commit della transazione, l'attività non viene accodata. Se viene eseguito il commit della transazione, l'attività viene accodata. Una volta accodata, l'attività non verrà eseguita immediatamente, quindi non è atomica rispetto alla transazione. Tuttavia, una volta accodato, l'attività verrà riprovata fino a quando non va a buon fine. Questo vale per qualsiasi attività accodata durante una funzione decorata.

Le attività transazionali sono utili perché consentono di combinare azioni non relative al datastore in una transazione che dipende dal completamento della transazione (come l'invio di un'email per la conferma di un acquisto). Puoi anche collegare le azioni Datastore alla transazione, ad esempio eseguire il commit delle modifiche ai gruppi di entità al di fuori della transazione se e solo se la transazione ha esito positivo.

Un'applicazione non può inserire più di cinque attività transazionali nelle code di attività durante una singola transazione. Le attività transazionali non devono avere nomi specificati dall'utente.

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