Como estruturar dados para ter consistência forte

Observação: os desenvolvedores que criam novos aplicativos são bastante incentivados a usar a biblioteca de cliente do NDB, que oferece diversos benefícios em comparação a esta biblioteca de cliente, como armazenamento em cache automático de entidades por meio da API Memcache. Se você estiver usando a antiga biblioteca de cliente do DB, leia o Guia de migração de DB para NDB.

O Cloud Datastore oferece alta disponibilidade, escalabilidade e durabilidade na distribuição de dados em muitas máquinas e no uso de replicação síncrona sem mestre em uma ampla área geográfica. No entanto, a desvantagem desse design é que a capacidade de gravação de qualquer grupo de entidades está limitada a cerca de uma confirmação por segundo. Há também limitações relacionadas às consultas ou transações que abrangem diversos grupos. Nesta página, mostramos essas limitações com detalhes e discutimos as práticas recomendadas para que os dados sejam compatíveis com uma consistência forte e ainda atendam aos requisitos de capacidade de gravação do aplicativo.

As leituras de consistência forte sempre retornam dados atuais e quando realizadas dentro de uma transação, surgem provenientes de um instantâneo único e consistente. No entanto, as consultas precisam especificar um filtro ancestral de consistência forte ou participar de uma transação e estas envolvem no máximo 25 grupos de entidades. As leituras de consistência eventual não têm essas limitações e são adequadas em muitos casos. O uso de leituras de consistência eventual permite a distribuição dos dados entre um número maior de grupos de entidades e faz com que você receba maior capacidade de gravação, executando commits em paralelo nos diferentes grupos de entidades. No entanto, é preciso entender as características das leituras de consistência eventual para determinar se elas são adequadas ao aplicativo:

  • Os resultados dessas leituras talvez não reflitam as transações mais recentes. Isso pode ocorrer porque essas leituras não garantem que a réplica na qual elas estão sendo executadas está atualizada. Em vez disso, elas usam os dados disponíveis na réplica no momento da execução da consulta. A latência da replicação é quase sempre inferior a alguns segundos.
  • Uma transação commit que abrange diversas entidades talvez tenha sido aplicada em algumas entidades e não em outras. Observe, no entanto, que uma transação nunca parecerá ter sido parcialmente aplicada em uma única entidade.
  • Os resultados da consulta incluem entidades que não atendem aos critérios de filtro e excluem entidades que atendem a esses mesmos critérios. Isso ocorre porque os índices podem ser lidos em uma versão diferente da que a própria entidade é lida.

Para entender como estruturar os dados para uma consistência forte, compare duas abordagens diferentes no exercício tutorial de Guestbook do App Engine. A primeira abordagem cria uma nova entidade raiz para cada saudação:

Java 7

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;
}
import webapp2
from google.appengine.ext import db

class Guestbook(webapp2.RequestHandler):
  def post(self):
    greeting = Greeting()
    ...

Em seguida, consulta o tipo de entidade Greeting para as dez saudações mais recentes.

import webapp2
from google.appengine.ext import db

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    greetings = db.GqlQuery("SELECT * "
                            "FROM Greeting "
                            "ORDER BY date DESC LIMIT 10")

No entanto, por usar uma consulta não ancestral, a réplica utilizada para executá-la neste esquema talvez não tenha visto a nova saudação no momento da execução. No entanto, quase todas as gravações estarão disponíveis para consultas não ancestrais dentro de alguns segundos do commit. Para muitos aplicativos, normalmente uma solução que fornece os resultados de uma consulta não ancestral no contexto das próprias alterações do usuário atual será suficiente para tornar essas latências de replicação completamente aceitáveis.

Caso a consistência forte seja importante para o aplicativo, uma abordagem alternativa é gravar entidades com um caminho ancestral que identifique a mesma entidade raiz em todas as entidades a serem lidas em uma única consulta de ancestral de consistência forte:

import webapp2
from google.appengine.ext import db

class Guestbook(webapp2.RequestHandler):
  def post(self):
    guestbook_name=self.request.get('guestbook_name')
    greeting = Greeting(parent=guestbook_key(guestbook_name))
    ...

Será possível executar uma consulta de ancestral de consistência forte no grupo de entidades identificado pela entidade raiz comum:

import webapp2
from google.appengine.ext import db

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    guestbook_name=self.request.get('guestbook_name')

    greetings = db.GqlQuery("SELECT * "
                            "FROM Greeting "
                            "WHERE ANCESTOR IS :1 "
                            "ORDER BY date DESC LIMIT 10",
                            guestbook_key(guestbook_name))

Esta abordagem atinge uma consistência forte na gravação para um único grupo de entidades por livro de visitas, mas também limita as alterações nele para não mais do que 1 gravação por segundo, limite suportado para grupos de entidades. Caso o aplicativo esteja propenso a encontrar um uso de gravação intensivo, convém levar em consideração o uso de outros meios. Por exemplo, colocar postagens recentes em um memcache com uma expiração e exibir uma mistura de postagens recentes do memcache e do Cloud Datastore, ou armazená-las em um cookie, colocar algum estado no URL ou algo totalmente diferente. O objetivo é encontrar uma solução de armazenamento em cache que forneça os dados para o usuário atual no período de tempo em que o usuário estiver postando no aplicativo. Se executar o comando "get", uma consulta de ancestral ou qualquer operação em uma transação, você sempre verá os dados gravados mais recentemente.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Python 2