Questo documento descrive il modello di dati per gli oggetti archiviati in Datastore, come sono strutturate le query utilizzando l'API e come vengono elaborate le transazioni.
Per visualizzare i contenuti del pacchetto datastore
, consulta la sezione Riferimento al pacchetto datastore
.
Entità
Gli oggetti in Datastore sono noti come entità. Un'entità ha una o più proprietà denominate, ciascuna delle quali può avere uno o più valori. I valori delle proprietà possono appartenere a diversi tipi di dati, tra cui numeri interi, numeri a virgola mobile, stringhe, date e dati binari. Una query su una proprietà con più valori verifica se uno dei valori soddisfa i criteri di query. Ciò rende queste proprietà utili per il test di appartenenza.
Tipi, chiavi e identificatori
Ogni entità di Datastore è di un determinato tipo, che la classifica ai fini delle query. Ad esempio, un'applicazione per le risorse umane potrebbe rappresentare ogni dipendente di un'azienda con un'entità di tipo Employee
. Inoltre, ogni entità ha la propria chiave, che la identifica in modo univoco. La chiave è composta dai seguenti componenti:
- Il tipo di entità
- Un identificatore, che può essere:
- una stringa del nome della chiave
- un ID intero
- Un percorso dell'antenato facoltativo che individua l'entità all'interno della gerarchia di Datastore
L'identificatore viene assegnato al momento della creazione dell'entità. Poiché fa parte della chiave dell'entità, è associato in modo permanente all'entità e non può essere modificato. Può essere assegnato in due modi:
- L'applicazione può specificare la propria stringa di nome della chiave per l'entità.
- Puoi chiedere a Datastore di assegnare automaticamente all'entità un ID numerico intero.
Assegnazione degli identificatori
Il server di runtime può essere configurato per generare automaticamente gli ID utilizzando due diversi criteri di ID automatico:
- Il criterio
default
genera una sequenza di ID distribuiti approssimativamente in modo uniforme. Ogni ID può contenere fino a 16 cifre. - Il criterio
legacy
crea una sequenza di ID interi più piccoli non consecutivi.
Se vuoi mostrare gli ID entità all'utente e/o dipendere dall'ordine, la cosa migliore da fare è utilizzare l'allocazione manuale.
Percorsi predecessori
Le entità in Cloud Datastore formano uno spazio strutturato in modo gerarchico simile alla struttura di directory di un file system. Quando crei un'entità, puoi facoltativamente designare un'altra entità come principale; la nuova entità è una figlio dell'entità padre (tieni presente che, a differenza di un file system, l'entità padre non è necessario che esista effettivamente). Un'entità senza un elemento padre è una root . L'associazione tra un'entità e la relativa entità principale è permanente e non può essere modificata dopo la creazione dell'entità. Cloud Datastore non assegnerà mai lo stesso ID numerico a due entità con lo stesso elemento principale o a due entità principali (quelle senza un elemento principale).
L'elemento padre di un'entità, il padre dell'elemento padre e così via in modo ricorsivo antenati; i relativi figli, figli dei bambini e così via, sono i discendenti. Entità base e tutti i suoi discendenti appartengono a all'interno dello stesso gruppo di entità. La sequenza di entità che inizia con una radice dell'entità e procedere da principale a figlio, portando a una determinata entità, costituiscono il percorso predecessore dell'entità. La chiave completa che identifica l'entità è composta da una sequenza di coppie tipo-identificatore che specifica predecessore e termina con quelli dell'entità stessa:
[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]
Per entità base, il percorso predecessore è vuoto e la chiave è costituita esclusivamente da tipo e identificatore dell'entità:
[Person:GreatGrandpa]
Questo concetto è illustrato dal seguente diagramma:
Query e indici
Oltre a recuperare le entità da Datastore direttamente tramite le relative chiavi, un'applicazione può eseguire una query per recuperarle in base ai valori delle relative proprietà. La query opera su entità di un determinato tipo; può specificare filtri sui valori delle proprietà, sulle chiavi e sugli antenati delle entità e può restituire zero o più entità come risultati. Una query può anche specificare ordinare per sequenziare i risultati in base ai valori delle proprietà. I risultati includono tutte entità con almeno valore per ogni proprietà menzionata nei filtri e nell'ordinamento e le cui i valori delle proprietà soddisfano tutti i criteri di filtro specificati. La query può restituire intere entità, entità previste, o semplicemente entità chiavi.
Una query tipica include quanto segue:
- Un tipo di entità a cui si applica la query
- Zero o più filtri in base al set di dati delle entità, valori delle proprietà, chiavi e predecessori
- Zero o più ordinare per sequenziare i risultati
Nota: per risparmiare memoria e migliorare una query, quando possibile, deve specificare un limite al numero vengono restituiti i risultati.
Una query può includere anche un filtro antenato che limita i risultati solo al gruppo di entità discendente da un antenato specificato. Questa è nota come query predecessore. Per impostazione predefinita, le query dei predecessori restituiscono risultati a elevata coerenza, che sono garantiti essere aggiornati con le ultime modifiche apportate al e i dati di Google Cloud. Al contrario, le query non predecessori possono coprire l'intero Datastore anziché un singolo gruppo di entità, ma sono solo a coerenza finale e possono restituire risultati inattivi. Se la coerenza forte è importante per la tua applicazione, potresti doverla prendere in considerazione durante la strutturazione dei dati, inserendo le entità correlate nello stesso gruppo di entità in modo che possano essere recuperate con un'antecedente anziché una query non antecedente per ulteriori informazioni.
App Engine predefinisce un indice semplice per ogni proprietà di un'entità.
Un'applicazione App Engine può definire ulteriori indici personalizzati in un
indice
di configurazione del deployment denominato
index.yaml
. Il server di sviluppo esegue automaticamente
aggiunge suggerimenti a questo file quando rileva query che non possono essere eseguite con gli indici esistenti.
Puoi ottimizzare gli indici manualmente modificando il file prima di caricare l'applicazione.
Nota: Il meccanismo di query basato su indice supporta un'ampia gamma di query ed è adatto a per la maggior parte delle applicazioni. Tuttavia, non supporta alcuni tipi di query comuni in altre tecnologie di database: in particolare, le unioni e le query aggregate non sono supportate nel motore di query di Datastore. Consulta: Pagina Query Datastore per conoscere i limiti delle query Datastore.
Transazioni
Ogni tentativo di inserire, aggiornare o eliminare un'entità avviene nel contesto di una transazione. Una singola transazione può includere un numero qualsiasi di queste operazioni. Per mantenere la coerenza dei dati, la transazione garantisce che tutte le operazioni che contiene vengano applicate a Datastore come unità o, in caso di esito negativo di una qualsiasi operazione, che nessuna di esse venga applicata.
È possibile eseguire più azioni su un'entità all'interno di una singola transazione. Ad esempio, per incrementare un campo contatore in un oggetto, devi leggere il valore del contatore, calcolare il nuovo valore e poi riarchiviarlo. Senza una transazione, è possibile che un altro processo incrementi il contatore tra il momento in cui leggi il valore e quello in cui lo aggiorni, facendo sì che l'applicazione sovrascriva il valore aggiornato. Eseguire la lettura, il calcolo e la scrittura in una singola transazione garantisce che nessun'altra procedura possa interferire con l'incremento.
Transazioni e gruppi di entità
All'interno di una transazione sono consentite solo query sugli antenati: in altre parole, ogni query transazionale deve essere limitata a un singolo gruppo di entità. La transazione stessa può essere applicata a più entità, che possono appartenere a un singolo gruppo di entità o (nel caso di una transazione tra gruppi) a un massimo di venticinque gruppi di entità diversi.
Datastore utilizza la concorrenza ottimistica per gestire le transazioni. Quando due o più transazioni tentano di modificare contemporaneamente lo stesso gruppo di entità (aggiornando le entità esistenti o creandone di nuove), la prima transazione per cui eseguire il commit avrà esito positivo e tutte le altre non andranno a buon fine al momento del commit. Queste altre transazioni possono quindi essere riprovate sui dati aggiornati. Tieni presente che questo limita il numero di scritture simultanee che puoi eseguire su qualsiasi entità in un determinato gruppo di entità.
Transazioni tra gruppi
Una transazione su entità appartenenti a diversi gruppi di entità viene chiamata transazione tra gruppi (XG). La transazione può essere applicata a un massimo di venticinque gruppi di entità e andrà a buon fine a condizione che nessuna transazione simultanea riguardi i gruppi di entità a cui si applica. Questo ti offre una maggiore flessibilità nell'organizzazione dei dati, perché non sei costretto a inserire dati diversi sotto lo stesso predecessore solo per eseguire scritture atomiche.
Come nel caso di una transazione a gruppo singolo, non puoi eseguire una query non precedente in una transazione XG. Tuttavia, puoi eseguire query predecessore su gruppi di entità separati. Le query non transazionali (non predecessori) potrebbero visualizzare tutti, alcuni o nessuno dei risultati di una transazione di cui è stato eseguito il commit in precedenza. Per informazioni di base su questo problema, consulta Scritture e visibilità dei dati in Datastore. Tuttavia, queste query non transazionali hanno più probabilità di ottenere i risultati di una transazione XG parzialmente impegnata rispetto a quelli di una transazione con un singolo gruppo con impegno parziale.
Una transazione XG che interessa un solo gruppo di entità ha esattamente lo stesso rendimento e lo stesso costo di una transazione non XG a un solo gruppo. In una transazione XG che interessa più gruppi di entità, le operazioni hanno lo stesso costo di come se fossero state eseguite in una transazione non XG, ma potrebbero riscontrare una latenza maggiore.
Scritture in Datastore e visibilità dei dati
I dati vengono scritti in Datastore in due fasi:
- Nella fase di commit, i dati delle entità vengono registrati nei log delle transazioni della maggior parte delle repliche e le repliche in cui non sono stati registrati sono contrassegnate come non aggiornate.
- La fase di applicazione avviene in modo indipendente in ogni replica e consiste in due azioni eseguite in parallelo:
- I dati delle entità vengono scritti in quella replica.
- Le righe dell'indice per l'entità vengono scritte in quella replica. Tieni presente che questa operazione può richiedere più tempo rispetto alla scrittura dei dati stessi.
L'operazione di scrittura viene restituita immediatamente dopo la fase di commit e la fase di applicazione viene eseguita in modo asincrono, eventualmente in momenti diversi in ogni replica e con ritardi di alcune centinaia di millisecondi o più dal completamento della fase di commit. Se si verifica un errore durante la fase di commit, esistono nuovi tentativi automatici. ma se gli errori persistono, Datastore restituisce un messaggio di errore che l'applicazione riceve come eccezione. Se la fase di commit ha esito positivo, ma l'applicazione non riesce in una determinata replica, viene eseguito il rollback dell'applicazione al completamento nella replica quando si verifica una delle seguenti condizioni:
- Le analisi periodiche di Datastore controllano la presenza di job di commit non completati e li applicano.
- Alcune operazioni (
get
,put
,delete
e query sugli antenati) che utilizzano il gruppo di entità interessato causano il completamento di eventuali modifiche che sono state committate, ma non ancora applicate, nella replica in cui vengono eseguite prima di procedere con la nuova operazione.
Questo comportamento di scrittura può avere diverse implicazioni su come e quando i dati sono visibili all'applicazione in diverse parti delle fasi di commit e applicazione:
- Se un'operazione di scrittura segnala un errore di timeout, non è possibile determinare (senza tentare di leggere i dati) se l'operazione è riuscita o meno.
- Poiché le query Datastore e dei predecessori applicano le modifiche in sospeso alla replica su cui vengono eseguite, queste operazioni vedono sempre una vista coerente di tutte le precedenti transazioni riuscite. Ciò significa che un'operazione
get
(ricerca di un'entità aggiornata in base alla relativa chiave) garantisce la visualizzazione della versione più recente dell'entità. - Le query non predecessori potrebbero restituire risultati inattivi perché potrebbero essere in esecuzione su una replica su cui non sono ancora state applicate le ultime transazioni. Ciò può verificarsi anche se è stata eseguita un'operazione che garantisce l'applicazione delle transazioni in sospeso perché la query potrebbe essere eseguita su una replica diversa rispetto all'operazione precedente.
- La tempistica delle modifiche simultanee può influire sui risultati delle query non antecedenti. Se un'entità soddisfa una query inizialmente, ma in seguito viene modificata in modo da non farlo più, l'entità può comunque essere inclusa nel set di risultati della query se le modifiche non erano ancora state applicate agli indici nella replica su cui è stata eseguita la query.=
Statistiche di Datastore
Datastore gestisce le statistiche relative ai dati archiviati per un'applicazione, come il numero di entità di un determinato tipo o lo spazio utilizzato dai valori delle proprietà di un determinato tipo. Puoi visualizzare queste statistiche nel Console Google Cloud Pagina Dashboard di Datastore. Puoi anche utilizzare l'API Datastore per accedere a questi valori in modo programmatico dall'interno dell'applicazione eseguendo query sulle entità con nomi speciali. Per ulteriori informazioni, consulta Statistiche di Datastore in Go 1.11.
Esempio di Datastore Go 1.11
In Go, le entità Datastore vengono create dai valori struct
. I campi della struttura diventano le proprietà dell'entità. Per creare una nuova entità, imposta il valore che vuoi memorizzare, crea una chiave e passali entrambi a datastore.Put()
. Per aggiornare un'entità esistente, è sufficiente eseguire un altro Put()
utilizzando la stessa chiave. Per recuperare un'entità da Datastore, devi prima impostare un valore in cui verrà annullato il marshall dell'entità, quindi passare a datastore.Get()
una chiave e un puntatore a quel valore.
Questo esempio archivia e recupera alcuni dati da Datastore:
Per ulteriori dettagli, consulta la documentazione di riferimento di Datastore.