Ross max
Secondo Wikipedia, il livello di isolamento di un sistema di gestione di database "definisce come/quando le modifiche apportate da un'operazione diventano visibili ad altre operazioni simultanee". L'obiettivo di questo articolo è spiegare l'isolamento query e transazione in Cloud Datastore utilizzato da App Engine. Dopo aver letto questo articolo dovresti avere un'idea più chiara del comportamento e delle scritture contemporanee, sia dentro che fuori dalle transazioni.
Transazioni interne: serializzabili
Dal più alto al più debole, i quattro livelli di isolamento sono Serializzabile, Lettura ripetibile, Lettura con impegno e Lettura senza impegno. Le transazioni Datastore soddisfano il livello di isolamento serializzabile. Ogni transazione è completamente isolata da tutte le altre transazioni e operazioni del datastore. Le transazioni su un determinato gruppo di entità vengono eseguite in serie, una dopo un'altra.
Consulta la sezione Isolamento e coerenza della documentazione relativa alle transazioni per ulteriori informazioni e l'articolo di Wikipedia sull'isolamento degli snapshot.
Transazioni esterne: lettura impegnata
Le transazioni Datastore all'esterno sono più simili al livello di isolamento dei impegni di lettura. Le entità recuperate dal datastore tramite query o recupero vedranno solo i dati impegnati. Un'entità recuperata non avrà mai dati parzialmente impegnati (alcuni da prima di un commit e altri da dopo). Tuttavia, l'interazione tra query e transazioni è un po' più sottile e, per capirla, dobbiamo esaminare la procedura di commit in modo più approfondito.
Processo di commit
Quando un commit viene restituito correttamente, la transazione viene garantita, ma ciò non significa che il risultato della scrittura sia immediatamente visibile ai lettori. L'applicazione di una transazione consiste in due traguardi:
- Traguardo A: il punto in cui sono state applicate le modifiche a un'entità
- Traguardo B - Il punto in cui sono state applicate modifiche agli indici per quell'entità.

In Cloud Datastore, la transazione viene generalmente applicata entro poche centinaia di millisecondi dopo la restituzione del commit. Tuttavia, anche se non è completamente applicato, le operazioni successive di lettura, scrittura e query pregresse riflettono sempre i risultati del commit, perché queste operazioni applicano eventuali modifiche in sospeso prima dell'esecuzione. Tuttavia, le query che interessano più gruppi di entità non possono determinare se esistono modifiche in sospeso prima dell'esecuzione e possono restituire risultati obsoleti o parzialmente applicati.
Una richiesta che cerca un'entità aggiornata in base alla sua chiave in un momento successivo al raggiungimento del traguardo A garantisce di vedere l'ultima versione di tale entità. Tuttavia, se una richiesta in parallelo esegue una query il cui predicato (la clausola WHERE
, per i fan di SQL/GQL in circolazione) non è soddisfatta dall'entità di pre-aggiornamento, ma è soddisfatta dall'entità post-aggiornamento, l'entità farà parte del set di risultati solo se la query viene eseguita dopo che l'operazione di applicazione ha raggiunto il traguardo B.
In altre parole, durante brevi finestre è possibile che un set di risultati non includa un'entità le cui proprietà, in base al risultato di una ricerca per chiave, soddisfino il predicato di query. È inoltre possibile che un set di risultati includa un'entità le cui proprietà, sempre in base al risultato di una ricerca per chiave, non soddisfano il predicato di query. Una query non può prendere in considerazione le transazioni che si trovano tra il traguardo A e il traguardo B al momento di decidere quali entità restituire. Verrà eseguito in base a dati inattivi, ma l'esecuzione di un'operazione get()
sulle chiavi restituite avrà sempre la versione più recente di tale entità. Ciò significa che potrebbero mancare i risultati corrispondenti alla tua query oppure i risultati che non corrispondono una volta ricevuta l'entità corrispondente.
In alcuni casi viene garantita l'applicazione completa delle modifiche in attesa prima dell'esecuzione della query, ad esempio qualsiasi query predecessore in Cloud Datastore. In questo caso, i risultati delle query saranno sempre aggiornati e coerenti.
Esempi
Abbiamo fornito una spiegazione generale dell'interazione tra query e aggiornamenti simultanei. Tuttavia, se siete come me, in genere trovate più facilmente questi concetti adottando esempi concreti. Vediamone alcuni. Iniziamo con alcuni semplici esempi per passare a quelli più interessanti.
Supponiamo di avere un'applicazione che archivia le entità di tipo Persona. Una persona ha le seguenti proprietà:
- Nome
- Altezza
Questa applicazione supporta le seguenti operazioni:
updatePerson()
getTallPeople()
, che restituisce tutte le persone di altezza superiore a 182 cm.
Abbiamo due entità per persona nel datastore:
- Adam, che è alto 68 pollici.
- Bob, che è alto 73 pollici.
Esempio 1: rendere Adam Taller
Supponiamo che un'applicazione riceva due richieste contemporaneamente. La prima richiesta aggiorna l'altezza di Adam da 68 pollici a 74 pollici. Una spinta di crescita! La seconda richiesta chiama getTallPeople(). Che cosa restituisce getTallPeople()?
La risposta dipende dalla relazione tra i due traguardi di commit attivati dalla richiesta 1 e la query getTallPeople() eseguita dalla richiesta 2. Supponiamo che abbia il seguente aspetto:
- Richiesta 1,
put()
- Richiesta 2,
getTallPeople()
- Richiesta 1,
put()
-->commit()
- Richiesta 1,
put()
-->commit()
-->mile A - Richiesta 1,
put()
-->commit()
-->mile B
In questo scenario, getTallPeople()
restituirà solo Mario. Perché? Poiché l'aggiornamento ad Adam che aumenta l'altezza non è stato ancora applicato, la modifica non è ancora visibile per la query effettuata nella Richiesta 2.
Supponiamo ora che abbia il seguente aspetto:
- Richiesta 1,
put()
- Richiesta 1,
put()
-->commit()
- Richiesta 1,
put()
-->commit()
-->mile A - Richiesta 2,
getTallPeople()
- Richiesta 1,
put()
-->commit()
-->mile B
In questo scenario, la query viene eseguita prima che la richiesta 1 raggiunga il traguardo B, per cui gli aggiornamenti agli indici Persona non sono ancora stati applicati. Di conseguenza, getTallPeople() restituisce solo Robert. Questo è un esempio di set di risultati che esclude un'entità le cui proprietà soddisfano il predicato di query.
Esempio 2: Rendere Bob più breve (Mi dispiace, Roberto)
In questo esempio, la richiesta 1 avrà un comportamento diverso. Invece di
aumentare l'altezza di Adam da 68 pollici a 74 pollici, ridurrà l'altezza di Bob da 73 pollici a 65 pollici. Ancora una volta, cosa
getTallPeople()
- Richiesta 1,
put()
- Richiesta 2,
getTallPeople()
- Richiesta 1,
put()
-->commit()
- Richiesta 1,
put()
-->commit()
-->mile A - Richiesta 1,
put()
-->commit()
-->mile B
In questo scenario, getTallPeople()
restituirà solo Roberto. Perché? Poiché
l'aggiornamento a Roberto che riduce la sua altezza non è stato ancora applicato,
la modifica non è ancora visibile per la query che emettiamo nella Richiesta 2.
Supponiamo ora che abbia il seguente aspetto:
- Richiesta 1,
put()
- Richiesta 1,
put()
-->commit()
- Richiesta 1,
put()
-->commit()
-->mile A - Richiesta 1,
put()
-->commit()
-->mile B - Richiesta 2,
getTallPeople()
In questo scenario, getTallPeople()
non restituirà nessuno. Perché? Poiché l'aggiornamento a Paolo che riduce la sua altezza è stato applicato entro il momento in cui eseguiamo la nostra query nella Richiesta 2.
Supponiamo ora che abbia il seguente aspetto:
- Richiesta 1,
put()
- Richiesta 1,
put()
-->commit()
- Richiesta 1,
put()
-->commit()
-->mile A - Richiesta 2,
getTallPeople()
- Richiesta 1,
put()
-->commit()
-->mile B
In questo scenario, la query viene eseguita prima del traguardo B, pertanto gli aggiornamenti
degli indici Persona non sono ancora stati applicati. Di conseguenza,
getTallPeople()
restituisce ancora Roberto, ma la proprietà altezza dell'entità Persona
che viene restituita è il valore aggiornato: 65. Ecco un esempio di set di risultati che include un'entità le cui proprietà non soddisfano il predicato di query.
Conclusione
Come puoi vedere dagli esempi precedenti, il livello di isolamento delle transazioni di Cloud Datastore è abbastanza simile a Read Commmit. Ovviamente esistono differenze significative, ma ora che hai capito queste differenze e i motivi che ne stanno alla base, dovresti essere in una posizione migliore per prendere decisioni intelligenti e correlate al datastore nelle tue applicazioni.