API App Engine Datastore per servizi in bundle legacy

Questo documento descrive il modello dei dati per gli oggetti archiviati in Datastore, il modo in cui vengono strutturate le query utilizzando l'API e il modo in cui vengono elaborate le transazioni.

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 e in virgola mobile, stringhe, date e dati binari. Una query su una proprietà con più valori verifica se uno o più dei valori soddisfa i criteri della query. Ciò rende tali proprietà utili per i test di appartenenza.

Tipi, chiavi e identificatori

Ogni entità Datastore è di un tipo particolare,che classifica l'entità ai fini delle query; ad esempio, un'applicazione per le risorse umane potrebbe rappresentare ciascun 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 del nome della chiave
    • un ID intero
  • Un percorso predecessore 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à, è associata in modo permanente all'entità e non può essere modificata. Può essere assegnato in due modi:

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

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 eventualmente indicare un'altra entità come parent;; la nuova entità è un'entità parent; dell'entità padre (tieni presente che, a differenza di un file system, l'entità padre non deve necessariamente esistere). Un'entità senza un'entità padre è un'entità principale. L'associazione tra un'entità e l'entità padre è permanente e non può essere modificata una volta creata l'entità. Cloud Datastore non assegnerà mai lo stesso ID numerico a due entità con la stessa entità padre o a due entità radice (senza un elemento padre).

L'entità padre, l'entità padre e così via in modo ricorsivo, sono i suoi antenati; i suoi figli, i figli di figli e così via sono i suoi discendenti. Un'entità base e tutti i suoi discendenti appartengono allo stesso gruppo di entità. La sequenza di entità che inizia con un'entità radice e che prosegue dalla risorsa padre a quella figlio e che porta a una determinata entità, costituisce il percorso predecessore di quell'entità. La chiave completa che identifica l'entità è composta da una sequenza di coppie tipo-identificatore che specifica il percorso predecessore e termina con quelli dell'entità stessa:

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

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

[Person:GreatGrandpa]

Questo concetto è illustrato dal seguente diagramma:

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

Query e indici

Oltre a recuperare le entità da Datastore direttamente dalle relative chiavi, un'applicazione può eseguire una query per recuperarle in base ai valori delle loro proprietà. La query opera su entità di un determinato kind; può specificare filtri su valori delle proprietà, chiavi e predecessori delle entità e può restituire zero o più entità come risultati. Una query può anche specificare gli ordini di ordinamento per sequenza i risultati in base ai valori delle proprietà. I risultati includono tutte le entità che hanno almeno un valore (possibilmente nullo) per ogni proprietà denominata nei filtri e negli ordini di ordinamento e i cui valori delle proprietà soddisfano tutti i criteri di filtro specificati. La query può restituire intere entità, entità previste 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 sola lettura.

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

Una query può anche includere un filtro predecessore che limita i risultati al solo gruppo di entità disceso da un predecessore specificato. Questa query è nota come query predecessore. Per impostazione predefinita, le query predecessori restituiscono risultati molto coerenti, garantiti essere sempre aggiornati con le ultime modifiche apportate ai dati. Le query non predecessori, invece, possono estendersi sull'intero Datastore anziché su un singolo gruppo di entità, ma sono solo coerenti alla fine e potrebbero restituire risultati obsoleti. Se un'elevata coerenza è importante per la tua applicazione, potrebbe essere necessario tenerne conto durante la strutturazione dei dati, posizionando entità correlate nello stesso gruppo di entità in modo che possano essere recuperate con un predecessore anziché con una query di non predecessore. Per saperne di più, consulta Strutturare i dati per garantire una coerenza elevata.

App Engine predefinisce un indice semplice su ogni proprietà di un'entità. Un'applicazione App Engine può definire ulteriori indici personalizzati in un file di configurazione degli indici denominato datastore-indexes.xml, generato nella directory /war/WEB-INF/appengine-generated dell'applicazione. 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 aggregate non sono supportati all'interno del motore di query Datastore. Consulta la pagina Query Datastore per le limitazioni sulle 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 illimitato di operazioni di questo tipo. 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 errore, che nessuna delle operazioni 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 poi memorizzarlo di nuovo. Senza una transazione, è possibile che un altro processo aumenti il contatore tra il momento in cui leggi il valore e quello dell'aggiornamento, causando la sovrascrittura del valore aggiornato da parte dell'applicazione. Eseguire operazioni di lettura, calcolo e scrittura in una singola transazione garantisce che nessun altro processo possa interferire con l'incremento.

Transazioni e gruppi di entità

All'interno di una transazione sono consentite solo le query sui predecessori, ovvero ogni query transazionale deve essere limitata a un singolo gruppo di entità. La transazione 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 contemporaneità ottimistica per gestire le transazioni. Quando due o più transazioni tentano di modificare contemporaneamente lo stesso gruppo di entità (aggiornando entità esistenti o creandone di nuove), la prima transazione di cui eseguire il commit avrà esito positivo e tutte le altre non andranno a buon fine con il commit. Queste altre transazioni possono quindi essere tentate di nuovo sui dati aggiornati. Tieni presente che questo limita il numero di scritture simultanee che puoi eseguire a qualsiasi entità di 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 venticinque gruppi di entità e avrà esito positivo purché nessuna transazione simultanea tocchi nessuno dei gruppi di entità a cui si applica. Questo ti offre una maggiore flessibilità nell'organizzazione dei dati, perché non sei costretto a inserire pezzi disparati sotto lo stesso predecessore solo per eseguire le scritture atomiche.

Come in una transazione di gruppo singolo, non puoi eseguire una query non predecessore in una transazione XG. Tuttavia, puoi eseguire query sui predecessori 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 nel datastore e visibilità dei dati. Tuttavia, per queste query non transazionali è più probabile che vengano visualizzati i risultati di una transazione XG con impegno parziale rispetto a quelli di una transazione a gruppo singolo con impegno parziale.

Una transazione XG che riguarda un solo gruppo di entità ha esattamente le stesse prestazioni e gli stessi costi di una transazione non XG su un singolo gruppo. In una transazione XG che interessa più gruppi di entità, le operazioni costano come se fossero eseguite in una transazione non XG, ma potrebbero riscontrare una latenza maggiore.

Scritture di 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 eventuali repliche in cui non sono stati registrati vengono contrassegnate come non contenenti log aggiornati.
  2. La fase Applica avviene in modo indipendente in ogni replica ed è composta da due azioni eseguite in parallelo:
    • I dati dell'entità sono scritti nella replica.
    • Le righe di indice dell'entità sono scritte in quella replica. Tieni presente che questa operazione può richiedere più tempo rispetto alla scrittura dei dati stessi.

L'operazione di scrittura ritorna immediatamente dopo la fase di commit e la fase di applicazione avviene in modo asincrono, possibilmente in momenti diversi di ogni replica, ed eventualmente con ritardi di qualche centinaio di millisecondi o più dal completamento della fase di commit. Se si verifica un errore durante la fase di commit, vengono eseguiti nuovi tentativi automatici; se l'errore persiste, Datastore restituisce un messaggio di errore ricevuto dall'applicazione come eccezione. Se la fase di commit ha esito positivo, ma si verifica un errore in una determinata replica, il comando Applica viene riportato in avanti fino al completamento nella replica quando si verifica una delle seguenti condizioni:

  • Le operazioni di sweep periodiche di Datastore verificano la presenza di job di commit non completati e li applicano.
  • Alcune operazioni (get, put, delete e query predecessori) che utilizzano il gruppo di entità interessato causano il completamento di eventuali modifiche di cui è stato eseguito il commit, 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 Commit e Apply:

  • 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é Datastore riceve e le query sui predecessori applicano qualsiasi modifica in sospeso alla replica su cui sono in esecuzione, 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 chiave) deve visualizzare la versione più recente dell'entità.
  • Le query non predecessori potrebbero restituire risultati inattivi perché potrebbero essere in esecuzione su una replica a cui non sono ancora state applicate le ultime transazioni. Questo può verificarsi anche se è stata eseguita un'operazione per la quale è garantito l'applicazione delle transazioni in sospeso, poiché la query potrebbe essere eseguita su una replica diversa rispetto all'operazione precedente.
  • La tempistica delle modifiche simultanee può influire sui risultati delle query dei non predecessori. Se un'entità inizialmente soddisfa una query, ma viene successivamente 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 Datastore

Datastore conserva le statistiche sui dati archiviati per un'applicazione, ad esempio quante entità sono presenti di un determinato tipo o quanto spazio viene utilizzato dai valori delle proprietà di un determinato tipo. Puoi visualizzare queste statistiche nella pagina Dashboard Datastore della console Google Cloud. Puoi anche utilizzare l'API Datastore per accedere a questi valori in modo programmatico dall'interno dell'applicazione eseguendo query su entità con nomi speciali. Consulta Statistiche Datastore in Java 8 per ulteriori informazioni.