O App Engine predefine um índice simples em cada propriedade de uma entidade.
Um aplicativo do App Engine pode definir outros índices personalizados em um arquivo de configuração de índice chamado datastore-indexes.xml
, que é gerado no diretório /war/WEB-INF/appengine-generated
do seu aplicativo. O servidor de desenvolvimento adiciona sugestões a esse arquivo automaticamente quando encontra consultas não executáveis com os índices atuais.
É possível ajustar os índices manualmente editando o arquivo antes de fazer upload do aplicativo.
Observação: o mecanismo de consulta baseado em índice é compatível com uma grande variedade de consultas e adequado à maioria dos aplicativos. No entanto, ele não aceita alguns tipos de consulta comuns em outras tecnologias de banco de dados. Especificamente, mesclagens e consultas agregadas não são compatíveis com o mecanismo de consulta do Datastore. Para conhecer as limitações das consultas do Datastore, consulte a página Consultas do Datastore.
Definição e estrutura dos índices
Em uma lista de propriedades de um determinado tipo de entidade, um índice é definido com uma ordem correspondente (ascendente ou descendente) para cada propriedade. Para uso com consultas de ancestral, o índice também inclui ancestrais de uma entidade.
Uma tabela de índice contém uma coluna para cada propriedade especificada na definição do índice. Cada linha da tabela representa uma entidade no Datastore que é um possível resultado das consultas baseadas em índice. Uma entidade só é incluída no índice se tiver um conjunto de valores indexado para cada propriedade usada no índice. Se a definição do índice se referir a uma propriedade em que a entidade não tem um valor, essa entidade não aparecerá no índice e, portanto, nunca será retornada como resultado de uma consulta baseada em índice.
Observação: o Datastore distingue
uma entidade que não tem uma propriedade de uma que tem a
propriedade com valor nulo (null
). Se você atribuir explicitamente um
valor nulo à propriedade de uma entidade, a entidade poderá ser incluída nos resultados
de uma consulta referente a essa propriedade.
Observação: em índices compostos por várias propriedades, nenhuma delas pode ser definida como não indexada.
Em uma tabela de índice, as linhas são classificadas primeiro pelo ancestral e depois pelos valores da propriedade, na ordem especificada na definição do índice. O índice perfeito, que permite a realização de uma consulta mais eficiente, é definido nas seguintes propriedades, na ordem:
- propriedades usadas nos filtros de igualdade;
- propriedade usada em um filtro de desigualdade, em que não é possível haver mais de um;
- propriedades usadas em ordens de classificação.
Isso garante que, para cada possível execução de consulta, todos os resultados apareçam na tabela em linhas consecutivas. O Datastore executa uma consulta usando um índice perfeito seguindo estas etapas:
- Identifica o índice correspondente ao tipo da consulta, às propriedades do filtro, aos operadores do filtro e às ordens de classificação.
- Verifica desde o início do índice até a primeira entidade que atenda a todas as condições do filtro da consulta.
- Continua verificando o índice, retornando uma entidade por vez até
- encontrar uma entidade que não atenda às condições do filtro ou;
- chegar ao fim do índice ou
- coletar o número máximo de resultados solicitados pela consulta.
Por exemplo, pense na seguinte consulta:
O índice perfeito para esta consulta é uma tabela de chaves para entidades do tipo
Person
, com colunas para os valores das propriedades
lastName
e
height
. O índice
é classificado primeiro em ordem crescente por
lastName
e depois em ordem decrescente por
height
.
Para gerar esses índices, configure os índices da seguinte maneira:
<?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>
Duas consultas do mesmo formato, mas com valores de filtro diferentes, usam o mesmo índice. Por exemplo, a consulta a seguir usa o mesmo índice da consulta acima:
As duas consultas abaixo também usam o mesmo índice, mesmo tendo formulários diferentes:
e
.Configuração de índice
Por padrão, para cada propriedade de
cada tipo de entidade, um índice é automaticamente predefinido pelo Datastore. Esses índices predefinidos são suficientes para realizar muitas
consultas simples, como as só de igualdade e as de desigualdade
simples. Para as demais consultas, o aplicativo precisa definir os índices necessários
em um arquivo de configuração
de índice denominado
datastore-indexes.xml
. Se o aplicativo tentar executar uma consulta que não possa ser
executada com os índices disponíveis, sejam eles predefinidos ou especificados no arquivo de configuração de índice,
a consulta falhará com
DatastoreNeedIndexException
.
O Datastore cria índices automáticos para consultas nos formatos de:
- consultas sem tipo usando apenas filtros de chave e ancestral;
- Consultas usando apenas filtros de igualdade e ancestral
- Consultas usando apenas filtros de desigualdade, limitados a uma única propriedade
- consultas usando apenas filtros de ancestral, filtros de igualdade em propriedades e filtros de desigualdade em chaves;
- consultas sem filtros e apenas uma ordem de classificação em uma propriedade, crescente ou decrescente.
Outros formatos de consulta exigem a especificação dos índices no arquivo de configuração do índice, incluindo:
- consultas com filtros de desigualdade e ancestral;
- consultas com um ou mais filtros de desigualdade em uma propriedade e um ou mais filtros de igualdade em outras propriedades;
- Consultas com uma ordem de classificação nas chaves, em ordem decrescente
- consultas com várias ordens de classificação
Índices e propriedades
Veja, a seguir, algumas considerações especiais sobre os índices e a relação deles com as propriedades de entidades no Datastore:
Propriedades com tipos de valor mistos
Quando duas entidades têm propriedades com o mesmo nome, mas tipos de valor diferentes, um
índice da propriedade classifica as entidades primeiro por
tipo de valor e depois por uma
ordenação secundária
apropriada a cada tipo. Por exemplo, se duas entidades tiverem uma propriedade
denominada age
, uma com valor inteiro e outra com valor de string, a entidade
com o valor inteiro sempre precederá aquela com o valor de string quando classificadas
pela propriedade age
, independentemente dos valores das propriedades.
Isso é especialmente válido no caso de números inteiros e de ponto flutuante,
que são tratados como tipos separados pelo Datastore.
Como todos os números inteiros são classificados antes dos flutuantes, uma propriedade com o valor inteiro
38
é classificada antes de outra com valor de ponto flutuante 37.5
.
Propriedades não indexadas
Caso não seja necessário filtrar ou classificar uma determinada propriedade, declare-a no Datastore como não indexada para que ele não mantenha as entradas de índice dessa propriedade. Isso reduz o custo de execução do aplicativo diminuindo o número de gravações do Datastore que ele precisa executar. Uma entidade com propriedade não indexada se comporta como se a propriedade não estivesse definida: as consultas com um filtro ou ordem de classificação na propriedade não indexada nunca corresponderão a essa entidade.
Observação:
configurar uma propriedade como não indexada, quando ela aparece em um índice composto
por várias propriedades, evita que ela seja indexada no índice composto.
Por exemplo, suponha que uma entidade tenha as propriedades a e b e
que você queira criar um índice capaz de satisfazer consultas como
WHERE a ="bike" and b="red"
. Imagine também que você não se importa
com as consultas WHERE a="bike"
e WHERE b="red"
.
Se você definir a como não indexada e criar um índice para a e b,
o Datastore não criará entradas de índice para o índice a e b e,
portanto, a consulta WHERE a="bike" and b="red"
não
funcionará. Para que o Datastore crie entradas para os índices a e
b, ambos a e b precisam ser indexados.
Na API Java Datastore de baixo nível, as propriedades são definidas como indexadas ou
não indexadas de acordo com a entidade, dependendo do método usado para defini-las
(setProperty()
ou
setUnindexedProperty()
):
Para alterar uma propriedade indexada para não indexada, redefina o valor dela com
setUnindexedProperty()
. Para alterar de não indexada para indexada, redefina o valor com
setProperty()
.
No entanto, observe que alterar uma propriedade não indexada para indexada não afeta entidades existentes criadas antes dessa alteração. A filtragem de consultas na propriedade não retornará tais entidades porque, quando criadas, elas não foram gravadas no índice da consulta. Para facilitar o acesso às entidades nas próximas consultas, é preciso gravá-las novamente no Datastore para inserção nos índices apropriados. Ou seja, você precisa seguir estas etapas para cada uma dessas entidades:
- Recuperar (get) a entidade do Datastore.
- Gravar (put) a entidade de volta no Datastore.
Da mesma forma, alterar uma propriedade indexada para não indexada só afeta as entidades gravadas posteriormente no Datastore. As entradas de índice de qualquer entidade atual com essa propriedade continuarão a existir até que as entidades sejam atualizadas ou excluídas. Para evitar resultados indesejados, é preciso limpar o código de todas as consultas que filtram ou classificam pela propriedade que agora é "não indexada".
Limites dos índices
O Datastore impõe limites quanto ao número e tamanho geral das entradas de índice associadas a uma única entidade. Esses limites são amplos e a maioria dos aplicativos não é afetada. No entanto, há circunstâncias em que você encontrará esses limites.
Conforme descrito acima,
o Datastore cria uma entrada em um índice predefinido para cada
propriedade de cada entidade, exceto strings de texto longas (Text
), strings de bytes longas
(Blob
) e entidades incorporadas
(EmbeddedEntity
),
bem como as que você declarou explicitamente
como não indexadas. A propriedade
também pode ser incluída em outros índices personalizados declarados no
arquivo de configuração
datastore-indexes.xml
. Contanto que
uma entidade não tenha propriedades de lista, ela terá no máximo uma entrada em
cada índice personalizado (índices não ancestrais) ou uma para cada ancestral da
entidade (índices ancestrais). Cada entrada de índice precisará ser atualizada cada vez que houver alteração no valor da propriedade.
Para uma propriedade com um único valor para cada entidade, cada valor possível precisa ser armazenado apenas uma vez por entidade no índice predefinido dessa propriedade. Mesmo assim, é possível que uma entidade com um grande número de propriedades de único valor exceda a entrada de índice ou limite de tamanho. Da mesma forma, uma entidade com diversos valores para a mesma propriedade requer uma entrada de índice individual para cada valor. Repetindo, caso tenha um grande número de valores possíveis, essa entidade excederá o limite de entradas.
A situação piora no caso de entidades com diversas propriedades em que cada uma delas aceita diversos valores. Para acomodar esse tipo de entidade, o índice precisa incluir uma entrada para cada combinação possível de valores de propriedades. Os índices personalizados que referenciam várias propriedades, cada uma com diversos valores, podem "explodir" de forma combinatória, exigindo grandes números de entradas para uma entidade com apenas um número relativamente pequeno de valores de propriedade possíveis. Esses índices em explosão podem aumentar drasticamente o custo da gravação de uma entidade no Datastore, devido ao grande número de entradas de índice que precisam ser atualizadas, além de poderem fazer com que a entidade exceda a entrada de índice ou o limite de tamanho.
Considere a consulta
que faz com que o SDK sugira o seguinte índice:
Esse índice exigirá um total de|x|
*
|y|
*
|date|
entradas para cada
entidade (em que |x|
denota o número de valores associados à entidade da
propriedade x
). Por exemplo, o código a seguir
cria uma entidade com quatro valores para a propriedade x
, três valores para a propriedade
y
e date
definido com a data atual. Isso requer 12 entradas de índice, uma para cada combinação
possível de valores de propriedade:
(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>
)
Quando a mesma propriedade é repetida várias vezes, o Datastore pode detectar índices em explosão e sugerir um índice alternativo. No entanto, em todas as outras circunstâncias (como a consulta definida neste exemplo), o Datastore gerará um índice em explosão. Nesse caso, é possível evitar isso. Basta configurar manualmente um índice no arquivo de configuração.
Isso reduz o número de entradas necessárias para apenas(|x|
*
|date|
+
|y|
*
|date|)
ou 7 entradas em vez de 12:
(1
, <now>
)
(2
, <now>
)
(3
, <now>
)
(4
, <now>
)
("red"
, <now>
)
("green"
, <now>
)
("blue"
, <now>
)
Qualquer operação "put" que faça com que um índice exceda o limite de entradas ou de tamanho
falhará com
IllegalArgumentException
. O texto da
exceção descreve o limite que foi
excedido ("Too many indexed properties"
ou "Index entries too large"
) e
o índice personalizado que a causou. Se você criar um novo índice que exceda
os limites de alguma entidade quando criado, as consultas ao índice falharão e
ele aparecerá no estado Error
no console do Google Cloud. Para
resolver índices no estado Error
:
Remova o índice no estado
Error
do arquivodatastore-indexes.xml
.Execute o comando a seguir no diretório em que o
datastore-indexes.xml
está localizado para remover esse índice do Datastore:gcloud datastore indexes cleanup datastore-indexes.xml
Resolva a causa do erro. Exemplo:
- Reformule a definição do índice e as consultas correspondentes.
- Remova as entidades que causam a explosão do índice.
Adicione novamente o índice ao arquivo
datastore-indexes.xml
.Execute o comando a seguir no diretório em que o
datastore-indexes.xml
está localizado para criar o índice no Datastore:gcloud datastore indexes create datastore-indexes.xml
Para evitar os índices em explosão, não faça consultas que exijam um índice personalizado usando uma propriedade de lista. Conforme descrito acima, isso inclui as consultas com diversas ordens de classificação ou com uma mistura de filtros de igualdade e desigualdade.