Como otimizar índices

Nesta página, descrevemos conceitos que serão avaliados ao selecionar os índices do Firestore em modo Datastore para seu aplicativo.

O Firestore no modo Datastore oferece alto desempenho de consulta usando índices para todas as consultas. O desempenho da maioria das consultas depende do tamanho do conjunto de resultados e não do tamanho total do banco de dados.

O Firestore no modo Datastore define índices integrados para cada propriedade em uma entidade. Esses índices de propriedade única são compatíveis com muitas consultas simples. O Firestore no modo Datastore é compatível com um recurso de mesclagem de índices que permite que seu banco de dados mescle índices integrados para aceitar outras consultas. Para consultas mais complexas, defina os índices compostos com antecedência.

Nesta página, o foco é o recurso de mesclagem de índices, porque ele afeta duas oportunidades importantes de otimização de índice:

  • Como acelerar consultas
  • Como reduzir o número de índices compostos

O exemplo a seguir demonstra o recurso de mesclagem de índices.

Como filtrar entidades Photo

Considere um banco de dados no modo Datastore com entidades do tipo Photo:

Foto
Propriedade Tipo de valor Descrição
owner_id String ID do usuário
tag Matriz de strings Palavras-chave tokenizadas
size Inteiro Enumeração:
  • 1 icon
  • 2 medium
  • 3 large
coloration Inteiro Enumeração:
  • 1 black & white
  • 2 color

Imagine que você precisa de um recurso de aplicativo que permita aos usuários consultar entidades Photo com base em um AND lógico dos seguintes itens:

  • Até três filtros com base nas propriedades:

    • owner_id
    • size
    • coloration
  • Uma string de pesquisa tag. O aplicativo tokeniza a string de pesquisa em tags e adiciona um filtro para cada tag.

    Por exemplo, o aplicativo transforma a string de pesquisa outside, family nos filtros de consulta tag=outside e tag=family.

Usando índices integrados e o recurso de mesclagem de índices do Firestore em modo Datastore, é possível atender aos requisitos de índice desse recurso de filtro Photo sem adicionar outros índices compostos.

Os índices integrados para entidades Photo são compatíveis com consultas de filtro único como:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_owner_id = client.query(kind="Photo", filters=[("owner_id", "=", "user1234")])

query_size = client.query(kind="Photo", filters=[("size", "=", 2)])

query_coloration = client.query(kind="Photo", filters=[("coloration", "=", 2)])

O recurso de filtro Photo também exige consultas que combinam vários filtros de igualdade com um AND lógico:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_all_properties = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "user1234"),
        ("size", "=", 2),
        ("coloration", "=", 2),
        ("tag", "=", "family"),
    ],
)

O Firestore no modo Datastore pode aceitar essas consultas mesclando índices integrados.

Como mesclar índices

O Firestore no modo Datastore pode usar a mesclagem de índices quando sua consulta e seus índices atendem a todas as seguintes restrições:

  • A consulta usa apenas filtros de igualdade (=)
  • Não há índice composto que corresponda perfeitamente aos filtros e à ordem da consulta
  • Cada filtro de igualdade corresponde a pelo menos um índice atual com a mesma ordem da consulta

Nessa situação, o Firestore no modo Datastore pode usar índices atuais para aceitar a consulta em vez de exigir que você configure outro índice composto.

Quando dois ou mais índices são classificados pelos mesmos critérios, o Firestore no modo Datastore pode mesclar os resultados de várias verificações de índice para encontrar os resultados comuns a todos esses índices. O Firestore no modo Datastore pode mesclar índices integrados, porque todos eles classificam os valores por chave de entidade.

Mesclando índices integrados, o Firestore no modo Datastore oferece suporte a consultas com filtros de igualdade em várias propriedades:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_all_properties = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "user1234"),
        ("size", "=", 2),
        ("coloration", "=", 2),
        ("tag", "=", "family"),
    ],
)

O Firestore no modo Datastore também pode mesclar resultados de índice de várias seções do mesmo índice. Mesclando diferentes seções do índice integrado para a propriedade tag, o Firestore no modo Datastore é compatível com consultas que combinam vários filtros tag em um lógico AND:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_tag = client.query(
    kind="Photo",
    filters=[
        ("tag", "=", "family"),
        ("tag", "=", "outside"),
        ("tag", "=", "camping"),
    ],
)

query_owner_size_color_tags = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "user1234"),
        ("size", "=", 2),
        ("coloration", "=", 2),
        ("tag", "=", "family"),
        ("tag", "=", "outside"),
        ("tag", "=", "camping"),
    ],
)

As consultas compatíveis com índices integrados mesclados completam o conjunto de consultas exigido pelo recurso de filtragem Photo. O suporte ao recurso de filtragem Photo não exigiu outros índices compostos.

Ao selecionar os índices ideais para seu aplicativo, é importante entender o recurso de mesclagem de índices. A mesclagem de índices oferece ao Firestore no modo Datastore maior flexibilidade de consulta, mas com uma possível compensação pelo desempenho. A próxima seção descreve o desempenho da mesclagem de índices e como melhorar o desempenho adicionando índices compostos.

Como encontrar o índice perfeito

O índice é classificado primeiro pelo ancestral e depois pelos valores da propriedade, na ordem especificada na definição do índice. O índice composto perfeito para uma consulta, que permite uma consulta mais eficiente, é definido nas seguintes propriedades, em ordem:

  1. Propriedades usadas nos filtros de igualdade
  2. propriedades usadas em ordens de classificação
  3. Propriedades usadas no filtro distinctOn
  4. Propriedades usadas no intervalo e filtros de desigualdade (que ainda não estão incluídos nas ordens de classificação)
  5. propriedades usadas em agregações e projeções que ainda não estão incluídas nas ordens de classificação e filtros de intervalo e desigualdade

Isso garante que todos os resultados de cada execução possível da consulta sejam contabilizados. O Firestore em bancos de dados no modo Datastore executa uma consulta usando um índice perfeito 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 ou a um subconjunto das condições do filtro da consulta.
  3. Continua verificando o índice, retornando cada entidade que satisfaça todas as condições do filtro 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:

SELECT * FROM Task
WHERE category = 'Personal'
  AND priority < 3
ORDER BY priority DESC

O índice composto perfeito para esta consulta é um índice de chaves para entidades do tipo Task, com colunas para os valores das propriedades category e priority. O índice é classificado primeiro em ordem crescente por category e depois em ordem decrescente por priority:

indexes:
- kind: Task
  properties:
  - name: category
    direction: asc
  - name: priority
    direction: desc

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

SELECT * FROM Task
WHERE category = 'Work'
  AND priority < 5
ORDER BY priority DESC

Para este índice

indexes:
- kind: Task
  properties:
  - name: category
    direction: asc
  - name: priority
    direction: asc
  - name: created
    direction: asc

O índice anterior pode atender às duas consultas a seguir:

SELECT * FROM Task
WHERE category = 'Personal'
  AND priority = 5
ORDER BY created ASC

do

SELECT * FROM Task
WHERE category = 'Work'
ORDER BY priority ASC, created ASC

Como otimizar sua seleção de índices

Nesta seção, descrevemos as características de desempenho da mesclagem de índices e duas oportunidades de otimização relacionadas à mesclagem de índices:

  • Adicione índices compostos para acelerar consultas que dependem de índices mesclados
  • Reduzir o número de índices compostos aproveitando os índices mesclados

Desempenho de mesclagem de índices

Em uma mesclagem de índices, o Firestore no modo Datastore mescla com eficiência os índices usando um algoritmo de mesclagem em zigue-zague. Usando esse algoritmo, o modo Datastore une possíveis correspondências de várias verificações de índice para produzir um conjunto de resultados que corresponda a uma consulta. A mesclagem de índices combina os componentes do filtro no tempo de leitura em vez do tempo de gravação. Diferentemente da maioria das consultas do Firestore no modo Datastore em que o desempenho depende apenas do tamanho do conjunto de resultados, o desempenho das consultas de mesclagem de índices depende dos filtros na consulta e de quantas possíveis correspondências o banco de dados considera.

O melhor desempenho de uma mesclagem de índices ocorre quando cada correspondência possível em um índice satisfaz os filtros de consulta. Nesse caso, o desempenho é O(R * I), em que R é o tamanho do conjunto de resultados e I é o número de índices verificados.

O pior desempenho ocorre quando o banco de dados precisa considerar muitas correspondências possíveis, mas poucas satisfazem os filtros de consulta. Nesse caso, o desempenho é O(S), em que S é o tamanho do menor conjunto de possíveis entidades de uma única verificação de índice.

O desempenho real depende da forma dos dados. O número médio de entidades consideradas para cada resultado retornado é O(S/(R * I)). As consultas têm pior desempenho quando muitas entidades correspondem a cada verificação de índice, mas poucas entidades correspondem à consulta como um todo, ou seja, R é pequeno e S é grande.

Quatro fatores atenuam esse risco:

  • O planejador de consultas não procura uma entidade até saber que a entidade corresponde à consulta inteira.

  • O algoritmo zigue-zague não precisa encontrar todos os resultados para retornar o próximo resultado. Se você solicitar os 10 primeiros resultados, pagará apenas a latência para encontrar esses 10 resultados.

  • O algoritmo zigue-zague pula seções grandes de resultados falsos positivos. O pior desempenho ocorrerá somente se resultados positivos falsos estiverem perfeitamente entrelaçados (em ordem de classificação) entre verificações.

  • A latência depende do número de entidades encontradas em cada verificação de índice, não do número de entidades que correspondem a cada filtro. Como mostrado na próxima seção, adicione índices compostos para melhorar o desempenho da mesclagem de índices.

Como acelerar uma consulta de mesclagem de índices

Quando o Firestore no modo Datastore mescla índices, cada verificação de índice geralmente é mapeada para um único filtro na consulta. É possível melhorar o desempenho da consulta adicionando índices compostos que correspondem a vários filtros na consulta.

Considere esta consulta:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_owner_size_tag = client.query(
    kind="Photo",
    filters=[
        ("owner_id", "=", "username"),
        ("size", "=", 2),
        ("tag", "=", "family"),
    ],
)

Cada filtro mapeia para uma verificação de índice nos seguintes índices integrados:

Index(Photo, owner_id)
Index(Photo, size)
Index(Photo, tag)

Ao adicionar o índice composto Index(Photo, owner_id, size), a consulta é mapeada para duas verificações de índice em vez de três:

#  Satisfies both 'owner_id=username' and 'size=2'
Index(Photo, owner_id, size)
Index(Photo, tag)

Considere um cenário com muitas imagens grandes, muitas imagens em preto e branco, mas poucas imagens panorâmicas grandes. Uma filtragem de consulta para imagens panorâmicas e em preto e branco será lenta se mesclar índices integrados:

Python

from google.cloud import datastore

# For help authenticating your client, visit
# https://cloud.google.com/docs/authentication/getting-started
client = datastore.Client()

query_size_coloration = client.query(
    kind="Photo", filters=[("size", "=", 2), ("coloration", "=", 1)]
)

Para melhorar o desempenho da consulta, é possível diminuir o valor de S (menor conjunto de entidades em uma única verificação de índice) em O(S/(R * I)) adicionando o seguinte índice composto:

Index(Photo, size, coloration)

Em comparação com o uso de dois índices integrados, esse índice composto produz menos resultados em potencial para os mesmos dois filtros de consulta. Essa abordagem melhora substancialmente o desempenho ao custo de mais um índice.

Como reduzir o número de índices compostos com mesclagem de índices

Embora os índices compostos que correspondem exatamente aos filtros em uma consulta tenham melhor desempenho, nem sempre é melhor ou possível adicionar um índice composto para cada combinação de filtros. É necessário equilibrar seus índices compostos em relação aos seguintes itens:

  • Limites de índice composto:

    Limite Valor
    Número máximo de índices compostos de um projeto
    Soma máxima dos tamanhos das entradas do índice composto de uma entidade 2 MiB
    Soma máxima dos itens a seguir para uma entidade:
    • número de valores de propriedade indexados
    • número de entradas de índice composto
    20.000
  • Custos de armazenamento de cada novo índice.
  • Efeitos na latência de gravação.

Os problemas de indexação geralmente surgem com campos de vários valores, como a propriedade tag das entidades Photo.

Por exemplo, imagine que o recurso de filtragem Photo agora precisa oferecer suporte a cláusulas de ordem decrescente com base em quatro outras propriedades:

Foto
Propriedade Tipo de valor Descrição
date_added Inteiro Data/hora
rating Ponto flutuante Avaliação de usuários agregados
comment_count Inteiro Número de comentários
download_count Inteiro Número de downloads

Se você desconsiderar o campo tag, é possível selecionar índices compostos que correspondam a cada combinação de filtros Photo:

Index(Photo, owner_id, -date_added)
Index(Photo, owner_id, -comments)
Index(Photo, size, -date_added)
Index(Photo, size, -comments)
...
Index(Photo, owner_id, size, -date_added)
Index(Photo, owner_id, size, -comments)
...
Index(Photo, owner_id, size, coloration, -date_added)
Index(Photo, owner_id, size, coloration, -comments)

O número total de índices compostos é:

2^(number of filters) * (number of different orders) = 2 ^ 3 * 4 = 32 composite indexes

Se você tentar aceitar até três filtros tag, o número total de índices compostos será:

2 ^ (3 + 3 tag filters) * 4 = 256 indexes.

Índices que incluem propriedades de vários valores, como tag, também levam a problemas de índice em explosão que aumentam os custos de armazenamento e a latência de gravação.

Para oferecer suporte a filtros no campo tag desse recurso, é possível reduzir o número total de índices contando com os índices mesclados. O seguinte conjunto de índices compostos é o mínimo necessário para oferecer suporte ao recurso de filtragem Photo com ordenação:

Index(Photo, owner_id, -date_added)
Index(Photo, owner_id, -rating)
Index(Photo, owner_id, -comments)
Index(Photo, owner_id, -downloads)
Index(Photo, size, -date_added)
Index(Photo, size, -rating)
Index(Photo, size, -comments)
Index(Photo, size, -downloads)
...
Index(Photo, tag, -date_added)
Index(Photo, tag, -rating)
Index(Photo, tag, -comments)
Index(Photo, tag, -downloads)

O número de índices compostos definidos é:

(number of filters + 1) * (number of orders) = 7 * 4 = 28

A mesclagem de índices também oferece os seguintes benefícios:

  • Permite que uma entidade Photo ofereça suporte a até 1.000 tags sem limite no número de tag filtros por consulta.
  • Reduz o número total de índices, o que reduz os custos de armazenamento e a latência de gravação.

Como selecionar índices para seu aplicativo

Selecione índices ideais para seu banco de dados no modo Datastore usando duas abordagens:

  • Usar a mesclagem de índices para compatibilidade com outras consultas

    • Requer menos índices compostos.
    • Reduz o custo de armazenamento por entidade.
    • Melhora a latência de gravação
    • Evita índices em explosão.
    • O desempenho depende da forma dos dados.
  • Definir um índice composto que corresponda a vários filtros em uma consulta

    • Melhora o desempenho da consulta.
    • Desempenho de consulta consistente que não depende da forma dos dados.
    • Precisa permanecer abaixo do limite de índices compostos
    • Aumento no custo de armazenamento por entidade.
    • Maior latência de gravação

Ao descobrir os índices ideais para seu aplicativo, a resposta pode mudar conforme a forma dos dados é alterada. O desempenho da consulta de amostragem fornece uma boa ideia das consultas comuns do seu aplicativo e de suas consultas lentas. Com essas informações, é possível adicionar índices para melhorar o desempenho de consultas comuns e lentas.