Transazioni NDB

Una transazione è un'operazione o un insieme di operazioni che hanno la garanzia di essere atomiche, il che significa che le transazioni non vengono mai applicate parzialmente. Vengono applicate tutte le operazioni della transazione o nessuna. 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 NDB, un'applicazione può gestire più transazioni contemporaneamente se sono indipendenti. L'API sincrona offre un'API semplificata utilizzando il @ndb.transactional() decoratore. 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 "entra in conflitto" con un'altra, non va a buon fine. NDB ritenta automaticamente le transazioni non riuscite alcune volte. La funzione può essere chiamata più volte se la transazione viene ritentata. Esiste un limite (valore predefinito 3) al numero di tentativi Se la transazione non va ancora a buon fine, NDB genera TransactionFailedError. Puoi modificare il numero di tentativi passando retries=N al transactional() decoratore. Un numero di tentativi pari a 0 significa che la transazione viene tentata una volta, ma non viene ritentata se non va a buon fine. Un numero di tentativi pari a N significa che la transazione può essere tentata 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 di antenati. Per impostazione predefinita, una transazione può funzionare solo con le entità dello stesso gruppo di entità (entità le cui chiavi hanno lo stesso "predecessore").

Puoi specificare le transazioni tra gruppi ("XG") (che consentono fino a 25 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 a gruppo singolo, ma non vanno a buon fine se il codice tenta di aggiornare le entità da più di un gruppo di entità.

Se la funzione genera un'eccezione, la transazione viene immediatamente interrotta e NDB genera di nuovo l' eccezione in modo che il codice chiamante la veda. Puoi forzare l'esito negativo di una transazione senza generare errori generando l' ndb.Rollback eccezione (in questo caso la chiamata di funzione restituisce None ). Non esiste un meccanismo per forzare un tentativo.

Potresti avere una funzione che non vuoi sempre eseguire in una transazione. Invece di 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 un codice è in esecuzione all'interno di una transazione, utilizza la in_transaction() funzione.

Puoi specificare il comportamento di una funzione "transazionale" se viene richiamata da un codice già in una transazione. Il @ndb.non_transactional decoratore 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 @ndb.transactional decoratore e la ndb.transaction funzione accettano un propagation argomento parola chiave. Ad esempio, se una funzione deve avviare una nuova transazione indipendente, decorala nel seguente 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 opzioni di transazione

Il comportamento delle transazioni e il comportamento di memorizzazione nella cache di NDB possono confondere 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 contiene il valore modificato, ma il datastore sottostante contiene ancora il valore non modificato.

Accodamento delle attività transazionali

Puoi accodare un'attività come parte di una transazione Datastore, in modo che l'attività venga accodata solo se il commit della transazione viene 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 con la transazione. Tuttavia, una volta accodata, l'attività verrà ritentata finché non va a buon fine. Questo vale per qualsiasi attività accodata durante una funzione decorata.

Le attività transazionali sono utili perché ti consentono di combinare azioni non Datastore a una transazione che dipende dal successo della transazione (ad esempio, l'invio di un'email di conferma di un acquisto). Puoi anche collegare le azioni Datastore alla transazione, ad esempio per eseguire il commit delle modifiche ai gruppi di entità al di fuori della transazione se e solo se la transazione va a buon fine.

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