Transazioni NDB

Una transazione è un'operazione o un insieme di operazioni per le quali viene garantito che siano atomiche, il che significa che le transazioni non vengono mai applicate parzialmente. Vengono applicate tutte le operazioni nella transazione o nessuna di queste viene applicata. Le transazioni hanno una durata massima di 60 secondi con un tempo di scadenza di inattività di 10 secondi dopo 30 secondi.

Utilizzando l'API asincrona dell'NDB, un'applicazione può gestire più transazioni contemporaneamente se sono 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, la transazione non va a buon fine; NDB riprova automaticamente a effettuare alcune transazioni non riuscite. La funzione può essere chiamata più volte se si tenta di nuovo la transazione. Esiste un limite (predefinito 3) al numero di nuovi tentativi tentati. Se la transazione continua a non riuscire, NDB genera TransactionFailedError. Puoi modificare il numero di nuovi tentativi passando retries=N al decorator transactional(). Un numero di nuovi tentativi pari a 0 indica che la transazione viene tentata una volta, ma non viene tentata nuovamente se non riesce; un numero di 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 dei 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 le transazioni tra gruppi ("XG") (che consentono fino a venticinque gruppi di entità), passando 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 transazioni di un singolo gruppo, ma non hanno esito negativo se il codice tenta di aggiornare le entità di più gruppi di entità.

Se la funzione genera un'eccezione, la transazione viene immediatamente interrotta e NDB solleva nuovamente l'eccezione in modo che il codice chiamante la vede. Puoi forzare la mancata riuscita di una transazione in modo automatico aumentando l'eccezione ndb.Rollback (in questo caso, la chiamata di funzione restituisce None). Non esiste un meccanismo per forzare un nuovo tentativo.

Potresti avere una funzione che non sempre vuoi eseguire in una transazione. Anziché decorare una funzione del genere 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 viene 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 di memorizzazione nella cache di NDB possono combinarsi per confonderti 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 avrà ancora il valore non modificato.

Accodamento delle attività transazionali

Puoi accodare un'attività come parte di una transazione Datastore, in modo che venga accodata solo se il commit della transazione è stato eseguito correttamente. Se il commit della transazione non viene eseguito, l'attività non viene accodata. Se il commit della transazione viene eseguito, l'attività viene accodata. Una volta accodata, l'attività non verrà eseguita immediatamente, quindi non è atomica sulla transazione. Tuttavia, una volta accodata, l'attività riproverà fino a quando non riesce. Questo vale per tutte le attività accodate durante una funzione decorata.

Le attività transazionali sono utili perché ti consentono di combinare azioni non Datastore in una transazione che dipende dalla riuscita della transazione (ad esempio, l'invio di un'email per confermare un acquisto). Puoi anche collegare le azioni Datastore alla transazione, ad esempio eseguire il commit delle modifiche in 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