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 un durata massima di 60 secondi con una scadenza di inattività di 10 secondi tempo 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 che utilizza Decoratore di @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 numero di nuovi tentativi pari a 0 indica che la transazione viene tentato una volta, ma non viene rieseguito se l'operazione non riesce; un numero di nuovi tentativi N significa che il tentativo di transazione potrebbe essere tentato 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 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 su più gruppi di entità e si comportano come le transazioni di singoli gruppi, ma non avrà esito negativo se il codice tenta di aggiornare di entità da più gruppi 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 del codice è in esecuzione all'interno di una transazione, utilizza la in_transaction() personalizzata.

Puoi specificare il comportamento di una funzione "transazionale" se viene richiamata da codice che si trova 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.

Accodamento 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 viene eseguito il commit dell'attività, in coda. Una volta inserita in coda, l'attività non verrà eseguita immediatamente, pertanto non è atomica con la transazione. Tuttavia, una volta accodata, l'attività riproverà fino a quando non riesce. 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 transazioni tasks nell'attività code durante una singola transazione. Le attività transazionali devono non hanno 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