La guerre des flammes de cette semaine sur la liste des performances de pgsql tourne une fois de plus autour du fait que PostgreSQL n'a pas la syntaxe d'indication traditionnelle disponible dans d'autres bases de données. Il y a un mélange de raisons techniques et pragmatiques derrière cela :
- L'introduction d'indices est une source courante de problèmes ultérieurs, car corriger un emplacement de requête une seule fois dans un cas particulier n'est pas une approche très robuste. Au fur et à mesure que votre ensemble de données s'agrandit, et peut-être change également de distribution, l'idée à laquelle vous faisiez allusion lorsqu'il était petit peut devenir une idée de plus en plus mauvaise.
- L'ajout d'une interface de conseil utile compliquerait le code de l'optimiseur, qui est déjà assez difficile à maintenir tel quel. Une partie de la raison pour laquelle PostgreSQL fonctionne aussi bien qu'il exécute des requêtes est parce que le code convivial ("nous pouvons cocher les indices sur notre liste de fonctionnalités de comparaison des fournisseurs !") qui ne se rentabilise pas en termes de création de la base de données mieux pour justifier son maintien continu, est rejetée par la politique. Si cela ne fonctionne pas, il ne sera pas ajouté. Et lorsqu'ils sont évalués objectivement, les indices sont en moyenne un problème plutôt qu'une solution.
- Le genre de problèmes qui fonctionnent avec les indices peuvent être des bogues de l'optimiseur. La communauté PostgreSQL répond aux véritables bogues dans l'optimiseur plus rapidement que quiconque dans l'industrie. Demandez autour de vous et vous n'aurez pas besoin de rencontrer de nombreux utilisateurs de PostgreSQL avant d'en trouver un qui a signalé un bogue et qui l'a vu être corrigé le lendemain.
Maintenant, la principale réponse tout à fait valide à la découverte d'indices manquants, normalement de la part des administrateurs de base de données qui y sont habitués, est "comment puis-je gérer un bogue d'optimiseur lorsque je le rencontre ?" Comme tous les travaux techniques de nos jours, il y a généralement une pression énorme pour obtenir la solution la plus rapide possible lorsqu'un problème de mauvaise requête apparaît.
Si PostgreSQL n'avait pas de moyens de gérer cette situation, il n'y aurait pas de bases de données PostgreSQL de production sérieuses . La différence est que les choses que vous ajustez dans cette base de données sont plus enracinées pour influencer les décisions que l'optimiseur prend déjà d'une manière assez subtile, plutôt que de simplement vous dire quoi faire. Ce sont des indices au sens littéral du terme, ils n'ont tout simplement pas l'interface utilisateur pour suggérer que les utilisateurs d'autres bases de données qui découvrent PostgreSQL recherchent.
Dans cet esprit, examinons ce que vous pouvez faire dans PostgreSQL pour contourner les mauvais plans de requête et les bogues de l'optimiseur, en particulier les problèmes que beaucoup de gens semblent penser ne peuvent être résolus qu'avec des indices :
- join_collapse_limit : Cela ajuste la flexibilité dont dispose l'optimiseur pour réorganiser les jointures de plusieurs tables. Normalement, il essaie toutes les combinaisons possibles lorsque les jointures peuvent être réorganisées (ce qui est la plupart du temps, sauf si vous utilisez une jointure externe). Abaisser join_collapse_limit, peut-être même à 1, supprime tout ou partie de cette flexibilité. Avec la valeur 1, vous obtiendrez les jointures dans l'ordre dans lequel vous les avez écrites, point final. La planification d'un grand nombre de jointures est l'une des choses les plus difficiles à faire pour l'optimiseur; chaque jointure amplifie les erreurs d'estimation et augmente le temps de planification des requêtes. Si la nature sous-jacente de vos données rend évident l'ordre des jointures, et que vous ne vous attendez pas à ce que cela change, une fois que vous avez déterminé le bon ordre, vous pouvez le verrouiller à l'aide de ce paramètre.
- random_page_cost : Par défaut sur 4,0, ce paramètre définit le coût de la recherche sur le disque pour trouver une page aléatoire sur le disque, par rapport à une valeur de référence de 1,0. Maintenant, en réalité, si vous mesurez le rapport entre les E/S aléatoires et séquentielles sur des disques durs classiques, vous constaterez que ce nombre est plus proche de 50. Alors pourquoi 4.0 ? Premièrement, parce que cela a mieux fonctionné que des valeurs plus élevées dans les tests communautaires. Deuxièmement, dans de nombreux cas, les données d'index en particulier seront mises en cache dans la mémoire, ce qui réduira le coût effectif de lecture de ces valeurs. Si, par exemple, votre index est mis en cache à 90 % dans la RAM, cela signifie que 10 % du temps, vous ferez l'opération qui coûte 50 fois plus cher ; cela rendrait votre random_page_cost effectif d'environ 5. Ce type de situation réelle est la raison pour laquelle la valeur par défaut a du sens où elle se trouve. Je vois normalement les index populaires obtenir> 95% de cache en mémoire. Si votre index est en fait beaucoup plus susceptible que cela d'être tous en RAM, réduire random_page_cost jusqu'à juste au-dessus de 1,0 peut être un choix raisonnable, pour refléter qu'il n'est pas plus cher que toute autre lecture. Dans le même temps, les recherches aléatoires sur des systèmes très occupés peuvent être beaucoup plus coûteuses que ce à quoi vous vous attendiez en regardant simplement des simulations mono-utilisateur. J'ai dû définir random_page_cost aussi haut que 60 pour que la base de données cesse d'utiliser des index lorsque le planificateur estimait mal leur coût. Généralement, cette situation provient d'une erreur d'estimation de sensibilité de la part du planificateur - si vous numérisez plus d'environ 20 % d'une table, le planificateur sait que l'utilisation d'un balayage séquentiel sera beaucoup plus efficace qu'un balayage d'index. La situation laide où j'ai dû forcer ce comportement à se produire beaucoup plus tôt que cela s'est produite lorsque le planificateur s'attendait à ce que 1 % des lignes soient renvoyées, mais c'était en fait plus proche de 15 %.
- work_mem : ajuste la quantité de mémoire disponible pour les requêtes effectuant le tri, le hachage et d'autres opérations similaires basées sur la mémoire. Il ne s'agit que d'une ligne directrice approximative pour les requêtes, pas d'une limite stricte, et un seul client peut finir par utiliser des multiples de work_mem lors de l'exécution d'une requête. Par conséquent, vous devez faire attention à ne pas définir cette valeur trop élevée dans le fichier postgresql.conf. Ce que vous pouvez faire à la place, cependant, il le définit avant d'exécuter une requête qui bénéficie vraiment d'avoir de la mémoire supplémentaire pour stocker les données de tri ou de hachage. Vous pouvez parfois trouver ces requêtes en journalisant les requêtes lentes à l'aide de log_min_duration_statement. Vous pouvez également les trouver en activant log_temp_files, qui enregistrera chaque fois que work_mem est trop petit, et donc les opérations de tri se déversent sur le disque au lieu de se produire en mémoire.
- OFFSET 0 : PostgreSQL réorganisera les sous-requêtes sous la forme d'une jointure, afin qu'il puisse ensuite utiliser la logique d'ordre de jointure habituelle pour l'optimiser. Dans certains cas, cette décision peut être vraiment mauvaise, car le genre de choses que les gens ont tendance à écrire en tant que sous-requêtes semblent un peu plus difficiles à estimer pour une raison quelconque (je dis cela en fonction du nombre de requêtes aussi gênantes que je vois). Une astuce sournoise que vous pouvez faire pour empêcher cette logique est de placer OFFSET 0 à la fin de la sous-requête. Cela ne change rien aux résultats, mais l'insertion du type de nœud de requête Limit utilisé pour exécuter OFFSET empêchera le réarrangement. La sous-requête s'exécutera alors toujours comme la plupart des gens s'y attendent :en tant que nœud de requête isolé.
- enable_seqscan, enable_indexscan, enable_bitmapscan : La désactivation de l'une de ces fonctionnalités pour rechercher des lignes dans une table est un marteau assez important pour recommander fortement d'éviter ce type d'analyse (ce qui ne l'empêche pas toujours - s'il n'y a aucun moyen d'exécuter votre plan mais un seqscan, vous obtiendrez un seqscan même si les paramètres sont désactivés). La principale chose pour laquelle je les recommande n'est pas de résoudre les requêtes, c'est d'expérimenter EXPLAIN et de voir pourquoi l'autre type d'analyse a été préféré.
- enable_nestloop, enable_hashjoin, enable_mergejoin : si vous pensez que votre problème est le type de jointure utilisé plutôt que la façon dont les tables sont lues, essayez de désactiver le type que vous voyez dans votre plan à l'aide de l'un de ces paramètres, puis exécutez EXPLAIN de nouveau. Des erreurs dans les estimations de sensibilité peuvent facilement donner l'impression qu'une jointure est plus ou moins efficace qu'elle ne l'est réellement. Et, encore une fois, voir comment le plan change avec la méthode de jointure actuelle désactivée peut être très instructif pour savoir pourquoi il a choisi celle-là en premier lieu.
- enable_hashagg, enable_material : Ces fonctionnalités sont relativement nouvelles dans PostgreSQL. L'utilisation agressive de Hash Aggregation a été introduite dans la version 8.4, et une matérialisation plus agressive dans la version 9.0. Si vous voyez ces types de nœuds dans votre sortie EXPLAIN et qu'ils semblent faire quelque chose de mal, parce que ce code est tellement plus récent, il est un peu plus susceptible d'avoir une limitation ou un bogue que certaines des fonctionnalités plus anciennes. Si vous aviez un plan qui fonctionnait bien dans les anciennes versions de PostgreSQL, mais qui utilise l'un de ces types de nœuds et semble donc fonctionner bien moins bien, la désactivation de ces fonctionnalités peut parfois vous ramener au comportement antérieur, ainsi que mettre en lumière pourquoi l'optimiseur a fait la mauvaise chose en tant que retour d'information utile. Notez que c'est généralement la manière dont les fonctionnalités plus avancées ont tendance à être introduites dans PostgreSQL : avec la possibilité de la désactiver à des fins de dépannage, s'il s'avère qu'il y a une régression du plan par rapport à la façon dont les versions précédentes exécutaient les choses.
- cursor_tuple_fraction : si vous n'avez pas l'intention de relire toutes les lignes d'une requête, vous devez utiliser un curseur pour l'implémenter. Dans ce cas, l'optimiseur essaie de déterminer s'il vous renvoie rapidement la première ligne ou s'il préfère optimiser l'ensemble de la requête, en fonction de ce paramètre. Par défaut, la base de données suppose que vous relisez 10 % de la requête lorsque vous utilisez un curseur. L'ajustement de ce paramètre vous permet de l'orienter vers l'attente que vous lisiez moins ou plus que cela.
Tous ces paramètres et ajustements de requête doivent être considérés comme des ajustements de triage. Vous ne voulez pas courir avec ceux-ci en place pour toujours (sauf peut-être pour join_collapse_limit). Vous les utilisez pour vous sortir d'un blocage, puis nous espérons que vous découvrirez quelle est la véritable cause sous-jacente du mauvais plan - mauvaises statistiques, limitation / bogue de l'optimiseur ou autre chose - et ensuite résoudre le problème de cette direction. Plus vous poussez le comportement de l'optimiseur dans une direction, plus vous êtes exposé aux changements futurs de vos données, ce qui fait que cette poussée n'est plus correcte. Si vous les utilisez correctement, comme moyen d'étudier pourquoi vous avez le mauvais plan (l'approche que j'ai utilisée dans le chapitre sur l'optimisation des requêtes de PostgreSQL 9.0 Haute Performance), la façon dont vous faites allusion aux choses dans PostgreSQL devrait vous faire quitter chaque exécution- avec un mauvais comportement de l'optimiseur un peu plus averti sur la façon d'éviter cette classe de problèmes à l'avenir