Índices de Cloud Datastore

App Engine predefine un índice simple en cada propiedad de una entidad. Una aplicación de App Engine puede definir otros índices personalizados en un archivo de configuración de índices llamado datastore-indexes.xml, que se genera en el directorio /war/WEB-INF/appengine-generated de tu aplicación. 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.

Para obtener un análisis más profundo de los índices y las consultas, accede a Selección de índices y búsqueda avanzada.

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, este mecanismo no admite algunos tipos de consulta que son comunes en otras tecnologías de base de datos. En particular, el motor de consultas de Cloud Datastore no admite las operaciones de unión o agregación. Para conocer las limitaciones de las consultas de Cloud Datastore, visita esta página.

Definición y estructura de los índices

Un índice se define en una lista de propiedades de un tipo de entidad determinada, con un orden correspondiente (ascendente o descendente) para cada propiedad. El índice también puede incluir las entidades principales de la entidad para usarlas en las consultas principales.

Una tabla de índice contiene una columna por cada propiedad que figura en la definición del índice. Cada fila de la tabla representa una entidad de Cloud Datastore que es un resultado potencial de las consultas basadas en el índice. Una entidad solo se incluye en el índice si tiene un valor indexado configurado para cada propiedad usada en el índice; si la definición del índice hace referencia a una propiedad cuya entidad no tiene ningún valor, esa entidad no aparecerá en el índice y nunca se mostrará como resultado de ninguna búsqueda basada en el índice.

Nota: Cloud Datastore usa un valor nulo (null) para distinguir entre una entidad que posee la propiedad y una que no. Si le asignas un valor nulo a una propiedad de una entidad de manera explícita, es posible que se incluya esa entidad en los resultados de una consulta que haga referencia a esa propiedad.

Nota: Ninguna de las propiedades individuales de los índices compuestos por varias propiedades debe configurarse como no indexada.

Las filas de una tabla de índice se ordenan primero por entidad principal y luego por valor de las propiedades, en el orden especificado en la definición del índice. El índice perfecto de una consulta, que permite que esta se ejecute de manera más eficiente, se define según las siguientes propiedades y en este orden:

  1. Propiedades usadas en filtros de igualdad
  2. Propiedad usada en un filtro de desigualdad (no puede haber más de uno)
  3. Propiedades usadas en órdenes de clasificación

Esto garantiza que todos los resultados para cada ejecución posible de la consulta aparezcan en filas consecutivas en la tabla. Cloud Datastore ejecuta una consulta con un índice perfecto mediante estos pasos:

  1. Identifica el índice correspondiente al tipo, las propiedades de filtro, los operadores de filtro y los órdenes de clasificación de la consulta.
  2. Busca desde el principio del índice hasta la primera entidad que cumple con todas las condiciones de filtro de la consulta.
  3. Continúa la búsqueda del índice y muestra cada entidad a la vez hasta que haga lo siguiente:
    • Encuentre una entidad que no cumple con las condiciones del filtro.
    • Llegue al final del índice.
    • Recopile la cantidad máxima de resultados solicitados por la consulta.

Por ejemplo, considera la siguiente consulta:

Java 8

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

Java 7

Query q1 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"),
                new FilterPredicate("height", FilterOperator.EQUAL, 72)))
        .addSort("height", Query.SortDirection.DESCENDING);

El índice perfecto de esta consulta es una tabla de claves para las entidades del tipo Person, con columnas que contienen los valores de las propiedades lastName y height. Primero, el índice se ordena de manera ascendente por lastName y, luego, en orden descendente por height:

Dos consultas del mismo tipo, pero con valores de filtro diferentes, usan el mismo índice. Por ejemplo, la siguiente consulta usa el mismo índice que la consulta anterior:

Java 8

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

Java 7

Query q2 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"),
                new FilterPredicate("height", FilterOperator.EQUAL, 63)))
        .addSort("height", Query.SortDirection.DESCENDING);

Las dos consultas siguientes también usan el mismo índice, a pesar de ser de distinto tipo:

Java 8

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

Java 7

Query q3 =
    new Query("Person")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"),
                new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian")))
        .addSort("height", Query.SortDirection.ASCENDING);

y

Java 8

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

Java 7

Query q4 =
    new Query("Person")
        .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair"))
        .addSort("firstName", Query.SortDirection.ASCENDING)
        .addSort("height", Query.SortDirection.ASCENDING);

Configuración de índices

De manera predeterminada, Cloud Datastore predefine automáticamente un índice para cada propiedad de cada tipo de entidad. Estos índices predefinidos son suficientes para realizar varias consultas sencillas, como consultas solo de igualdad y consultas de desigualdad simples. Para todas las demás consultas, la aplicación debe definir los índices que necesita en un archivo de configuración de índices llamado datastore-indexes.xml. Si la aplicación intenta realizar una consulta que no puede ejecutarse con los índices disponibles (estén predefinidos o especificados en el archivo de configuración de índices), la consulta fallará y mostrará DatastoreNeedIndexException.

Datastore compila índices automáticos para consultas de los siguientes tipos:

  • Consultas sin categoría que usan solo filtros de entidad principal y de clave
  • Consultas que usan solo filtros de entidad principal y de igualdad
  • Consultas que usan solo filtros de desigualdad (los que están limitados a una única propiedad)
  • Consultas que usan solo filtros de entidad principal, filtros de igualdad en propiedades y filtros de desigualdad en claves
  • Consultas sin filtros y con un solo orden de clasificación en una propiedad, ya sea ascendente o descendente

Otros tipos de consulta requieren que se especifiquen sus índices en el archivo de configuración de índices. Se incluyen las siguientes:

  • Consultas con filtros de entidad principal y de desigualdad
  • Consultas con uno o más filtros de desigualdad en una propiedad y uno o más filtros de igualdad en otras propiedades
  • Consultas con un orden de clasificación por claves descendente
  • Consultas con varios órdenes de clasificación

Índices y propiedades

Estas son algunas consideraciones especiales sobre los índices y cómo se relacionan con las propiedades de las entidades de Cloud Datastore:

Propiedades con tipos de valores mixtos

Cuando dos entidades tienen propiedades con el mismo nombre, pero tipos de valor diferentes, un índice de la propiedad clasifica las entidades primero por tipo de valor y, luego, por un orden secundario adecuado para cada tipo. Por ejemplo, si dos entidades tienen una propiedad llamada age, una con un valor entero y la otra con un valor string, la entidad con el valor de número entero siempre precede a la que tiene el valor string cuando se las ordena por la propiedad age, sin importar el valor mismo de las propiedades.

Esto es muy importante en el caso de los números enteros y los de punto flotante, que Cloud Datastore trata como tipos separados. Como todos los números enteros se ordenan antes que los números flotantes, una propiedad con el valor de número entero 38 aparece antes que una con el valor de número flotante 37.5.

Propiedades no indexadas

Si sabes que nunca tendrás que ordenar o filtrar una propiedad en particular, puedes indicar que la propiedad es no indexada para que Cloud Datastore no mantenga las entradas de índice de esa propiedad. Esta estrategia reduce el costo de ejecutar tu aplicación, ya que disminuye la cantidad de operaciones de escritura de Cloud Datastore que debe realizar. Una entidad con una propiedad no indexada se comporta como si la propiedad no estuviera configurada: las consultas con un filtro o un orden de clasificación en la propiedad no indexada nunca mostrarán esa entidad.

Nota: Si una propiedad figura en un índice compuesto por varias propiedades, configurarla como no indexada evitará que se indexe en el índice compuesto.

Por ejemplo, supongamos que una entidad tiene las propiedades a y b, y que quieres crear un índice que logre cumplir con consultas como WHERE a ="bike" and b="red". También supongamos que no te interesan las consultas WHERE a="bike" y WHERE b="red". Si configuras a como no indexada y creas un índice para a y b, Cloud Datastore no creará entradas de índice de a y b y, por lo tanto, la consulta WHERE a="bike" and b="red" no funcionará. Para que Cloud Datastore cree entradas para los índices de a y b, a y b deben estar indexadas.

En la API de Datastore de Java de bajo nivel, las propiedades se definen como indexadas o no indexadas por entidad, según el método que uses para configurarlas (setProperty() o setUnindexedProperty()):

Java 8

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

Java 7

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Key acmeKey = KeyFactory.createKey("Company", "Acme");

Entity tom = new Entity("Person", "Tom", acmeKey);
tom.setProperty("name", "Tom");
tom.setProperty("age", 32);
datastore.put(tom);

Entity lucy = new Entity("Person", "Lucy", acmeKey);
lucy.setProperty("name", "Lucy");
lucy.setUnindexedProperty("age", 29);
datastore.put(lucy);

Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25);

Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter);

// Returns tom but not lucy, because her age is unindexed
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());

Para cambiar una propiedad de indexada a no indexada, restablece su valor con setUnindexedProperty(). Para hacer lo contrario, restablécelo con setProperty().

Sin embargo, ten en cuenta que cambiar una propiedad de no indexada a indexada no afecta a ninguna entidad existente que se haya creado antes del cambio. Las consultas que filtren según la propiedad no mostrarán esas entidades existentes, ya que las entidades no estaban escritas en el índice de la consulta cuando se crearon. Para que las consultas futuras puedan acceder a las entidades, debes volver a escribirlas en Cloud Datastore para que se ingresen en los índices apropiados. Es decir, debes hacer lo siguiente para cada una de esas entidades:

  1. Recupera (get) la entidad de Cloud Datastore.
  2. Ingresa (put) la entidad de nuevo en Cloud Datastore.

De manera similar, cambiar una propiedad de indexada a no indexada solo afecta a las entidades escritas posteriormente en Cloud Datastore. Las entradas de índice de cualquier entidad actual con esa propiedad existirán hasta que se actualicen o borren las entidades. Para evitar resultados no deseados, debes borrar definitivamente todas las consultas de tu código que filtran o clasifican según la propiedad (ahora no indexada).

Límites de índice

Datastore impone límites al número y tamaño general de las entradas de índice que se pueden asociar con una sola entidad. Estos límites son grandes y la mayoría de las aplicaciones no se ven afectadas. Sin embargo, hay circunstancias en las que puedes alcanzar estos límites.

Como se describió antes, Cloud Datastore crea una entrada en un índice predefinido para cada propiedad de cada entidad, excepto de strings de texto largas (Text), strings de bytes largas (Blob), entidades incorporadas (EmbeddedEntity) y entidades que se declararon de forma explícita como no indexadas. La propiedad también puede estar incluida en índices personalizados adicionales declarados en tu archivo de configuración datastore-indexes.xml. Siempre que una entidad no tenga propiedades de lista, tendrá a lo sumo una entrada en cada uno de estos índices personalizados (para índices no principales) o una para cada una de las entidades principales de la entidad (para índices principales). Cada una de estas entradas de índice debe actualizarse cada vez que cambie el valor de la propiedad.

En el caso de una propiedad que tiene un único valor para cada entidad, cada valor posible debe almacenarse solo una vez por entidad en el índice predefinido de la propiedad. Pese a ello, es posible que una entidad con un gran número de estas propiedades de un único valor supere el límite de entradas o de tamaño del índice. De manera similar, una entidad que puede tener múltiples valores para la misma propiedad requiere una entrada de índice distinta para cada valor. De la misma forma, si el número de valores posibles es grande, esta entidad puede superar el límite de entradas.

La situación empeora en el caso de las entidades con varias propiedades, cada una de las cuales puede asumir diversos valores. A fin de alojar una entidad de ese tipo, el índice debe incluir una entrada para cada combinación posible de valores de propiedad. Los índices personalizados que hacen referencia a varias propiedades, cada una con múltiples valores, son susceptibles a un "alto crecimiento" combinatorio y pueden requerir un gran número de entradas para una entidad con una cantidad relativamente pequeña de valores de propiedad posibles. Estos índices con alto crecimiento pueden aumentar en gran medida el costo de escribir una entidad en Cloud Datastore debido a la alta cantidad de entradas de índice que deben actualizarse. También pueden hacer fácilmente que la entidad supere el límite de entradas o de tamaño del índice.

Considera la consulta

Java 8

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

Java 7

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("y", FilterOperator.EQUAL, 2)))
        .addSort("date", Query.SortDirection.ASCENDING);

que hace que el SDK sugiera el siguiente índice:

Java 8

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>

Java 7

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget" ancestor="false" source="manual">
    <property name="x" direction="asc"/>
    <property name="y" direction="asc"/>
    <property name="date" direction="asc"/>
  </datastore-index>
</datastore-indexes>
Este índice requerirá un total de entradas |x| * |y| * |date| para cada entidad (donde |x| denota el número de valores asociados con la entidad para la propiedad x). Por ejemplo, el siguiente código

Java 8

Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

Java 7

Entity widget = new Entity("Widget");
widget.setProperty("x", Arrays.asList(1, 2, 3, 4));
widget.setProperty("y", Arrays.asList("red", "green", "blue"));
widget.setProperty("date", new Date());
datastore.put(widget);

crea una entidad con cuatro valores para la propiedad x, tres valores para la propiedad y y date definido como la fecha actual. Esto requerirá 12 entradas de índice, una para cada combinación posible de valores de propiedad:

(1, "red", <now>) (1, "green", <now>) (1, "blue", <now>)

(2, "red", <now>) (2, "green", <now>) (2, "blue", <now>)

(3, "red", <now>) (3, "green", <now>) (3, "blue", <now>)

(4, "red", <now>) (4, "green", <now>) (4, "blue", <now>)

Cuando la misma propiedad se repite varias veces, Cloud Datastore puede detectar índices con alto crecimiento y sugerir un índice alternativo. Sin embargo, en todas las demás circunstancias (como la consulta definida en este ejemplo), Cloud Datastore generará un índice con alto crecimiento. En este caso, puedes evitar el índice con alto crecimiento si configuras de forma manual un índice en tu archivo de configuración de índices:

Java 8

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>

Java 7

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes autoGenerate="false">
  <datastore-index kind="Widget">
    <property name="x" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
  <datastore-index kind="Widget">
    <property name="y" direction="asc" />
    <property name="date" direction="asc" />
  </datastore-index>
</datastore-indexes>
Esto reduce el número de entradas necesarias a (|x| * |date| + |y| * |date|) o 7 entradas en lugar de 12:

(1, <now>) (2, <now>) (3, <now>) (4, <now>)

("red", <now>) ("green", <now>) ("blue", <now>)

Cualquier operación put que haga que un índice supere los límites de entradas o de tamaño fallará y mostrará IllegalArgumentException. En el texto de la excepción, se describe qué límite se superó ("Too many indexed properties" o "Index entries too large") y qué índice personalizado fue la causa. Si creas un índice nuevo que podría superar los límites de cualquier entidad, las consultas sobre ese índice fallarán y el índice aparecerá con el estado Error en GCP Console. Para resolver los índices con el estado Error, sigue los siguientes pasos:

  1. Quita el índice con el estado Error de tu archivo datastore-indexes.xml.

  2. Ejecuta el siguiente comando desde el directorio donde esté ubicado tu datastore-indexes.xml para quitar ese índice de Cloud Datastore.

    appcfg.sh vacuum_indexes [YOUR_APP_DIR]
    
  3. Resuelve la causa del error. Por ejemplo:

    • Reformula la definición del índice y las consultas correspondientes.
    • Quita las entidades que generan el alto crecimiento del índice.
  4. Agrega el índice de nuevo a tu archivo datastore-indexes.xml.

  5. Ejecuta el comando siguiente desde el directorio en el que está ubicado tu archivo datastore-indexes.xml para crear el índice en Cloud Datastore:

    appcfg.sh update_indexes datastore-indexes.xml
    

Puedes evitar los índices con alto crecimiento si evitas las consultas que requieren un índice personalizado con una propiedad de lista. Como se describió antes, esto incluye consultas con varios órdenes de clasificación o con una combinación de filtros de igualdad y desigualdad.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Entorno estándar de App Engine para Java 8