API de App Engine Datastore para servicios agrupados en paquetes heredados

En este documento, se describe el modelo de datos de los objetos almacenados en Datastore, la forma en que se estructuran las consultas mediante la API y la manera en que se procesan las transacciones. Para ver el contenido del paquete datastore, consulta la referencia del paquete datastore.

Entidades

Los objetos en Datastore se conocen como entidades. Una entidad posee una o más propiedades con nombre, que pueden tener uno o más valores. Los valores de propiedad pueden pertenecer a diversos tipos de datos, como números enteros, números de coma flotante, strings, fechas y datos binarios, entre otros. Una consulta sobre una propiedad con múltiples valores comprueba si alguno de estos cumple con los criterios de la consulta. Esto hace que tales propiedades sean útiles para las pruebas de membresía.

Tipos, identificadores y claves

Cada entidad de Datastore es de un tipo particular que clasifica la entidad para las consultas; por ejemplo, una aplicación de recursos humanos podría representar a cada empleado de una empresa mediante una entidad del tipo Employee. Además, cada entidad tiene su propia clave, que la identifica de forma única. La clave consta de los siguientes componentes:

  • El tipo de entidad
  • Un identificador, que puede ser de dos tipos:
    • una string de nombre de clave
    • un ID de número entero
  • Una ruta principal opcional que ubica la entidad dentro de la jerarquía de Datastore

El identificador se asigna cuando se crea la entidad. Ya que es parte de la clave de la entidad, el identificador se encuentra asociado de forma permanente con la entidad y no puede cambiarse. Se puede asignar de dos formas:

  • Tu aplicación puede especificar su propia string de nombre de clave para la entidad.
  • Puedes hacer que Datastore asigne de forma automática un ID de número entero a la entidad.

Asigna identificadores

El servidor del entorno de ejecución se puede configurar para generar ID automáticamente mediante dos políticas de identificación automática diferentes:

  • La política default genera una secuencia de ID que se distribuyen de manera casi uniforme. Cada ID puede tener hasta 16 dígitos.
  • La política legacy crea una secuencia de ID de números enteros no consecutivos más breves.

Si quieres mostrar al usuario los ID de la entidad o depender de que los soliciten, lo mejor que puedes hacer es usar una asignación manual.

Rutas de principales

Las entidades de Cloud Datastore conforman un espacio con estructura jerárquica, similar a la estructura de directorios de un sistema de archivos. Cuando creas una entidad, tienes la opción de designar otra entidad como superior, en cuyo caso la nueva entidad es secundaria respecto de la entidad superior (ten en cuenta que, a diferencia de lo que ocurre en un sistema de archivos, no es necesario que efectivamente exista una entidad superior). Las entidades que no tienen una principal se denominan entidades raíz. La asociación entre una entidad secundaria y la entidad superior es permanente, y no puede cambiarse una vez creada la entidad. Cloud Datastore nunca asignará el mismo ID numérico a dos entidades asociadas a la misma entidad superior ni a dos entidades raíz (no asociadas a una entidad superior).

La entidad superior de una entidad determinada, la superior de la superior y así sucesivamente son sus principales. Las entidades secundarias respecto de una entidad determinada, las secundarias de las secundarias y así sucesivamente son sus descendientes. Una entidad raíz y todos sus descendientes pertenecen al mismo grupo de entidad. La secuencia de entidades que comienza con una entidad raíz y sigue de principal a secundaria, hasta llegar a una entidad determinada, constituye la ruta de principal de esa entidad. La clave completa que identifica una entidad consta de una secuencia de pares de tipo-identificador que especifican la ruta de principales y terminan con el tipo y el identificador de la propia entidad:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

En el caso de una entidad raíz, la ruta de principales está vacía, y la clave consta solo del tipo y el identificador de la entidad:

[Person:GreatGrandpa]

Este concepto se ilustra en el diagrama siguiente:

Muestra la relación de la entidad raíz con las entidades secundarias del grupo de entidades

Índices y consultas

Además de recuperar entidades desde Datastore directamente mediante sus claves, una aplicación puede realizar una consulta para recuperarlas mediante los valores de sus propiedades. La consulta opera en entidades de un determinado tipo; puede especificar filtros en los valores de propiedad, las claves y los principales de las entidades, y puede mostrar cero o más entidades como resultados. Una consulta también puede especificar órdenes de clasificación para secuenciar los resultados según los valores de propiedad. Los resultados incluyen todas las entidades que tienen al menos un valor por cada propiedad mencionada en los filtros y órdenes de clasificación, y cuyos valores de propiedad cumplen con todos los criterios de filtro especificados. La consulta puede devolver entidades completas, entidades proyectadas, o solo claves de entidades.

Una consulta típica incluye lo siguiente:

Cuando se ejecuta una consulta, se recuperan todas las entidades de ese tipo que cumplen con todos los filtros indicados, en el orden especificado. Las consultas se ejecutan en modo de solo lectura.

Nota: Para conservar la memoria y mejorar el rendimiento, las consultas deben especificar, siempre que sea posible, un límite en la cantidad de resultados que se muestran.

Una consulta también puede incluir un filtro de principal que limite los resultados solo al grupo de entidad que desciende de un principal específico. Una consulta de este tipo se conoce como consulta principal. De forma predeterminada, las consultas principales muestran resultados con coherencia sólida y se garantiza que están actualizados con los últimos cambios en los datos. Las consultas no principales, por el contrario, pueden abarcar todo Datastore, en lugar de un solo grupo de entidades, pero solo tienen coherencia eventual y pueden mostrar resultados obsoletos. Si la coherencia sólida es importante para tu aplicación, es posible que debas tener esto en cuenta cuando estructures tus datos. Coloca las entidades relacionadas en el mismo grupo para que puedan recuperarse con una consulta principal, en lugar de una consulta no principal que solicite más información.

App Engine predefine un índice simple en cada propiedad de una entidad. Una aplicación de App Engine puede definir más índices personalizados en un archivo de configuración de índice llamado index.yaml. El servidor de desarrollo agrega sugerencias a este archivo de forma automática cuando encuentra consultas que no pueden ejecutarse con los índices existentes. Para ajustar los índices de forma manual, edita el archivo antes de subir la aplicación.

Nota: El mecanismo de consulta basado en índices admite un rango amplio de consultas y es apto para la mayoría de las aplicaciones. Sin embargo, no admite algunos tipos de consultas comunes en otras tecnologías de bases de datos. En particular, las uniones y consultas agregadas no son compatibles con el motor de consultas de Datastore. Visita Consultas de Datastore para conocer sus limitaciones.

Transacciones

Cada intento de insertar, actualizar o borrar una entidad ocurre en el contexto de una transacción. Una transacción puede incluir una cantidad indeterminada de las operaciones mencionadas. Para conservar la coherencia de los datos, la transacción garantiza que todas las operaciones que contiene se apliquen a Datastore como una unidad o, si alguna de ellas falla, que no se aplique ninguna.

Puedes realizar varias acciones en una entidad con una sola transacción. Por ejemplo, para incrementar un campo de contador en un objeto, tienes que leer el valor del contador, calcular el valor nuevo y luego almacenarlo. Si no se hace todo en una transacción, es posible que otro proceso aumente el valor del contador entre el momento en que tú lo lees y el momento en que lo actualizas, lo que haría que tu aplicación sobrescriba el valor actualizado. Realizar la lectura, el cálculo y la escritura en una sola transacción garantiza que ningún otro proceso pueda interferir en el incremento.

Transacciones y grupos de entidad

Solo se permiten consultas principales dentro de una transacción. Esto quiere decir que cada consulta transaccional debe limitarse a un solo grupo de entidad. La transacción puede aplicarse a varias entidades, que pueden pertenecer a un mismo grupo o, en el caso de una transacción entre grupos, a un máximo de veinticinco grupos de entidades distintos.

En Datastore, se usa la simultaneidad optimista para administrar transacciones. Cuando dos o más transacciones intentan cambiar el mismo grupo de entidad a la vez (ya sea para actualizar las entidades existentes o crear otras nuevas), la primera transacción que se confirme tendrá éxito y ninguna de las otras podrá confirmarse. Estas otras transacciones se pueden volver a intentar con los datos actualizados. Ten en cuenta que esto limita la cantidad de escrituras simultáneas que pueden aplicarse a una entidad específica de un grupo determinado.

Transacciones entre grupos

Cuando una transacción afecta a entidades pertenecientes a diferentes grupos, se la denomina transacción entre grupos (XG). La transacción se puede aplicar en un máximo de veinticinco grupos de entidad y tendrá éxito siempre que ninguna transacción simultánea afecte a alguno de los grupos de entidades a los que se aplica. Esto te brinda más flexibilidad para organizar tus datos, ya que no estás obligado a colocar distintos tipos de datos debajo del mismo principal solo para realizar escrituras atómicas en ellos.

Al igual que en una transacción de un solo grupo, no puedes realizar una consulta no principal en una transacción XG. Sin embargo, puedes realizar consultas principales en grupos de entidad separados. Las consultas no transaccionales (no principales) pueden ver todos los resultados de una transacción confirmada con anterioridad, algunos de estos o ninguno (para obtener más información sobre este problema, consulta Operaciones de escritura de Datastore y visibilidad de datos). Sin embargo, es más probable que esas consultas no transaccionales vean los resultados de una transacción XG confirmada de forma parcial que los resultados de una transacción de un solo grupo confirmada de forma parcial.

Una transacción XG que solo afecta a un único grupo de entidad tiene exactamente el mismo rendimiento y costo que una transacción de un solo grupo, no XG. En una transacción XG que alcanza a varios grupos de entidad, las operaciones cuestan lo mismo que si se realizaran en una transacción que no es XG, pero pueden experimentar una latencia mayor.

Operaciones de escritura en Datastore y visibilidad de datos

Los datos se escriben en Datastore en dos fases:

  1. En la fase de confirmación, los datos de la entidad se agregan a los registros de transacciones de la mayoría de las réplicas (y se agrega una marca a las otras réplicas para indicar que sus registros no están actualizados).
  2. La fase de aplicación se produce de forma independiente en cada réplica y consta de dos acciones realizadas en paralelo:
    • Los datos de la entidad se escriben en esa réplica.
    • Las filas del índice de la entidad se escriben en esa réplica. Ten en cuenta que esto puede llevar más tiempo que escribir los datos en sí.

La operación de escritura se muestra de inmediato después de la fase de confirmación, y la fase de aplicación se realiza de forma asíncrona, posiblemente en diferentes momentos en cada réplica y con retrasos de unos pocos cientos de milisegundos o más tras la finalización de la fase de confirmación. Si se produce un error durante la fase de confirmación, se realizan reintentos automáticos. Pero si las fallas continúan, Datastore muestra un mensaje de error que la aplicación recibe como excepción. Si la fase de confirmación tiene éxito, pero la de aplicación falla en una réplica en particular, la aplicación se avanza hasta su finalización en esa réplica cuando ocurre alguna de las siguientes situaciones:

  • Los barridos periódicos de Datastore comprueban si hay trabajos de confirmación incompletos y los aplicas.
  • Ciertas operaciones (get, put, delete y consultas principales) que usan el grupo de entidad afectado provocan que los cambios confirmados, pero que aún no se aplicaron, se completen en la réplica en la que se ejecutan antes de proceder con la operación nueva.

Este comportamiento de escritura puede tener varias implicaciones sobre cómo y cuándo los datos son visibles para tu aplicación en diferentes partes de las fases de confirmación y aplicación:

  • Si una operación de escritura informa un error de tiempo de espera, no se puede determinar (sin intentar leer los datos) si la operación se realizó con éxito o no.
  • Debido a que Datastore obtiene las modificaciones pendientes, y las consultas principales las aplican a la réplica en la que se ejecutan, estas operaciones siempre ven una vista coherente de todas las transacciones anteriores exitosas. Esto significa que una operación get (la búsqueda de una entidad actualizada por su clave) garantiza que se vea la versión más reciente de esa entidad.
  • Las consultas no principales pueden mostrar resultados obsoletos, ya que pueden estar ejecutándose en una réplica en la que aún no se han aplicado las transacciones más recientes. Esto puede ocurrir incluso si ya se realizó una operación que garantiza la aplicación de las transacciones pendientes, ya que la consulta puede ejecutarse en una réplica diferente de la que se usó en la operación anterior.
  • El momento en que se hacen los cambios simultáneos puede afectar los resultados de las consultas no principales. Si una entidad satisface en primera instancia una consulta, pero luego se modifica de modo tal que ya no lo hace, la entidad puede incluirse de todos modos en el conjunto de resultados si todavía no se aplicaron los cambios a los índices de la réplica en la que se ejecutó la consulta.

Estadísticas de Datastore

Datastore conserva estadísticas sobre los datos almacenados para una aplicación, como cuántas entidades hay de un tipo determinado o cuánto espacio usan los valores de propiedad de un tipo determinado. Puedes ver estas estadísticas en la página Panel de Datastore de la consola de Google Cloud. También puedes usar la API de Datastore a fin de acceder a estos valores de manera programática desde la aplicación; para ello, consulta las entidades con nombres especiales. Consulta Estadísticas de Datastore en Go 1.11 para obtener más información.

Ejemplo de Datastore en Go 1.11

En Go, las entidades de Datastore se crean a partir de valores struct. Los campos de la estructura se convierten en propiedades de la entidad. Para crear una entidad nueva, configura el valor que deseas almacenar, crea una clave y pasa ambos a datastore.Put(). Para actualizar una entidad existente, solo debes hacer otra Put() mediante la misma clave. Si deseas recuperar una entidad de Datastore, primero, debes configurar un valor al que se deserializará la entidad y, luego, pasarle una clave y un puntero que indique la ubicación de ese valor a datastore.Get().

En este ejemplo, se muestra cómo almacenar y recuperar algunos datos de Datastore:

import (
	"fmt"
	"net/http"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/user"
)

type Employee struct {
	Name     string
	Role     string
	HireDate time.Time
	Account  string
}

func handle(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	e1 := Employee{
		Name:     "Joe Citizen",
		Role:     "Manager",
		HireDate: time.Now(),
		Account:  user.Current(ctx).String(),
	}

	key, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "employee", nil), &e1)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var e2 Employee
	if err = datastore.Get(ctx, key, &e2); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Stored and retrieved the Employee named %q", e2.Name)
}

Para obtener más información, consulta la referencia de Datastore.