Strutturazione dei dati per una coerenza elevata

Datastore offre disponibilità elevata, scalabilità e durabilità tramite la distribuzione di dati su più macchine e l'utilizzo della replica sincrona su un'ampia area geografica. Tuttavia, questa struttura presenta un compromesso: la velocità effettiva di scrittura per ogni singolo gruppo di entità è limitata a circa un commit al secondo e sono previste limitazioni per le query o le transazioni che riguardano 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 elevata coerenza, pur soddisfacendo i requisiti di velocità effettiva di scrittura dell'applicazione.

Le letture altamente coerenti restituiscono sempre i dati correnti e, se eseguite in una transazione, sembreranno provenire da un unico snapshot coerente. Tuttavia, le query devono specificare un filtro dei predecessori affinché siano altamente coerenti o partecipino a una transazione e le transazioni possono coinvolgere al massimo 25 gruppi di entità. Le letture alla fine coerenti non presentano queste limitazioni e sono adeguate in molti casi. L'utilizzo di letture coerenti alla fine può consentirti di distribuire i dati tra un numero maggiore di gruppi di entità, consentendoti di ottenere una velocità effettiva di scrittura maggiore eseguendo i commit in parallelo sui diversi gruppi di entità. Tuttavia, devi comprendere le caratteristiche delle letture finali costantemente coerenti per determinare se sono adatte alla tua applicazione:

  • I risultati di queste letture potrebbero non riflettere le ultime transazioni. Questo può accadere perché queste letture non garantiscono che la replica su cui sono in esecuzione sia aggiornata. ma utilizzano qualsiasi dato disponibile nella replica al momento dell'esecuzione della query. La latenza di replica è quasi sempre meno di pochi secondi.
  • È possibile che una transazione impegnata che interessa più entità sia stata applicata ad alcune di queste e non ad altre. Tieni presente, tuttavia, che una transazione non sembra mai essere stata 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 ed escludere entità che avrebbero dovuto essere incluse. Questo può accadere perché gli indici potrebbero essere letti con una versione diversa rispetto a quella in cui viene letta l'entità stessa.

Per capire come strutturare i dati per un'elevata coerenza, confronta due approcci diversi per una semplice applicazione guestbook. Il primo approccio crea una nuova entità base 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;
}

Quindi esegue 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 predecessore, la replica utilizzata per eseguire la query in questo schema potrebbe non aver visto il nuovo saluto nel momento in cui viene eseguita la query. Tuttavia, quasi tutte le scritture saranno disponibili per le query non ai antenati entro pochi secondi dal commit. Per molte applicazioni, una soluzione che fornisca i risultati di una query non predecessore nel contesto delle modifiche dell'utente corrente in genere è sufficiente per rendere completamente accettabili queste latenze di replica.

Se l'elevata coerenza è importante per la tua applicazione, un approccio alternativo è scrivere entità con un percorso predecessore che identifica la stessa entità base in tutte le entità che devono essere lette in un'unica query predecessore altamente 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 con elevata coerenza all'interno del gruppo di entità identificato dall'entità base 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 consente di raggiungere una elevata coerenza scrivendo in 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 presenti un utilizzo più elevato in scrittura, potresti dover prendere in considerazione l'uso di altri mezzi: ad esempio, potresti inserire i post recenti in una memcache con una scadenza e visualizzare un mix di post recenti di memcache e Datastore oppure memorizzarli nella cache in un cookie, inserire uno stato nell'URL o qualcos'altro di completamente. L'obiettivo è trovare una soluzione di memorizzazione nella cache che fornisca i dati dell'utente corrente per il periodo di tempo in cui l'utente pubblica sulla tua applicazione. Ricorda, se esegui un get, una query dei predecessori o qualsiasi operazione all'interno di una transazione, vedrai sempre i dati scritti più di recente.