Transazioni NDB

Una transazione è un'operazione o un insieme di operazioni che sono garantite come atomiche, 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 un periodo di scadenza di inattività di 10 secondi dopo 30 secondi.

Utilizzando l'API asincrona NDB, un'applicazione può gestire più operazioni contemporaneamente se sono indipendenti. L'API sincrona offre un'API semplificata che utilizza il decoratore @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 "collide" con un'altra, non va a buon fine; NDB ritenta automaticamente queste transazioni non andate a buon fine alcune volte. La funzione può essere chiamata più volte se la transazione viene ripetuta. Esiste un limite (3 per impostazione predefinita) al numero di tentativi di ripetizione effettuati. Se la transazione continua a non riuscire, NDB genera TransactionFailedError. Puoi modificare il numero di tentativi passando retries=N al decoratore transactional(). Un conteggio dei tentativi pari a 0 indica che la transazione viene tentata una volta, ma non viene riprovata in caso di errore. Un conteggio dei tentativi pari a N indica 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 query sugli antenati. Per impostazione predefinita, una transazione può funzionare solo con le entità nello stesso gruppo di entità (le entità le cui chiavi hanno lo stesso "antenato").

Puoi specificare 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 in più gruppi di entità e si comportano come le transazioni a gruppo singolo, ma non falliscono se il codice tenta di aggiornare le entità di più di un gruppo di entità.

Se la funzione genera un'eccezione, la transazione viene interrotta immediatamente e NDB genera nuovamente l'eccezione in modo che il codice chiamante la veda. Puoi forzare il fallimento silenzioso di una transazione sollevando l'eccezione ndb.Rollback (in questo caso la chiamata alla funzione restituisce None). Non esiste un meccanismo per forzare un nuovo tentativo.

Potresti avere una funzione che non vuoi eseguire sempre 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 funzione in_transaction().

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

@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 di transazione

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

Coda delle attività transazionali

Puoi mettere in coda un'attività nell'ambito di una transazione Datastore, in modo che venga messa in coda solo se la transazione viene eseguita correttamente. Se la transazione non viene eseguita, il compito non viene inserito in coda. Se la transazione viene eseguita, l'attività viene messa in coda. Una volta inserita in coda, l'attività non verrà eseguita immediatamente, pertanto non è atomica con la transazione. Tuttavia, una volta messa in coda, l'attività riproverà finché non andrà a buon fine. Questo vale per qualsiasi attività in coda durante una funzione decorata.

Le attività transazionali sono utili perché ti consentono di combinare azioni non Datastore con una transazione che dipende dal suo buon esito (ad esempio l'invio di un'email per confermare un acquisto). Puoi anche associare le azioni di Datastore alla transazione, ad esempio per applicare le 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à transactional nelle code di lavoro durante una singola transazione. Le attività di transazione 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