Estructura los datos para lograr una coherencia sólida

Datastore proporciona alta disponibilidad, escalabilidad y durabilidad mediante la distribución de datos en muchas máquinas y el uso de una replicación síncrona en un área geográfica amplia. Sin embargo, este diseño tiene la desventaja de que la capacidad de procesamiento de las operaciones de escritura de cualquier grupo de entidades se limita a alrededor de una confirmación por segundo. También hay limitaciones para las consultas o las transacciones que pueden alcanzar varios grupos de entidades. En esta página, se describen esas limitaciones en mayor profundidad y se analizan las recomendaciones para estructurar los datos a fin de lograr una coherencia sólida y cumplir con los requisitos de rendimiento de escritura de la aplicación.

Las lecturas de coherencia sólida siempre muestran datos actuales y, si se realizan dentro de una transacción, parecerán provenir de una única instantánea coherente. Sin embargo, las consultas deben especificar un filtro principal para tener coherencia sólida o participar en una transacción, y las transacciones pueden involucrar un máximo de 25 grupos de entidades. Las lecturas con coherencia eventual no tienen esas limitaciones y son adecuadas en muchos casos. Utilizar lecturas con coherencia eventual puede permitirte distribuir tus datos entre más grupos de entidades, lo que hace posible obtener una mayor capacidad de procesamiento de escritura a través de la ejecución de confirmaciones en paralelo en los distintos grupos de entidades. Sin embargo, debes comprender las características de las lecturas con coherencia eventual a fin de determinar si son adecuadas para tu aplicación:

  • Los resultados de esas lecturas pueden no reflejar las transacciones más actuales. Esto puede suceder porque esas lecturas no se aseguran de que la réplica sobre la que se ejecutan esté actualizada. En cambio, utilizan cualquier dato disponible en esa réplica cuando se ejecuta la consulta. La latencia de replicación es casi siempre inferior a unos pocos segundos.
  • Puede parecer que una transacción confirmada en múltiples entidades se aplicó solo a algunas de ellas. Sin embargo, nunca parecerá que una transacción se aplicó de manera parcial dentro de una sola entidad.
  • Los resultados de una consulta pueden contener entidades que no deberían haberse incluido según los criterios del filtro y es posible que se excluyan entidades que sí deberían haberse incluido. Esto puede ocurrir porque los índices pueden leerse en una versión diferente de la que se lee en la entidad.

Si deseas comprender cómo estructurar tus datos para una coherencia sólida, compara dos enfoques diferentes correspondientes a una aplicación de libro de visitas simple. Con el primer enfoque, se crea una entidad raíz nueva para cada entidad creada:

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;
}

A continuación, consulta la categoría de entidad Greeting para obtener los diez saludos más recientes.

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));
}

Sin embargo, debido a que usas una consulta no principal, es posible que la réplica utilizada para realizar la consulta en este esquema no haya visto el nuevo saludo en el momento en que se ejecuta la consulta. No obstante, casi todas las escrituras estarán disponibles para consultas no principales pocos segundos después de su confirmación. En el caso de muchas aplicaciones, una solución que proporcione los resultados de una consulta no principal en el contexto de los cambios propios del usuario actual será generalmente suficiente para hacer que las latencias de replicación sean completamente aceptables.

Si es importante que tu aplicación tenga coherencia sólida, un enfoque alternativo es escribir entidades con una ruta de acceso principal que identifique la misma entidad raíz en todas las entidades que deben leerse en una consulta principal única con coherencia sólida:

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;
}

Luego podrás realizar una consulta principal con coherencia sólida dentro del grupo de entidades que identifica la entidad raíz común.

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));
}

Con este enfoque, se logra una coherencia sólida por medio de la escritura en un solo grupo de entidades por libro de visitas, pero también se limitan los cambios al libro de visitas a no más de 1 escritura por segundo (el límite admitido para los grupos de entidades). Si es probable que la aplicación tenga un uso más intenso de la escritura, podría ser conveniente que uses otros medios: por ejemplo, puedes colocar publicaciones recientes en un Memcache con vencimiento y mostrar una mezcla de publicaciones recientes de Memcache y Datastore, o podrías almacenarlas en caché en una cookie, poner algún estado en la URL, o una opción completamente distinta. El objetivo es encontrar una solución de almacenamiento en caché que le brinde datos al usuario actual durante el período en el que el usuario publica en tu aplicación. Recuerda que, si realizas una operación get, una consulta principal o cualquier operación dentro de una transacción, siempre verás los datos escritos más recientes.