App Engine predefines a simple index on each property of an entity.
An App Engine application can define further custom indexes in an
configuration file named
which is generated in your application's
. The development server automatically
adds suggestions to this file as it encounters queries that cannot be executed with the existing indexes.
You can tune indexes manually by editing the file before uploading the application.
Note: The index-based query mechanism supports a wide range of queries and is suitable for most applications. However, it does not support some kinds of query common in other database technologies: in particular, joins and aggregate queries aren't supported within the Datastore query engine. See Datastore Queries page for limitations on Datastore queries.
Index definition and structure
An index is defined on a list of properties of a given entity kind, with a corresponding order (ascending or descending) for each property. For use with ancestor queries, the index may also optionally include an entity's ancestors.
An index table contains a column for every property named in the index's definition. Each row of the table represents an entity in Datastore that is a potential result for queries based on the index. An entity is included in the index only if it has an indexed value set for every property used in the index; if the index definition refers to a property for which the entity has no value, that entity will not appear in the index and hence will never be returned as a result for any query based on the index.
Note: Datastore distinguishes
between an entity that does not possess a property and one that possesses the
property with a null value (
null). If you explicitly assign a
null value to an entity's property, that entity may be included in the results
of a query referring to that property.
Note: Indexes composed of multiple properties require that each individual property must not be set to unindexed.
The rows of an index table are sorted first by ancestor and then by property values, in the order specified in the index definition. The perfect index for a query, which allows the query to be executed most efficiently, is defined on the following properties, in order:
- Properties used in equality filters
- Property used in an inequality filter (of which there can be no more than one)
- Properties used in sort orders
This ensures that all results for every possible execution of the query appear in consecutive rows of the table. Datastore executes a query using a perfect index by the following steps:
- Identifies the index corresponding to the query's kind, filter properties, filter operators, and sort orders.
- Scans from the beginning of the index to the first entity that meets all of the query's filter conditions.
- Continues scanning the index, returning each entity in turn, until it
- encounters an entity that does not meet the filter conditions, or
- reaches the end of the index, or
- has collected the maximum number of results requested by the query.
For example, consider the following query:
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);
The perfect index for this query is a table of keys for entities of kind
Person, with columns for the values of the
height properties. The index
is sorted first in ascending order by
and then in descending order by
To generate these indexes, configure your indexes like this:
<?xml version="1.0" encoding="utf-8"?> <datastore-indexes autoGenerate="false"> <datastore-index kind="Person" ancestor="false" source="manual"> <property name="lastName" direction="asc"/> <property name="height" direction="desc"/> </datastore-index> </datastore-indexes>
Two queries of the same form but with different filter values use the same index. For example, the following query uses the same index as the one above:
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);
The following two queries also use the same index, despite their different forms:
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);
Query q4 = new Query("Person") .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair")) .addSort("firstName", Query.SortDirection.ASCENDING) .addSort("height", Query.SortDirection.ASCENDING);
By default, Datastore automatically predefines an index for each
property of each entity kind. These predefined indexes are sufficient to perform
many simple queries, such as equality-only queries and simple inequality
queries. For all other queries, the application must define the indexes it needs
in an index configuration
datastore-indexes.xml. If the application tries to perform a query
that cannot be executed with the available indexes (either predefined or
specified in the index configuration file), the query will fail
Datastore builds automatic indexes for queries of the following forms:
- Kindless queries using only ancestor and key filters
- Queries using only ancestor and equality filters
- Queries using only inequality filters (which are limited to a single property)
- Queries using only ancestor filters, equality filters on properties, and inequality filters on keys
- Queries with no filters and only one sort order on a property, either ascending or descending
Other forms of query require their indexes to be specified in the index configuration file, including:
- Queries with ancestor and inequality filters
- Queries with one or more inequality filters on a property and one or more equality filters on other properties
- Queries with a sort order on keys in descending order
- Queries with multiple sort orders
Indexes and properties
Here are a few special considerations to keep in mind about indexes and how they relate to the properties of entities in Datastore:
Properties with mixed value types
When two entities have properties of the same name but different value types, an
index of the property sorts the entities first by
value type and then by a
appropriate to each type. For example, if two entities each have a property
age, one with an integer value and one with a string value, the entity
with the integer value always precedes the one with the string value when sorted
age property, regardless of the property values themselves.
This is especially worth noting in the case of integers and floating-point
numbers, which are treated as separate types by Datastore.
Because all integers are sorted before all floats, a property with the integer
38 is sorted before one with the floating-point value
If you know you will never have to filter or sort on a particular property, you can tell Datastore not to maintain index entries for that property by declaring the property unindexed. This lowers the cost of running your application by decreasing the number of Datastore writes it has to perform. An entity with an unindexed property behaves as if the property were not set: queries with a filter or sort order on the unindexed property will never match that entity.
If a property appears in an index composed of multiple properties, then setting
it to unindexed will prevent it from being indexed in the composed index.
For example, suppose that an entity has properties a and b and that you want to create an index able to satisfy queries like
WHERE a ="bike" and b="red". Also suppose that you don't care
about the queries
WHERE a="bike" and
If you set a to unindexed and create an index for a and b
Datastore will not create index entries for the a and
b index and so the
WHERE a="bike" and b="red" query won't
work. For Datastore to create entries for the a and
b indexes, both a and b must be indexed.
In the low-level Java Datastore API, properties are defined as indexed or
unindexed on a per-entity basis, depending on the method you use to set them
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());
You can change an indexed property to unindexed by resetting its value with
setUnindexedProperty(), or from unindexed to indexed by resetting it with
Note, however, that changing a property from unindexed to indexed does not affect any existing entities that may have been created before the change. Queries filtering on the property will not return such existing entities, because the entities weren't written to the query's index when they were created. To make the entities accessible by future queries, you must rewrite them to Datastore so that they will be entered in the appropriate indexes. That is, you must do the following for each such existing entity:
- Retrieve (get) the entity from Datastore.
- Write (put) the entity back to Datastore.
Similarly, changing a property from indexed to unindexed only affects entities subsequently written to Datastore. The index entries for any existing entities with that property will continue to exist until the entities are updated or deleted. To avoid unwanted results, you must purge your code of all queries that filter or sort by the (now unindexed) property.
Datastore imposes limits on the number and overall size of index entries that can be associated with a single entity. These limits are large, and most applications are not affected. However, there are circumstances in which you might encounter the limits.
As described above,
Datastore creates an entry in a predefined index for every
property of every entity except long text strings (
Text), long byte strings
Blob), and embedded entities
and those you have explicitly
declared as unindexed. The property
may also be included in additional, custom indexes declared in your
file. Provided that
an entity has no list properties, it will have at most one entry in each such
custom index (for non-ancestor indexes) or one for each of the entity's
ancestors (for ancestor indexes). Each of these index entries must be updated
every time the value of the property changes.
For a property that has a single value for each entity, each possible value needs to be stored just once per entity in the property's predefined index. Even so, it is possible for an entity with a large number of such single-valued properties to exceed the index entry or size limit. Similarly, an entity that can have multiple values for the same property requires a separate index entry for each value; again, if the number of possible values is large, such an entity can exceed the entry limit.
The situation becomes worse in the case of entities with multiple properties, each of which can take on multiple values. To accommodate such an entity, the index must include an entry for every possible combination of property values. Custom indexes that refer to multiple properties, each with multiple values, can "explode" combinatorially, requiring large numbers of entries for an entity with only a relatively small number of possible property values. Such exploding indexes can dramatically increase the cost of writing an entity to Datastore, because of the large number of index entries that must be updated, and also can easily cause the entity to exceed the index entry or size limit.
Consider the query
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);
which causes the SDK to suggest the following index:
<?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>
|date|entries for each entity (where
|x|denotes the number of values associated with the entity for property
x). For example, the following code
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);
creates an entity with four values for property
x, three values for property
date set to the current date. This will require 12 index entries, one
for each possible combination of property values:
When the same property is repeated multiple times, Datastore can detect exploding indexes and suggest an alternative index. However, in all other circumstances (such as the query defined in this example), Datastore will generate an exploding index. In this case, you can circumvent the exploding index by manually configuring an index in your index configuration file:
<?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>
|date|), or 7 entries instead of 12:
Any put operation that would cause an index to exceed the index entry or size
limit will fail with an
. The text of the
exception describes which limit was
"Too many indexed properties" or
"Index entries too large") and
which custom index was the cause. If you create a new index that would exceed
the limits for any entity when built, queries against the index will fail and
the index will appear in the
Error state in the Google Cloud console. To
resolve indexes in the
Remove the index in the
Errorstate from your
Run the following command from the directory where your
datastore-indexes.xmlis located to remove that index from Datastore:
gcloud datastore indexes cleanup datastore-indexes.xml
Resolve the cause of the error. For example:
- Reformulate the index definition and corresponding queries.
- Remove the entities that are causing the index to explode.
Add the index back to your
Run the following command from the directory where your
datastore-indexes.xmlis located to create the index in Datastore:
gcloud datastore indexes create datastore-indexes.xml
You can avoid exploding indexes by avoiding queries that would require a custom index using a list property. As described above, this includes queries with multiple sort orders or queries with a mix of equality and inequality filters.