API Datastore App Engine per servizi in bundle legacy

Nota: Gli sviluppatori che creano nuove applicazioni sono vivamente incoraggiati a utilizzare il Libreria client NDB, che offre numerosi vantaggi rispetto a questa libreria client, come la memorizzazione automatica nella cache delle entità tramite tramite Google Cloud CLI o tramite l'API Compute Engine. Se al momento utilizzi la libreria client DB precedente, leggi la guida alla migrazione da DB a NDB

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.

Entità

Gli oggetti in Datastore sono denominati 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 dei valori soddisfa i criteri di query. Ciò rende queste proprietà utili per i 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 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 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 del nome della chiave per l'entità.
  • Puoi chiedere a Datastore di assegnare 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 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 principale è un'entità radice. L'associazione tra un'entità e la relativa entità è permanente e non può essere modificato dopo la creazione dell'entità. Cloud Datastore non assegnerà mai lo stesso ID numerico a due entità con lo stesso elemento padre, oppure a due radici (quelle senza un elemento padre).

L'entità principale, l'entità principale dell'entità principale e così via in modo ricorsivo sono i suoi antenati; le sue entità figlio, le entità figlio delle entità figlio e così via sono i suoi discendenti. Un'entità principale e tutti i suoi discendenti appartengono allo 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 un'entità principale, il percorso dell'antenato è vuoto e la chiave è costituita unicamente dal tipo e dall'identificatore dell'entità:

[Person:GreatGrandpa]

Questo concetto è illustrato dal seguente diagramma:

Mostra la relazione tra entità base e quella secondaria
  entità nel gruppo di entità

Query e indici

Oltre a recuperare le entità da Datastore direttamente tramite chiave, un'applicazione può eseguire query per recuperarle in base ai valori delle loro proprietà. La query opera su entità di una data kind; può specificare filtri sulle entità valori di proprietà, chiavi e predecessori e può restituire zero più entità come risultati. Una query può anche specificare ordini di ordinamento per ordinare i risultati in base ai valori delle proprietà. I risultati includono tutte le entità che hanno almeno un valore (eventualmente 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à proiettate o solo le chiavi delle entità.

Una query tipica include quanto segue:

  • Un tipo di entità a cui si applica la query
  • Zero o più filtri basati su valori, chiavi e antenati delle proprietà delle entità
  • Zero o più ordinare per sequenziare i risultati
Quando viene eseguita, la query recupera tutte le entità del tipo specificato che soddisfano tutti i filtri specificati, ordinati nell'ordine specificato. Le query vengono eseguite in sola lettura.

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 di 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 sugli antenati restituiscono risultati molto coerenti, che sono garantiti aggiornati con le ultime modifiche ai dati. Le query non relative all'antenato, invece, possono interessare l'intero Datastore anziché un singolo gruppo di entità, ma sono solo coerenti in modo definitivo e potrebbero restituire risultati obsoleti. Se per la tua applicazione è importante l'elevata coerenza, potresti dover tenerne conto quando struttura i dati, posizionando entità correlate nello stesso gruppo di entità in modo che possano essere recuperate con una query predecessore anziché con una query non predecessore. consulta la sezione Strutturare i dati per una elevata coerenza per ulteriori informazioni.

App Engine predefinisce un indice semplice su 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 manualmente gli indici modificando il file prima di caricare l'applicazione.

Nota: il meccanismo di query basato su indici supporta una vasta gamma di query ed è adatto 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 illimitato di operazioni di questo tipo. Per mantenere la coerenza dei dati, la transazione assicura che tutte le operazioni in essa contenute vengano applicate a Datastore come un'unità o, se una delle operazioni non va a buon fine, che nessuna 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 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 le operazioni 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 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 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 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 antecedenti) possono vedere tutti, alcuni o nessuno dei risultati di una transazione impegnata in precedenza. Per informazioni generali su questo problema, consulta Scritture nel datastore e visibilità dei dati. 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 le stesse prestazioni e lo stesso costo di una transazione XG singola, non XG. 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 e visibilità dei dati di Datastore

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 tutte le repliche in cui non sono stati registrati sono contrassegnate come non aggiornate.
  2. La fase di applicazione si svolge in modo indipendente in ogni replica ed è composta da 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 della scrittura dei dati stessi.

L'operazione di scrittura restituisce immediatamente dopo la fase di commit e la fase di applicazione, quindi si svolge in modo asincrono, possibilmente in momenti diversi in ogni replica, e magari 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, vengono eseguiti 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 scansioni periodiche di Datastore verificano 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 di recupero e di individuazione dell'antenato di Datastore applicano eventuali modifiche in sospeso alla replica su cui vengono eseguite, queste operazioni mostrano sempre una visione coerente di tutte le transazioni riuscite precedenti. Ciò significa che è garantito che un'operazione get (la ricerca di un'entità aggiornata in base alla sua chiave) visualizzi la 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.
  • I tempi delle modifiche simultanee possono influire sui risultati delle query non predecessori. 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 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 per entità con nomi speciali. Per ulteriori informazioni, consulta Statistiche di Datastore in Python 2.

Esempio di Datastore per Python 2

Nell'API Python, un modello descrive un tipo di entità, inclusi i tipi e la configurazione delle relative proprietà. Un'applicazione definisce un modello utilizzando una classe Python, con attributi di classe che descrivono le proprietà. Il nome della classe diventa il nome del tipo di entità. Le entità del tipo specificato sono rappresentate da istanze della classe del modello, con attributi istanza che rappresentano i valori delle proprietà. Per creare una nuova entità, devi creare un oggetto della classe desiderata, impostarne gli attributi e quindi salvarlo (chiamando un metodo come put()):

import datetime
from google.appengine.ext import db
from google.appengine.api import users


class Employee(db.Model):
  name = db.StringProperty(required=True)
  role = db.StringProperty(required=True,
                           choices=set(["executive", "manager", "producer"]))
  hire_date = db.DateProperty()
  new_hire_training_completed = db.BooleanProperty(indexed=False)
  email = db.StringProperty()


e = Employee(name="John",
             role="manager",
             email=users.get_current_user().email())
e.hire_date = datetime.datetime.now().date()
e.put()

L'API Datastore fornisce due interfacce per le query: un'interfaccia di oggetti di query e un linguaggio di query simile a SQL chiamato GQL. Una query restituisce le entità sotto forma di istanze delle classi del modello:

training_registration_list = ["Alfred.Smith@example.com",
                              "jharrison@example.com",
                              "budnelson@example.com"]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE email IN :1",
                                training_registration_list)
for e in employees_trained:
  e.new_hire_training_completed = True
  db.put(e)