Índices do Cloud Datastore

Observação: os desenvolvedores que criam novos aplicativos são bastante incentivados a usar a Biblioteca de cliente NDB, que oferece diversos benefícios em comparação com esta biblioteca de cliente, como armazenamento em cache automático de entidades por meio da API Memcache. Se você estiver usando a antiga biblioteca de cliente DB, leia o Guia de migração de DB para NDB.

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 index.yaml. 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.

Veja uma discussão mais detalhada sobre índices e consultas em Seleção de índice e pesquisa avançada.

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. Em especial, as consultas agregadas e conjuntas não são compatíveis com o mecanismo de consulta do Cloud Datastore. Consulte as limitações das consultas do Cloud Datastore na 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 nomeada na definição desse índice. Cada linha representa uma entidade no Cloud Datastore, gerando um potencial resultado para consultas baseadas no índice. Uma entidade é incluída no índice apenas quando há nele um conjunto de valores indexado para cada propriedade usada no índice. Caso a definição do índice se refira a uma propriedade em que a entidade não tenha 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 Cloud Datastore diferencia uma entidade que não tem uma propriedade de outra que tem uma propriedade com um valor nulo (None). Se você atribuir explicitamente um valor nulo à propriedade de uma entidade, essa entidade poderá ser incluída nos resultados de uma consulta referente a essa propriedade.

Observação: em índices compostos por várias propriedades, cada uma delas não pode ser configurada 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:

  1. propriedades usadas nos filtros de igualdade;
  2. propriedade usada em um filtro de desigualdade, em que não é possível haver mais de um;
  3. 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. Uma consulta é feita pelo Cloud Datastore, usando um índice perfeito e seguindo estas etapas:

  1. Identifica o índice correspondente ao tipo de consulta, às propriedades do filtro, aos operadores do filtro e às ordens de classificação
  2. Verifica desde o início do índice até a primeira entidade que atenda a todas as condições do filtro da consulta
  3. 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, veja a seguinte consulta (declarada em GQL):

SELECT * FROM Person WHERE last_name = "Smith"
                       AND height < 72
                  ORDER BY height DESC

O índice perfeito para essa consulta é uma tabela de chaves para entidades do tipo Person, com colunas para os valores das propriedades last_name e height. A ordem desse índice é primeiro crescente por last_name e decrescente por height.

Duas consultas de formulários iguais com valores de filtros diferentes usam o mesmo índice. Por exemplo, a consulta a seguir usa o mesmo índice da consulta acima:

SELECT * FROM Person WHERE last_name = "Jones"
                       AND height < 63
                     ORDER BY height DESC

As duas consultas abaixo também usam o mesmo índice, mesmo tendo formulários diferentes:

SELECT * FROM Person WHERE last_name = "Friedkin"
                       AND first_name = "Damian"
                     ORDER BY height ASC

e

SELECT * FROM Person WHERE last_name = "Blair"
                  ORDER BY first_name, height ASC

Configuração de índice

Por padrão, para cada propriedade de cada tipo de entidade, um índice é automaticamente predefinido pelo Cloud 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 index.yaml. Se o aplicativo tentar fazer 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, ela falhará com uma exceção NeedIndexError.

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 formulários 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 Cloud Datastore:

Propriedades com tipos de valor mistos

Quando duas entidades têm propriedades com o mesmo nome e tipos de valores diferentes, um índice da propriedade as classifica primeiro por tipo de valor, depois por uma classificação secundária apropriada a cada tipo. Por exemplo, quando duas entidades têm uma propriedade chamada age, uma com valor inteiro e a outra com valor de uma string, a primeira sempre precede a segunda quando classificada pela propriedade age, independentemente desses valores de propriedades.

Isso vale especialmente para os números inteiros e números de pontos flutuantes, tratados de maneira distinta pelo Cloud Datastore. Como todos os números inteiros são classificados antes dos flutuantes, uma propriedade com o valor inteiro 38 é classificada antes do 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 Cloud Datastore como não indexada para que ele não mantenha as entradas de índice dessa propriedade. Isso diminui o custo da execução do aplicativo e consequentemente o número de gravações que o Cloud Datastore precisa executar. Uma entidade com propriedade não indexada se comporta como se a propriedade não estivesse configurada. Consultas com um filtro ou ordem de classificação na propriedade não indexada não terão correspondências com essa entidade.

Observação: configurar uma propriedade como não indexada, quando ela aparece em um índice composto por diversas propriedades, evitará a indexação no índice composto.

Por exemplo, vamos supor que uma entidade tenha as propriedades a e b, e que você queira criar um índice para consultas como WHERE a ="bike" and b="red". Suponha também que as consultas WHERE a="bike" e WHERE b="red" não são relevantes para você. Se você definir a como não indexada e criar um índice para a e b, o Cloud Datastore não criará entradas de índice para a e b. Portanto, a consulta WHERE a="bike" and b="red" não funcionará. Para que o Cloud Datastore crie entradas para os índices a e b, ambos precisam ser indexados.

Declare uma propriedade não indexada definindo indexed=False no construtor de propriedades:

class Person(db.Model):
  name = db.StringProperty()
  age = db.IntegerProperty(indexed=False)

Você poderá alterar a propriedade novamente para indexada chamando o construtor novamente com indexed=True:

class Person(db.Model):
  name = db.StringProperty()
  age = db.IntegerProperty(indexed=True)

No entanto, alterar uma propriedade excluída para indexada não afeta qualquer entidade criada antes dessa alteração. A filtragem de consultas na propriedade não retornará tais entidades, porque quando elas foram criadas não foram gravadas no índice da consulta. Para facilitar o acesso às entidades nas próximas consultas, é preciso gravá-las novamente no Cloud Datastore para inserção nos índices apropriados. Ou seja, você precisa seguir estas etapas para cada uma dessas entidades:

  1. Recupere ("get") a entidade do Cloud Datastore.
  2. Grave ("put") a entidade novamente no Cloud Datastore.

Da mesma forma, alterar uma propriedade de "indexada" para "não indexada" afeta apenas as entidades gravadas posteriormente no Cloud Datastore. As entradas de índice de qualquer entidade 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 Cloud Datastore cria uma entrada em um índice predefinido para cada propriedade de cada entidade, exceto para strings de texto longas (Text) e strings de bytes longas (Blob) e para aquelas que você declarou explicitamente como não indexadas. Também é possível incluir a propriedade em outros índices personalizados declarados em seu arquivo de configuração index.yaml. Contanto que uma entidade não tenha propriedades de lista, ela terá no máximo uma entrada em cada índice personalizado (para os não ancestrais) ou uma para cada ancestral da entidade (para índices de ancestral). 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 referentes a diversas propriedades, cada uma com diversos valores, "explodem" combinatoriamente e requerem grandes números de entradas para uma entidade com apenas um número relativamente pequeno de valores de propriedade possíveis. Esses índices de explosão aumentam drasticamente o custo de gravação de uma entidade no Cloud Datastore, devido ao grande número de entradas de índice que precisam ser atualizadas e também fazem com que a entidade exceda facilmente a entrada de índice ou o limite de tamanho.

Considere a consulta

SELECT * FROM Widget WHERE x=1 AND y=2 ORDER BY date

que faz com que o SDK sugira o seguinte índice:

indexes:
- kind: Widget
  properties:
  - name: x
  - name: y
  - name: date
Esse índice requer um total de |x| * |y| * |date| entradas para cada entidade, em que |x| indica o número de valores associados à entidade para a propriedade x. Por exemplo, o código a seguir
class Widget(db.Expando):
  pass

e2 = Widget()
e2.x = [1, 2, 3, 4]
e2.y = ['red', 'green', 'blue']
e2.date = datetime.datetime.now()
e2.put()

cria uma entidade com quatro valores para a propriedade x, três valores para a propriedade y e a date configurada como 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 diversas vezes, o Cloud Datastore detecta os índices em explosão e sugere um índice alternativo. No entanto, em todas as outras circunstâncias, como na consulta definida neste exemplo, o Cloud Datastore gera um índice em explosão. Nesse caso, é possível evitar o índice em explosão configurando manualmente um índice no arquivo de configuração.

indexes:
- kind: Widget
  properties:
  - name: x
  - name: date
- kind: Widget
  properties:
  - name: y
  - name: date
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 exceda o limite de entradas ou de tamanho do índice falhará com uma exceção BadRequestError. 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 provocou a falha. Se você criar um novo índice que exceda os limites de qualquer entidade criada, as consultas no índice falharão e o índice aparecerá com o estado de Error no console do GCP. Para resolver índices no estado de Error:

  1. Remova o índice no estado de Error do arquivo index.yaml.

  2. Execute o seguinte comando a partir do diretório em que o index.yaml está localizado para remover esse índice do Cloud Datastore:

    gcloud datastore cleanup-indexes index.yaml
    
  3. Resolva a causa do erro. Por exemplo:

    • Reformule a definição do índice e as consultas correspondentes.
    • Remova as entidades que causam a explosão do índice.
  4. Adicione novamente o índice ao arquivo index.yaml.

  5. Execute o seguinte comando no diretório em que o index.yaml está localizado para criar o índice no Cloud Datastore:

    gcloud datastore create-indexes index.yaml
    

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.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Python 2