Optimisation des index

Cette page décrit les concepts à prendre en compte lors de la sélection des index Firestore en mode Datastore pour votre application.

Firestore en mode Datastore offre des performances de requête élevées grâce à l'utilisation d'index pour toutes les requêtes. Les performances de la plupart des requêtes dépendent de la taille de l'ensemble de résultats et non de la taille totale de la base de données.

Firestore en mode Datastore définit des index intégrés pour chaque propriété d'une entité. Ces index de propriétés uniques sont adaptés à des types de requêtes simples. Firestore en mode Datastore présente une fonctionnalité de fusion d'index qui permet à votre base de données de fusionner des index intégrés afin d'accepter d'autres requêtes. Pour les requêtes plus complexes, vous devez définir des index composites à l'avance.

Cette page s'intéresse à la fonctionnalité de fusion d'index, car elle affecte deux aspects importants de l'optimisation des index :

  • Accélérer les requêtes
  • Réduire le nombre d'index composites

L'exemple suivant montre comment opère la fonctionnalité de fusion d'index.

Filtrage des entités Photo

Prenons l'exemple d'une base de données en mode Datastore avec des entités de genre Photo :

Photo
Valeur Type de valeur Description
owner_id Chaîne ID utilisateur
tag Tableau de chaînes Mots clés tokenisés
size Integer Énumération :
  • 1 icon
  • 2 medium
  • 3 large
coloration Entier Énumération :
  • 1 black & white
  • 2 color

Imaginons que vous ayez besoin d'une fonctionnalité permettant aux utilisateurs d'interroger les entités Photo en fonction d'un AND logique des éléments suivants :

  • Jusqu'à trois filtres basés sur les propriétés :

    • owner_id
    • size
    • coloration
  • Une chaîne de recherche tag. L'application segmente la chaîne de recherche en tags et ajoute un filtre pour chaque tag.

    Par exemple, l'application transforme la chaîne de recherche outside, family en filtres de requête tag=outside et tag=family.

Grâce aux index intégrés et à la fonctionnalité de fusion d'index de Firestore en mode Datastore, vous pouvez répondre aux exigences d'index de cette fonction de filtre Photo sans ajouter d'index composites.

Les index intégrés pour les entités Photo acceptent les requêtes à filtre unique, telles que :

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)])

La fonction de filtre Photo nécessite également des requêtes qui combinent plusieurs filtres d'égalité avec un AND logique :

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"),
    ],
)

Firestore en mode Datastore est en mesure d'accepter ces requêtes en fusionnant des index intégrés.

Fusion d'index

Firestore en mode Datastore peut utiliser la fusion d'index lorsque votre requête et vos index répondent à l'ensemble des contraintes suivantes :

  • La requête n'utilise que des filtres d'égalité (=).
  • Il n'existe aucun index composite qui corresponde parfaitement aux filtres et à l'ordre de tri de la requête.
  • Chaque filtre d'égalité correspond à au moins un index existant ayant le même ordre de tri que la requête.

Dans ce cas, Firestore en mode Datastore peut utiliser des index existants pour accepter la requête, ce qui évite d'avoir à configurer un index composite supplémentaire.

Lorsque plusieurs index sont triés selon les mêmes critères, Firestore en mode Datastore peut fusionner les résultats de plusieurs analyses d'index pour trouver les résultats communs à tous ces index. Firestore en mode Datastore peut fusionner des index intégrés, car tous effectuent le tri des valeurs par clé d'entité.

En fusionnant les index intégrés, Firestore en mode Datastore accepte les requêtes avec des filtres d'égalité appliqués à plusieurs propriétés :

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"),
    ],
)

Firestore en mode Datastore peut également fusionner des résultats d'index à partir de plusieurs sections d'un même index. En fusionnant différentes sections de l'index intégré pour la propriété tag, Firestore en mode Datastore accepte les requêtes combinant plusieurs filtres tag dans un AND logique :

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"),
    ],
)

Les requêtes acceptées par les index intégrés fusionnés complètent l'ensemble des requêtes nécessaires à la fonction de filtrage Photo. Notez que la prise en charge de la fonction de filtrage Photo n'a pas nécessité d'index composites supplémentaires.

Lorsque vous sélectionnez les index optimaux pour votre application, il est important de comprendre la fonctionnalité de fusion d'index. La fusion d'index apporte à Firestore en mode Datastore une plus grande flexibilité d'interrogation, mais un compromis en termes de performances est à rechercher. La section suivante décrit les performances de la fusion d'index et explique comment l'ajout d'index composites peut les améliorer.

Trouver l'indice parfait

L'index est d'abord trié par ancêtre, puis par valeurs de propriété, dans l'ordre spécifié dans la définition de l'index. L'index composite parfait pour une requête, permettant de l'exécuter le plus efficacement, est défini dans les propriétés suivantes, dans l'ordre:

  1. Propriétés utilisées dans les filtres d'égalité
  2. Propriétés utilisées dans les ordres de tri
  3. Propriétés utilisées dans distinctOn filtre
  4. Propriétés utilisées dans les filtres de plage et d'inégalité (qui ne sont pas déjà incluses dans les ordres de tri)
  5. Propriétés utilisées dans les agrégations et les projections (qui ne sont pas déjà incluses dans les ordres de tri et les filtres de plage et d'inégalité)

Cela garantit que tous les résultats pour chaque exécution possible de la requête sont pris en compte. Les bases de données Firestore en mode Datastore exécutent une requête avec un index parfait en procédant comme suit:

  1. Il identifie l'index correspondant au genre de la requête, aux propriétés du filtre, aux opérateurs de filtre et aux ordres de tri.
  2. Il effectue une analyse en partant du début de l'index jusqu'à la première entité qui répond à toutes les conditions de filtre de la requête ou à un sous-ensemble d'entre elles.
  3. Il continue l'analyse de l'index, en renvoyant chaque entité qui remplit toutes les conditions de filtre, jusqu'à :
    • rencontre une entité ne répondant pas aux conditions de filtre ;
    • atteigne la fin de l'index ;
    • ait collecté le nombre maximal de résultats demandés par la requête.

Par exemple, examinez la requête suivante :

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

L'index composite idéal pour cette requête est un index de clés pour les entités de genre Task, avec des colonnes pour les valeurs des propriétés category et priority. L'index est d'abord trié par ordre croissant de category, puis par ordre décroissant de priority.

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

Deux requêtes ayant la même forme, mais avec des valeurs de filtrage différentes utilisant le même index. Par exemple, la requête suivante utilise le même index que la précédente:

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

Pour cet index,

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

L'index précédent peut satisfaire les deux requêtes suivantes:

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

et

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

Optimiser la sélection de vos index

Cette section décrit les caractéristiques de performances de la fusion d'index et les deux possibilités d'optimisation pouvant être envisagées :

  • Ajouter des index composites pour accélérer les requêtes qui s'appuient sur des index fusionnés
  • Réduire le nombre d'index composites en exploitant des index fusionnés

Performances de fusion d'index

Dans une fusion d'index, Firestore en mode Datastore fusionne efficacement les index à l'aide d'un algorithme de jointure par fusion en zigzag. Grâce à cet algorithme, le mode Datastore joint les correspondances potentielles de plusieurs analyses d'index pour produire un ensemble de résultats qui correspond à une requête. La fusion d'index combine les composants de filtre au moment de la lecture plutôt qu'au moment de l'écriture. Contrairement à la plupart des requêtes de Firestore en mode Datastore où les performances ne dépendent que de la taille de l'ensemble de résultats, les performances des requêtes de fusion d'index dépendent des filtres de la requête et du nombre de correspondances potentielles dont la base de données doit tenir compte.

Les performances optimales d'une fusion d'index sont obtenues lorsque chaque correspondance potentielle dans un index satisfait les filtres de requête. Dans ce cas, les performances se traduisent par O(R * I), où R correspond à la taille de l'ensemble de résultats et I au nombre d'index analysés.

Le pire des cas se produit lorsque la base de données doit tenir compte de nombreuses correspondances potentielles, mais que peu d'entre elles répondent aux filtres de requête. Dans ce cas, les performances se traduisent par O(S), où S est la taille du plus petit ensemble d'entités potentielles d'une seule analyse d'index.

Les performances réelles dépendent de la forme des données. Le nombre moyen d'entités prises en compte pour chaque résultat renvoyé est de O(S/(R * I)). Les requêtes sont moins performantes lorsque plusieurs entités correspondent à chaque analyse d'index, mais que peu d'entités correspondent à la requête dans son ensemble, ce qui signifie que R est petit et S important.

Quatre éléments permettent d'atténuer ce risque :

  • Le planificateur de requêtes ne recherche pas d'entité tant qu'il ignore si l'entité correspond à la requête entière.

  • L'algorithme en zigzag n'a pas besoin de trouver tous les résultats pour renvoyer le résultat suivant. Si vous demandez les 10 premiers résultats, vous ne payez que la latence nécessaire à leur obtention.

  • L'algorithme en zigzag ignore une grande partie des faux positifs. Le pire des cas ne se produit que si les résultats de faux positifs sont parfaitement imbriqués (par ordre de tri) entre les analyses.

  • La latence dépend du nombre d'entités trouvées dans chaque analyse d'index, et non du nombre d'entités correspondant à chaque filtre. Comme indiqué dans la section suivante, vous pouvez ajouter des index composites pour améliorer les performances de la fusion d'index.

Accélérer une requête de fusion d'index

Lorsque Firestore en mode Datastore effectue la fusion d'index, chaque analyse d'index est souvent mappée sur un seul filtre de la requête. Vous pouvez améliorer les performances des requêtes en ajoutant des index composites correspondant à plusieurs filtres de la requête.

Examinez cette requête :

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"),
    ],
)

Chaque filtre correspond à une analyse d'index dans les index intégrés suivants :

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

Si vous ajoutez l'index composite Index(Photo, owner_id, size), la requête correspond à deux analyses d'index au lieu de trois :

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

Prenons l'exemple d'un scénario comportant un grand nombre de grandes images et d'images en noir et blanc, mais peu d'images panoramiques de grande taille. En utilisant la fusion d'index intégrés, le filtrage des requêtes pour les images panoramiques et les images en noir et blanc sera lent :

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)]
)

Pour améliorer les performances des requêtes, vous pouvez réduire la valeur de S (le plus petit ensemble d'entités dans une seule analyse d'index) dans O(S/(R * I)) en ajoutant l'index composite suivant :

Index(Photo, size, coloration)

Comparé à l'utilisation de deux index intégrés, cet index composite produit moins de résultats potentiels pour les deux mêmes filtres de requête. Cette approche améliore considérablement les performances au prix d'un index supplémentaire.

Réduire le nombre d'index composites à l'aide de la fusion d'index

Bien que les index composites qui correspondent exactement aux filtres d'une requête donnent les meilleurs résultats, il n'est pas toujours possible d'en ajouter un pour chaque combinaison de filtres. Vous devez équilibrer vos index composites par rapport aux éléments suivants :

  • Limites d'un index composite :

    Limite Montant
    Nombre maximal d'index composites pour une base de données
    Valeur globale maximale pour les tailles des entrées d'index composite d'une entité 2 Mio
    Valeur globale maximale par entité pour les éléments suivants :
    • nombre de valeurs de propriété indexée
    • nombre d'entrées d'index composite
    20 000
  • Coûts de stockage de chaque index supplémentaire.
  • Effets sur la latence d'écriture.

Des problèmes d'indexation se produisent souvent avec les champs à valeurs multiples, tels que la propriété tag des entités Photo.

Imaginons que la fonction de filtrage Photo doive désormais accepter les clauses par ordre décroissant sur la base de quatre propriétés supplémentaires :

Photo
Valeur Type de valeur Description
date_added Entier Date/Heure
rating Float Note globale des utilisateurs
comment_count Integer Le nombre de commentaires
download_count Entier Nombre de téléchargements

Si vous ne tenez pas compte du champ tag, il est possible de sélectionner des index composites qui correspondent à chaque combinaison de filtres 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)

Le nombre total d'index composites est le suivant :

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

Si vous essayez d'utiliser jusqu'à trois filtres tag, le nombre total d'index composites est le suivant:

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

Les index qui incluent des propriétés à plusieurs valeurs telles que tag entraînent également des problèmes d'index exponentiels qui augmentent les coûts de stockage et la latence d'écriture.

Pour appliquer les filtres au champ tag de cette fonctionnalité, vous pouvez réduire le nombre total d'index en vous appuyant sur des index fusionnés. L'ensemble d'index composites suivant est le minimum requis pour accepter la fonction de filtre Photo avec classement :

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)

Le nombre d'index composites définis est le suivant :

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

La fusion d'index offre également ces avantages :

  • Elle permet à une entité Photo d'accepter jusqu'à 1 000 tags, sans limite du nombre de filtres tag par requête.
  • Elle réduit le nombre total d'index, ce qui réduit les coûts de stockage et la latence d'écriture.

Sélectionner des index pour votre application

Il existe deux approches pour sélectionner des index optimaux pour votre base de données en mode Datastore :

  • Utiliser la fusion d'index pour accepter d'autres requêtes

    • Nécessite moins d'index composites
    • Réduit le coût du stockage par entité
    • Améliore la latence d'écriture
    • Évite les index exponentiels
    • Les performances dépendent de la forme des données
  • Définir un index composite qui correspond à plusieurs filtres dans une requête

    • Améliore les performances des requêtes
    • Performances des requêtes cohérentes et non dépendantes de la forme des données
    • Doit rester sous la limite des index composites
    • Augmente le coût du stockage par entité
    • Augmentation de la latence d'écriture

Lors de la détermination des index optimaux pour votre application, la réponse peut changer à mesure que la forme des données évolue. L'analyse des performances à partir d'un échantillon de données donne une idée relativement précise des requêtes courantes de votre application et de celles qui sont ralenties. Ces informations permettent de déterminer s'il convient d'ajouter des index afin d'améliorer les performances des requêtes à la fois courantes et lentes.