Strutturazione dei dati per una coerenza elevata

Datastore offre alta disponibilità, scalabilità e durabilità distribuendo i dati su molti computer e utilizzando la replica sincrona su un'ampia area geografica. Tuttavia, questo design comporta un compromesso: il throughput di scrittura per ogni singolo gruppo di entità è limitato a circa un commit al secondo e ci sono limitazioni per le query o le transazioni che coprono 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 consistenza rigorosa, soddisfacendo al contempo i requisiti di throughput in scrittura dell'applicazione.

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

  • I risultati di queste letture potrebbero non riflettere le transazioni più recenti. Questo accade perché queste letture non garantiscono che la replica su cui vengono eseguite sia aggiornata. Utilizzano invece i dati disponibili sulla replica al momento dell'esecuzione della query. La latenza della replica è quasi sempre inferiore a qualche secondo.
  • Una transazione impegnata che ha interessato più entità potrebbe sembrare essere stata applicata solo ad alcune entità e non ad altre. Tieni però presente che una transazione non sembrerà mai essere stata applicata parzialmente all'interno di una singola entità.
  • I risultati della query possono includere entità che non dovevano essere incluse in base ai criteri di filtro ed escludere entità che dovevano essere incluse. Questo può verificarsi perché gli indici potrebbero essere letti in una versione diversa rispetto a 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 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;
}

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 relativa all'antenato, 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 le query non relative agli antenati entro pochi secondi dal commit. Per molte applicazioni, in genere è sufficiente una soluzione che fornisca i risultati di una query non principale nel contesto delle modifiche dell'utente corrente 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 dell'antenato che identifichi la stessa entità base in tutte le entità che devono essere lette in una singola query sull'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'antenato fortemente coerente 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 garantisce elevata coerenza scrivendo in un singolo gruppo di entità per guestbook, ma limita anche le modifiche al guestbook a non più di 1 scrittura al secondo (il limite supportato per i gruppi di entità). Se la tua applicazione è probabile che abbia un utilizzo più elevato delle scritture, potresti dover prendere in considerazione l'utilizzo di altri mezzi: ad esempio, potresti inserire i post recenti in un memcache con una scadenza e mostrare un mix di post recenti da memcache e Datastore oppure puoi memorizzarli nella cache in un cookie, inserire un po' di stato nell'URL o qualcos'altro. 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 nella tua applicazione. Ricorda che, se esegui un comando GET, una query sull'antenato o qualsiasi operazione all'interno di una transazione, vedrai sempre i dati scritti più di recente.