Optimiser les performances des requêtes

Pour résoudre les problèmes de requêtes lentes, utilisez Expliquer la requête afin d'obtenir le plan d'exécution de la requête et le profil d'exécution du runtime. La section suivante décrit les étapes à suivre pour optimiser les performances des requêtes en fonction du profil d'exécution :

Limiter le nombre de résultats

Utilisez le champ "Enregistrements renvoyés" dans l'arborescence d'exécution pour déterminer si la requête renvoie de nombreux documents. Envisagez de limiter le nombre de documents renvoyés à l'aide de l'étape limit(...). Cela réduit la taille en octets sérialisés des résultats lorsqu'ils sont renvoyés aux clients sur le réseau. Dans les cas où le nœud Limit est précédé d'un nœud MajorSort, le moteur de requête peut fusionner les nœuds Limit et MajorSort, et remplacer une matérialisation et un tri complets en mémoire par un tri TopN, ce qui réduit la quantité de mémoire requise pour la requête.

Limiter la taille du document de résultat

Envisagez de limiter la taille du document renvoyé en utilisant select(...) pour ne renvoyer que les champs requis ou remove_fields(...) pour supprimer les champs trop volumineux. Cela permet de réduire les coûts de calcul et de mémoire liés au traitement des résultats intermédiaires, ainsi que la taille en octets sérialisés des résultats lorsqu'ils sont renvoyés aux clients sur le réseau. Dans les cas où tous les champs référencés dans la requête sont couverts par un index régulier, cela permet également à la requête d'être entièrement couverte par l'analyse de l'index, ce qui évite d'avoir à extraire des documents du stockage principal.

Utiliser des index

Suivez les instructions ci-dessous pour configurer et optimiser les index.

Déterminer si la requête utilise un index

Pour savoir si la requête utilise un index, vérifiez les nœuds feuilles dans l'arborescence d'exécution. Si le nœud feuille de l'arborescence d'exécution est un nœud TableScan, cela signifie que la requête n'utilise pas d'index et qu'elle analyse les documents du stockage principal. Si un index est utilisé, le nœud feuille de l'arborescence d'exécution affiche l'ID et les champs de l'index.

Identifier un meilleur index

Un index est utile pour une requête s'il peut réduire le nombre de documents que le moteur de requête doit extraire du stockage principal ou si l'ordre de ses champs peut répondre aux exigences de tri de la requête.

Si un index est utilisé pour une requête, mais que le moteur de requête récupère et supprime toujours de nombreux documents (comme l'indique un nœud "Scan" qui renvoie de nombreux enregistrements, suivi d'un nœud "Filter" qui renvoie peu d'enregistrements), cela signifie que le prédicat de requête satisfait à l'aide de l'index n'est pas sélectif. Pour créer un index plus adapté, consultez Créer des index.

Si un index est utilisé pour une requête, mais que le moteur de requête effectue toujours un réordonnancement en mémoire de l'ensemble de résultats, comme l'indique un nœud MajorSort dans l'arborescence d'exécution de la requête, cela signifie que l'index utilisé ne peut pas être utilisé pour répondre à l'exigence de tri de la requête. Pour créer un index plus adapté, consultez la section suivante.

Créer des index

Suivez la documentation sur la gestion des index pour créer des index. Pour vous assurer que votre requête peut utiliser des index, créez des index standards (et non Multikey) avec des champs dans l'ordre suivant :

  1. Tous les champs qui seront utilisés dans les opérateurs d'égalité. Pour maximiser les chances de réutilisation des requêtes, ordonnez les champs par ordre décroissant de leur fréquence dans les opérateurs d'égalité des requêtes.
  2. Tous les champs sur lesquels le tri sera effectué (dans le même ordre).
  3. Champs qui seront utilisés dans les opérateurs d'intervalle ou d'inégalité par ordre décroissant de sélectivité des contraintes de requête.
  4. Champs qui seront renvoyés dans le cadre d'une requête dans l'index : l'inclusion de ces champs dans l'index permet à l'index de couvrir la requête et d'éviter d'avoir à récupérer le document à partir du stockage principal.

Forcer une analyse d'index ou de table

Lorsque vous interrogez Firestore en mode natif, il utilise automatiquement tous les index susceptibles d'améliorer l'efficacité de la requête. Par conséquent, vous n'avez pas besoin de spécifier un index pour vos requêtes. Toutefois, pour les requêtes critiques pour votre charge de travail, nous vous recommandons d'utiliser l'option forceIndex afin d'obtenir des performances plus cohérentes.

Dans certains cas, Firestore en mode natif peut choisir un index qui entraîne une augmentation de la latence des requêtes. Si vous avez suivi la procédure de dépannage des régressions de performances et vérifié qu'il est judicieux d'essayer un autre index pour la requête, vous pouvez spécifier l'index à l'aide de l'option forceIndex.

Vous pouvez utiliser l'option forceIndex sur n'importe quelle étape d'entrée des opérations de pipeline pour remplacer le plan de requête par défaut de Firestore en mode natif et spécifier un index à utiliser, ou pour forcer une analyse de table.

Forcer un index spécifique

Pour forcer la requête à utiliser un index spécifique, indiquez l'ID de l'index sous forme de chaîne à l'option forceIndex. Vous trouverez l'ID d'index dans la console ou dans les messages d'erreur.

L'exemple suivant force le planificateur à utiliser l'index avec l'ID CICAgOi36pgK :

// Force Planner to use Index ID CICAgOi36pgK
db.pipeline()
  .collectionGroup({ collectionId: "customers", forceIndex: "CICAgOi36pgK" })
  .limit(100)

Voici quelques cas d'utilisation pour forcer un index spécifique :

  • Tester les performances de différents index.
  • S'assurer qu'un index spécifique et connu comme optimal est utilisé pour une requête.
  • Remplacer l'optimiseur lorsque son choix par défaut est sous-optimal pour une requête spécifique.

Si l'index spécifié est introuvable, la requête échoue.

Forcer une analyse de table

Une analyse de table lit les documents de la collection ou du groupe de collections sans utiliser d'index secondaires. Pour forcer une analyse de table, définissez forceIndex sur primary.

L'exemple suivant force une analyse de table :

// Force Planner to only do a Full-Table Scan
db.pipeline()
  .collectionGroup({ collectionId: "customers", forceIndex: "primary" })
  .limit(100)

Vous pouvez utiliser une analyse de table dans les cas suivants :

  • Pour les très petites collections où la surcharge d'index n'est pas justifiée.
  • Pour les requêtes qui accèdent à la plupart des documents d'une collection.
  • Pour le débogage et les comparaisons de performances.

Utiliser forceIndex avec l'explication de la requête

Vous pouvez utiliser Query Explain, en particulier avec l'option analyze, pour observer les effets de forceIndex :

  • Vérifiez que Firestore en mode natif a utilisé l'index spécifié dans forceIndex en vérifiant les nœuds feuilles de l'arborescence d'exécution pour l'ID d'index.
  • Vérifiez qu'un nœud TableScan apparaît dans le plan lorsque vous utilisez forceIndex: "primary".
  • Comparez les métriques de performances (latence, documents analysés et entrées d'index analysées, par exemple) avec et sans forceIndex pour affiner les performances des requêtes.

Bonnes pratiques pour forceIndex

Bien que forceIndex offre un meilleur contrôle sur l'exécution des requêtes, l'optimiseur de requêtes de Firestore en mode natif est généralement efficace pour la plupart des cas d'utilisation. Tenez compte des bonnes pratiques suivantes lorsque vous utilisez forceIndex :

  • Utilisez forceIndex judicieusement. Si vous constatez des performances sous-optimales avec le plan de requête par défaut, utilisez Query Explain pour diagnostiquer le problème avant de forcer un index.
  • Lorsque vous utilisez forceIndex, veillez à tester vos requêtes avec des volumes de données réalistes pour comprendre leurs caractéristiques de performances et de coûts.
  • Évitez d'utiliser forceIndex: "primary" sur de grandes collections dans les environnements de production.