Transazioni NDB

Una transazione è un'operazione o un insieme di operazioni che è garantito che siano 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ù transazioni 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 "entra in conflitto" con un'altra, non va a buon fine; NDB ritenta automaticamente alcune volte le transazioni non riuscite. La funzione può essere chiamata più volte se la transazione viene ritentata. Esiste un limite (3 per impostazione predefinita) al numero di tentativi eseguiti; se la transazione non va ancora a buon fine, 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 ritentata in caso di errore; un conteggio dei tentativi pari a N indica 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 "antenato").

Puoi specificare transazioni cross-group ("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 le transazioni di un singolo gruppo, ma non hanno esito negativo 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 di nuovo l'eccezione in modo che il codice chiamante la visualizzi. Puoi forzare l'interruzione silenziosa di una transazione generando l'eccezione ndb.Rollback (la chiamata di funzione restituisce None in questo caso). Non esiste un meccanismo per forzare un nuovo tentativo.

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

Puoi specificare il comportamento di una funzione "transazionale" se viene richiamata da un codice già presente in una transazione. Il decoratore @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 decoratore @ndb.transactional e la funzione ndb.transaction accettano un argomento di parola chiave propagation. 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 insieme alle altre Opzioni di contesto e Opzioni di transazione

Il comportamento delle transazioni e quello di 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 contiene il valore modificato, ma il datastore sottostante contiene ancora il valore non modificato.

Accodamento delle attività transazionali

Puoi mettere in coda un'attività nell'ambito di una transazione Datastore, in modo che l'attività venga messa in coda solo se la transazione viene eseguita correttamente. Se il commit della transazione non viene eseguito, l'attività non viene inserita nella coda. Se viene eseguito il commit della transazione, l'attività viene messa in coda. Una volta accodata, l'attività non verrà eseguita immediatamente, pertanto non è atomica con la transazione. Tuttavia, una volta accodata, l'attività verrà ritentata finché non va a buon fine. Ciò 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 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 per 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