Strutturazione dei dati per una coerenza elevata

Datastore offre alta disponibilità, scalabilità e durabilità distribuendo i dati su più macchine e utilizzando la replica sincrona su un'ampia area geografica. Tuttavia, questo design presenta un compromesso: la velocità effettiva di scrittura per qualsiasi singolo gruppo di entità è limitata a circa un commit al secondo e ci sono limitazioni per le query o le transazioni che interessano più gruppi di entità. Questa pagina descrive queste limitazioni in modo più dettagliato e illustra le best practice per strutturare i dati in modo da supportare una coerenza elevata, soddisfacendo al contempo i requisiti di velocità effettiva di scrittura dell'applicazione.

Le letture a elevata coerenza restituiscono sempre i dati correnti e, se eseguite all'interno di una transazione, sembreranno provenire da un'unica istantanea coerente. Tuttavia, le query devono specificare un filtro di antenati per essere fortemente coerenti o partecipare a una transazione e le transazioni possono coinvolgere al massimo 25 gruppi di entità. Le letture con coerenza finale non presentano queste limitazioni e sono adeguate in molti casi. L'utilizzo di letture coerenti alla fine può consentire di distribuire i dati tra un numero maggiore di gruppi di entità, consentendoti di ottenere un maggiore throughput di scrittura eseguendo commit in parallelo sui diversi gruppi di entità. Tuttavia, devi comprendere le caratteristiche delle letture alla fine coerenti per determinare se sono adatte alla tua applicazione:

  • I risultati di queste letture potrebbero non riflettere le transazioni più recenti. Ciò può verificarsi perché queste letture non garantiscono che la replica su cui vengono eseguite sia aggiornata. Utilizzano invece i dati disponibili nella replica al momento dell'esecuzione della query. La latenza di replica è quasi sempre inferiore a pochi secondi.
  • Una transazione confermata che ha interessato più entità potrebbe sembrare essere stata applicata ad alcune entità e non ad altre. Tieni presente, tuttavia, che una transazione non verrà mai visualizzata come applicata parzialmente all'interno di una singola entità.
  • I risultati della query possono includere entità che non avrebbero dovuto essere incluse in base ai criteri di filtro e potrebbero escludere entità che avrebbero dovuto essere incluse. Ciò può verificarsi perché gli indici potrebbero essere letti a una versione diversa da quella dell'entità stessa.

Per capire come strutturare i dati per una elevata coerenza, confronta due approcci diversi per una semplice applicazione guestbook. Il primo approccio crea una nuovaentità basee per ogni entità creata:

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // No parent key specified, so Greeting is a root entity.
  Entity greeting = new Entity("Greeting");
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

Esegue quindi una query sul tipo di entità Greeting per i dieci saluti più recenti.

protected List<Entity> listGreetingEntities(DatastoreService datastore) {
  Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING);
  return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));
}

Tuttavia, poiché utilizzi una query non discendente, la replica utilizzata per eseguire la query in questo schema potrebbe non aver visto il nuovo saluto al momento dell'esecuzione della query. Tuttavia, quasi tutte le scritture saranno disponibili per query non discendenti entro pochi secondi dal commit. Per molte applicazioni, una soluzione che fornisce i risultati di una query non discendente nel contesto delle modifiche dell'utente corrente sarà in genere sufficiente a rendere completamente accettabili queste latenze di replica.

Se la elevata coerenza è importante per la tua applicazione, un approccio alternativo è scrivere entità con un percorso antenato che identifichi la stessa entità base in tutte le entità che devono essere lette in una singola query antenato fortemente coerente:

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor).
  Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);

  // Place greeting in the same entity group as guestbook.
  Entity greeting = new Entity("Greeting", guestbookKey);
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

Potrai quindi eseguire una query da predecessore fortemente coerente all'interno del gruppo di entità identificato dall&#39entità basee comune:

protected List<Entity> listGreetingEntities(DatastoreService datastore) {
  Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);
  Query query =
      new Query("Greeting", guestbookKey)
          .setAncestor(guestbookKey)
          .addSort("date", Query.SortDirection.DESCENDING);
  return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));
}

Questo approccio garantisce una elevata coerenza scrivendo a un singolo gruppo di entità per guestbook, ma limita anche le modifiche al guestbook a non più di una scrittura al secondo (il limite supportato per i gruppi di entità). Se è probabile che la tua applicazione incontri un utilizzo di scrittura più intenso, potresti dover prendere in considerazione l'utilizzo di altri mezzi: ad esempio, potresti inserire i post recenti in una memcache con una scadenza e visualizzare un mix di post recenti dalla memcache e Datastore oppure potresti memorizzarli nella cache in un cookie, inserire uno stato nell'URL o altro ancora. L'obiettivo è trovare una soluzione di memorizzazione nella cache che fornisca i dati per l'utente corrente per il periodo di tempo in cui l'utente pubblica contenuti nella tua applicazione. Ricorda che se esegui un'operazione get, una query ancestor o qualsiasi operazione all'interno di una transazione, vedrai sempre i dati scritti più di recente.