API Datastore di App Engine per i servizi in bundle legacy

Questo documento descrive il modello dei dati per gli oggetti archiviati in Datastore, come vengono strutturate le query utilizzando l'API e come vengono elaborate le transazioni. Per visualizzare i contenuti del pacchetto datastore, consulta il riferimento al pacchetto datastore.

Entità

Gli oggetti in Datastore sono noti come entità. Un'entità ha una o più proprietà denominate, ognuna delle quali può avere uno o più valori. I valori delle proprietà possono appartenere a diversi tipi di dati, tra cui numeri interi, numeri in virgola mobile, stringhe, date e dati binari. Una query su una proprietà con più valori verifica se uno dei valori soddisfa i criteri della query. Ciò rende queste proprietà utili per il test dell'appartenenza.

Tipi, chiavi e identificatori

Ogni entità 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 una propria chiave, che la identifica in modo univoco. La chiave è costituita dai seguenti componenti:

  • Il tipo di entità
  • Un identificatore, che può essere
    • una stringa nome chiave
    • un ID intero
  • Un percorso degli antenati facoltativo che individua l'entità all'interno della gerarchia 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:

  • La tua applicazione può specificare la propria stringa del nome della chiave per l'entità.
  • Puoi fare in modo che Datastore assegni automaticamente all'entità un ID numerico intero.

Assegnazione degli identificatori

Il server di runtime può essere configurato per generare automaticamente gli ID utilizzando due diverse norme di generazione automatica degli ID:

  • Il criterio default genera una sequenza di ID distribuiti in modo approssimativamente 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 dal loro ordine, la cosa migliore da fare è utilizzare l'allocazione manuale.

Percorsi degli antenati

Le entità in Cloud Datastore formano uno spazio strutturato gerarchicamente simile alla struttura di directory di un file system. Quando crei un'entità, puoi designare facoltativamente un'altra entità come genitore; la nuova entità è un elemento secondario dell'entità padre (tieni presente che, a differenza di un file system, l'entità genitore non deve esistere). Un'entità senza un elemento principale è un'entità radice. 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à radice (quelle senza un elemento principale).

Il genitore di un'entità, il genitore del genitore e così via in modo ricorsivo sono i suoi antenati; i suoi figli, i figli dei figli e così via sono i suoi discendenti. Un'entità base e tutti i relativi discendenti appartengono allo stesso gruppo di entità. La sequenza di entità che inizia con un'entità principale e procede da genitore a figlio, fino a un'entità specifica, costituisce il percorso degli antenati dell'entità. La chiave completa che identifica l'entità è costituita da una sequenza di coppie tipo-identificatore che specificano il percorso degli antenati e terminano con quelli dell'entità stessa:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Per unentità base, il percorso degli antenati è vuoto e la chiave è costituita unicamente dal tipo e dall'identificatore dell'entità:

[Person:GreatGrandpa]

Questo concetto è illustrato nel seguente diagramma:

Mostra la relazione tra l'entità base e le entità secondarie
  nel gruppo di entità

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 ordini di ordinamento per sequenziare i risultati in base ai valori delle proprietà. I risultati includono tutte le entità che hanno almeno un valore per ogni proprietà denominata nei filtri e negli ordinamenti e i cui valori delle proprietà soddisfano tutti i criteri di filtro specificati. La query può restituire entità intere, entità proiettate, o solo chiavi di entità.

Una query tipica include quanto segue:

Quando viene eseguita, la query recupera tutte le entità del tipo specificato che soddisfano tutti i filtri specificati, ordinate nell'ordine specificato. Le query vengono eseguite in modalità di sola lettura.

Nota:per risparmiare memoria e migliorare le prestazioni, una query deve, se possibile, specificare un limite al numero di risultati restituiti.

Una query può includere anche un filtro antenato che limita i risultati al solo gruppo di entità discendente da un antenato specificato. Una query di questo tipo è nota come query antenato. Per impostazione predefinita, le query di antenati restituiscono risultati fortemente coerenti, che sono garantiti per essere aggiornati con le ultime modifiche ai dati. Le query non discendenti, al contrario, possono estendersi all'intero Datastore anziché a un singolo gruppo di entità, ma sono solo alla fine coerenti e potrebbero restituire risultati obsoleti. Se la elevata coerenza è importante per la tua applicazione, potresti doverla prendere in considerazione quando strutturi i dati, inserendo le entità correlate nello stesso gruppo di entità in modo che possano essere recuperate con una query di tipo ancestor anziché non ancestor 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 file di configurazione dell'indice denominato index.yaml. Il server di sviluppo aggiunge automaticamente 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 alla maggior parte delle applicazioni. Tuttavia, non supporta alcuni tipi di query comuni in altre tecnologie di database: in particolare, i join e le query di aggregazione non sono supportati nel motore di query Datastore. Consulta la pagina Query Datastore per i limiti relativi alle 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à oppure, se una delle operazioni non va a buon fine, che nessuna di queste venga applicata.

Puoi 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 memorizzarlo di nuovo. Senza una transazione, è possibile che un altro processo incrementi il contatore tra il momento in cui leggi il valore e il momento in cui lo aggiorni, causando la sovrascrittura del valore aggiornato da parte dell'applicazione. L'esecuzione di lettura, calcolo e scrittura in un'unica transazione garantisce che nessun altro processo possa interferire con l'incremento.

Transazioni e gruppi di entità

All'interno di una transazione sono consentite solo query di antenati, ovvero 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 lo stesso gruppo di entità contemporaneamente (aggiornando le entità esistenti o creandone di nuove), la prima transazione di commit andrà a buon fine e tutte le altre non riusciranno a eseguire il commit. Queste altre transazioni possono essere riprovate con i dati aggiornati. Tieni presente che questo limite 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 gruppi di entità diversi viene chiamata transazione cross-group (XG). La transazione può essere applicata a un massimo di 25 gruppi di entità e andrà a buon fine a condizione che nessuna transazione simultanea tocchi uno dei gruppi di entità a cui si applica. In questo modo hai più flessibilità nell'organizzazione dei dati, perché non sei costretto a inserire dati disparati sotto lo stesso antenato solo per eseguire scritture atomiche.

Come in una transazione a un solo gruppo, non puoi eseguire una query non discendente in una transazione XG. Tuttavia, puoi eseguire query di antenati su gruppi di entità separati. Le query non transazionali (non predecessori) possono 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, vedi Scritture Datastore e visibilità dei dati. Tuttavia, è più probabile che queste query non transazionali visualizzino i risultati di una transazione XG parzialmente eseguita rispetto a quelli di una transazione a gruppo singolo parzialmente eseguita.

Una transazione XG che tocca un solo gruppo di entità ha esattamente lo stesso rendimento e costo di una transazione non XG a un solo gruppo. In una transazione XG che tocca più gruppi di entità, le operazioni costano come se fossero eseguite in una transazione non XG, ma potrebbero subire una latenza maggiore.

Scritture Datastore e visibilità dei dati

I dati vengono scritti in Datastore in due fasi:

  1. Nella fase di commit, i dati dell'entità vengono registrati nei log delle transazioni della maggior parte delle repliche e le repliche in cui non sono stati registrati vengono contrassegnate come non aggiornate.
  2. La fase di applicazione si verifica in modo indipendente in ogni replica ed è costituita da due azioni eseguite in parallelo:
    • I dati dell'entità vengono scritti in questa replica.
    • Le righe dell'indice per l'entità vengono scritte in questa 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, possibilmente in momenti diversi in ogni replica e possibilmente con ritardi di qualche centinaio di millisecondi o più rispetto al completamento della fase di commit. Se si verifica un errore durante la fase di commit, vengono eseguiti nuovi tentativi automatici; tuttavia, se gli errori persistono, Datastore restituisce un messaggio di errore che la tua applicazione riceve come eccezione. Se la fase di commit va a buon fine, ma l'applicazione non riesce in una determinata replica, l'applicazione viene completata in quella replica quando si verifica una delle seguenti condizioni:

  • I controlli periodici di Datastore verificano la presenza di job Commit non completati e li applicano.
  • Determinate operazioni (get, put, delete e query sugli antenati) che utilizzano il gruppo di entità interessato fanno sì che le modifiche di cui è stato eseguito il commit ma non ancora applicate vengano completate 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 alla tua 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 get e ancestor di Datastore applicano tutte le modifiche in sospeso alla replica su cui vengono eseguite, queste operazioni vedono sempre una visualizzazione coerente di tutte le transazioni precedenti riuscite. Ciò significa che un'operazione get (ricerca di un'entità aggiornata in base alla chiave) garantisce la visualizzazione dell'ultima versione dell'entità.
  • Le query non discendenti potrebbero restituire risultati non aggiornati perché potrebbero essere eseguite su una replica a cui non sono ancora state applicate le transazioni più recenti. 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 discendenti. Se un'entità inizialmente soddisfa una query, ma viene modificata in modo che non la soddisfi più, l'entità potrebbe comunque essere inclusa nel set di risultati della query se le modifiche non sono ancora state applicate agli indici nella replica su cui è stata eseguita la query.

Statistiche di Datastore

Datastore gestisce statistiche sui dati archiviati per un'applicazione, ad esempio il numero di entità di un determinato tipo o lo spazio utilizzato dai valori delle proprietà di un determinato tipo. Puoi visualizzare queste statistiche nella pagina Dashboard Datastore della consoleGoogle Cloud . Puoi anche utilizzare l'API Datastore per accedere a questi valori in modo programmatico dall'interno dell'applicazione eseguendo query per entità con nomi speciali. Per saperne di più, consulta Statistiche 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à, devi configurare il valore che vuoi archiviare, creare una chiave e passarli entrambi a datastore.Put(). L'aggiornamento di un'entità esistente consiste nell'eseguire un altro Put() utilizzando la stessa chiave. Per recuperare un'entità da Datastore, devi prima impostare un valore in cui verrà eseguito l'unmarshalling dell'entità, quindi passare una chiave e un puntatore a quel valore a datastore.Get().

Questo esempio archivia e recupera alcuni dati da Datastore:

import (
	"fmt"
	"net/http"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/user"
)

type Employee struct {
	Name     string
	Role     string
	HireDate time.Time
	Account  string
}

func handle(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	e1 := Employee{
		Name:     "Joe Citizen",
		Role:     "Manager",
		HireDate: time.Now(),
		Account:  user.Current(ctx).String(),
	}

	key, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "employee", nil), &e1)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var e2 Employee
	if err = datastore.Get(ctx, key, &e2); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Stored and retrieved the Employee named %q", e2.Name)
}

Per ulteriori dettagli, consulta le Informazioni di riferimento su Datastore.