API App Engine Datastore per i servizi in bundle legacy

Nota: gli sviluppatori che creano nuove applicazioni sono vivamente incoraggiati a utilizzare la libreria client NDB, che offre diversi vantaggi rispetto a questa libreria client, ad esempio la memorizzazione nella cache automatica delle entità tramite l'API Memcache. Se al momento utilizzi la libreria client DB precedente, leggi la guida alla migrazione da DB a NDB

Questo documento descrive il modello dei 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 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 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 è 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 definitivamente all'entità e non può essere modificato. Può essere assegnata 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.

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 optionally designare un'altra entità come principale; la nuova entità è secondaria dell'entità padre (tieni presente che, a differenza di un file system, l'entità principale non deve necessariamente 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à principali (quelle senza un elemento principale).

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à base e tutti i suoi discendenti appartengono allo stesso gruppo di entità. La sequenza di entità che inizia con un'entità principale e procede da una entità principale a una secondaria, fino a una determinata entità, costituisce il percorso degli antenati di quell'entità. La chiave completa che identifica l'entità è costituita da una sequenza di coppie di tipo-identificatore che specificano il percorso dell'antenato e termina con quelli dell'entità stessa:

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

Per unentità base, 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 l'entità base e le entità figlie 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 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:

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 come di sola lettura.

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

Una query può includere anche un filtro antenato che limita i risultati solo al gruppo di entità discendente da un antenato specificato. Questa query è nota come query sull'antenato. 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 la elevata coerenza è 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 una query sull'antenato anziché con una query non sull'antenato. Per ulteriori informazioni, consulta Strutturare i dati per la coerenza forte.

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 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 la pagina Query Datastore per informazioni sui 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 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'altra procedura 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 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 gruppi di entità diversi è 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. In questo modo, hai una maggiore flessibilità nell'organizzazione dei dati, perché non sei costretto a inserire insieme elementi eterogenei nello stesso antenato solo per eseguire scritture atomiche.

Come in una transazione a gruppo singolo, non puoi eseguire una query non sull'antenato in una transazione XG. Tuttavia, puoi eseguire query sugli antenati su gruppi di entità distinti. 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 maggiori probabilità di mostrare i risultati di una transazione XG parzialmente impegnata rispetto a quelli di una transazione a gruppo singolo parzialmente impegnata.

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à, il costo delle operazioni è lo stesso che se fossero eseguite in una transazione non XG, ma la latenza potrebbe essere superiore.

Scritture in Datastore e visibilità dei dati

I dati vengono scritti in Datastore in due fasi:

  1. 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.
  2. 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 della 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, 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 va a buon fine, ma l'applicazione non riesce in una determinata replica, l'applicazione viene eseguita fino al completamento in quella 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.
  • Determinate 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 parti diverse 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 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 antecedenti potrebbero restituire risultati non aggiornati perché potrebbero essere eseguite su una replica su 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 antecedenti. Se un'entità soddisfa inizialmente una query, ma in un secondo momento viene modificata in modo da non soddisfarla più, l'entità potrebbe essere comunque 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 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 nella pagina Dashboard di 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 sulle 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 poi 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 oggetto query e un linguaggio di query simile a SQL chiamato GQL. Una query restituisce 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)