ProHoster > Blog > administration > PostgreSQL Query Profiler : comment faire correspondre le plan et la requête
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête
Beaucoup de ceux qui utilisent déjà expliquer.tensor.ru - notre service de visualisation de plans PostgreSQL n'est peut-être pas conscient de l'un de ses super pouvoirs - transformer une partie difficile à lire du journal du serveur...
... dans une requête magnifiquement conçue avec des astuces contextuelles pour les nœuds de plan correspondants :
Dans cette transcription de la deuxième partie de son rapport à PGConf.Russia 2020 Je vais vous dire comment nous avons réussi à y parvenir.
La transcription de la première partie, dédiée aux problèmes typiques de performances des requêtes et à leurs solutions, se trouve dans l'article "Recettes pour les requêtes SQL en difficulté".
Tout d'abord, commençons à colorier - et nous ne colorerons plus le plan, nous l'avons déjà coloré, nous l'avons déjà beau et compréhensible, mais une demande.
Il nous a semblé qu'avec une telle « feuille » non formatée, la requête extraite du journal semble très moche et donc peu pratique.
Surtout lorsque les développeurs « collent » le corps de la requête dans le code (il s'agit bien sûr d'un anti-modèle, mais cela arrive) sur une seule ligne. Horrible!
Dessinons cela d'une manière ou d'une autre plus joliment.
Et si nous pouvons dessiner cela magnifiquement, c'est-à-dire démonter et reconstituer le corps de la demande, alors nous pouvons alors « attacher » un indice à chaque objet de cette demande - ce qui s'est passé au point correspondant du plan.
Arbre de syntaxe de requête
Pour ce faire, la demande doit d'abord être analysée.
Parce que nous avons le cœur du système fonctionne sur NodeJS, puis nous avons créé un module pour cela, vous pouvez trouvez-le sur GitHub. En fait, il s’agit de « liaisons » étendues aux composants internes de l’analyseur PostgreSQL lui-même. Autrement dit, la grammaire est simplement compilée en binaire et des liaisons y sont établies à partir de NodeJS. Nous avons pris comme base les modules d'autres personnes - il n'y a pas de grand secret ici.
Nous alimentons le corps de la requête en entrée de notre fonction - en sortie, nous obtenons un arbre de syntaxe analysé sous la forme d'un objet JSON.
Nous pouvons maintenant parcourir cet arbre dans la direction opposée et assembler une requête avec les retraits, la coloration et le formatage souhaités. Non, ce n'est pas personnalisable, mais il nous a semblé que ce serait pratique.
Mappage des nœuds de requête et de plan
Voyons maintenant comment combiner le plan que nous avons analysé dans la première étape et la requête que nous avons analysée dans la seconde.
Prenons un exemple simple : nous avons une requête qui génère un CTE et le lit deux fois. Il génère un tel plan.
Cela signifie que si nous voyons une génération CTE quelque part dans la requête et un nœud quelque part dans le plan CTE, alors ces nœuds « se battent » définitivement les uns contre les autres, nous pouvons immédiatement les combiner.
Problème avec un astérisque: Les CTE peuvent être imbriqués.
Il y en a de très mal imbriqués, et même du même nom. Par exemple, vous pouvez à l'intérieur CTE A faire CTE X, et au même niveau à l'intérieur CTE B refais-le CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Lorsque vous comparez, vous devez comprendre cela. Comprendre cela « avec vos yeux » – même en voyant le plan, même en voyant le corps de la demande – est très difficile. Si votre génération CTE est complexe, imbriquée et que les requêtes sont volumineuses, alors elle est complètement inconsciente.
UNION
Si nous avons un mot-clé dans la requête UNION [ALL] (opérateur de jointure de deux échantillons), alors dans le plan il correspond soit à un nœud Append, ou une Recursive Union.
Ce qui est "au-dessus" au-dessus UNION - c'est le premier descendant de notre nœud, qui est « en dessous » - le second. Si à travers UNION on a plusieurs blocs "collés" à la fois, alors Append-il n'y aura toujours qu'un seul nœud, mais il n'aura pas deux, mais plusieurs enfants - dans l'ordre dans lequel ils vont, respectivement :
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Problème avec un astérisque: à l'intérieur de la génération d'échantillonnage récursif (WITH RECURSIVE) peut également être plusieurs UNION. Mais seul le tout dernier bloc après le dernier est toujours récursif UNION. Tout ce qui précède est un, mais différent UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Vous devez également être capable de « ressortir » de tels exemples. Dans cet exemple, nous voyons que UNION-il y avait 3 segments dans notre demande. En conséquence, un UNION match Append-nœud, et à l'autre - Recursive Union.
Données en lecture-écriture
Tout est disposé, on sait désormais à quelle pièce de la demande correspond quelle pièce du plan. Et dans ces pièces, nous pouvons facilement et naturellement retrouver les objets « lisibles ».
D’un point de vue requête, on ne sait pas s’il s’agit d’une table ou d’un CTE, mais ils sont désignés par le même nœud RangeVar. Et en termes de « lisibilité », il s'agit aussi d'un ensemble de nœuds assez limité :
Seq Scan on [tbl]
Bitmap Heap Scan on [tbl]
Index [Only] Scan [Backward] using [idx] on [tbl]
CTE Scan on [cte]
Insert/Update/Delete on [tbl]
Nous connaissons la structure du plan et de la requête, nous connaissons la correspondance des blocs, nous connaissons les noms des objets - nous effectuons une comparaison biunivoque.
Encore tâche "avec un astérisque". Nous prenons la demande, l'exécutons, nous n'avons aucun alias - nous la lisons simplement deux fois à partir du même CTE.
Nous examinons le plan : quel est le problème ? Pourquoi avions-nous un pseudonyme ? Nous ne l'avons pas commandé. Où trouve-t-il un tel « numéro numéro » ?
PostgreSQL l'ajoute lui-même. Tu as juste besoin de comprendre ça juste un tel pseudonyme pour nous, à des fins de comparaison avec le plan, cela n'a aucun sens, c'est simplement ajouté ici. Ne prêtons pas attention à lui.
La seconde tâche "avec un astérisque": si nous lisons depuis une table partitionnée, alors nous obtiendrons un nœud Append ou Merge Append, qui sera composé d'un grand nombre d'« enfants », et dont chacun sera en quelque sorte Scan'om de la section table : Seq Scan, Bitmap Heap Scan ou Index Scan. Mais, dans tous les cas, ces « enfants » ne seront pas des requêtes complexes - c'est ainsi que ces nœuds peuvent être distingués des Append à UNION.
Nous comprenons aussi de tels nœuds, les rassemblons « en un seul tas » et disons : «tout ce que vous lisez sur megatable est ici et dans l'arborescence".
Nœuds de réception de données « simples »
Values Scan correspond au plan VALUES dans la demande.
Result est une demande sans FROM comme SELECT 1. Ou quand vous avez une expression volontairement fausse dans WHERE-block (puis l'attribut apparaît One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Mais avec les requêtes imbriquées, tout est plus compliqué - malheureusement, elles ne se transforment pas toujours en InitPlan/SubPlan. Parfois, ils se transforment en ... Join ou ... Anti Join, surtout quand tu écris quelque chose comme WHERE NOT EXISTS .... Et ici, il n'est pas toujours possible de les combiner - dans le texte du plan il n'y a pas d'opérateurs correspondant aux nœuds du plan.
Encore tâche "avec un astérisque": quelques VALUES dans la demande. Dans ce cas et dans le plan vous obtiendrez plusieurs nœuds Values Scan.
Les suffixes "numérotés" aideront à les distinguer les uns des autres - ils sont ajoutés exactement dans l'ordre dans lequel ceux correspondants se trouvent VALUES-blocs le long de la demande de haut en bas.
Traitement des données
Il semble que tout ait été réglé dans notre demande. Il ne reste plus que Limit.
Mais ici, tout est simple - des nœuds tels que Limit, Sort, Aggregate, WindowAgg, Unique « mapper » un à un les opérateurs correspondants dans la requête, s'ils sont là. Il n’y a pas de « stars » ni de difficultés ici.
INSCRIPTION
Des difficultés surgissent lorsque l'on veut combiner JOIN Entre elles. Ce n'est pas toujours possible, mais c'est possible.
Du point de vue de l'analyseur de requêtes, nous avons un nœud JoinExpr, qui a exactement deux enfants - gauche et droite. C'est donc ce qui se trouve « au-dessus » de votre JOIN et ce qui est écrit « en dessous » dans la demande.
Et du point de vue du plan, ce sont deux descendants de certains * Loop/* Join-nœud. Nested Loop, Hash Anti Join,... - quelque chose comme ca.
Utilisons une logique simple : si nous avons des tables A et B qui se « rejoignent » dans le plan, alors dans la requête elles pourraient être localisées soit A-JOIN-BOu B-JOIN-A. Essayons de combiner de cette façon, essayons de combiner dans l'autre sens, et ainsi de suite jusqu'à ce que nous soyons à court de telles paires.
Redessinons-le sous forme de graphiques - oh, ça ressemble déjà à quelque chose !
Notons que nous avons des nœuds qui ont simultanément des enfants B et C – peu nous importe dans quel ordre. Combinons-les et retournons l'image du nœud.
Regardons à nouveau. Nous avons maintenant des nœuds avec des enfants A et des paires (B + C) - également compatibles avec eux.
Super! Il s'avère que nous sommes ces deux-là JOIN de la demande avec les nœuds du plan ont été combinés avec succès.
Hélas, ce problème n'est pas toujours résolu.
Par exemple, si dans une demande A JOIN B JOIN C, et dans le plan, tout d'abord, les nœuds « externes » A et C étaient connectés. Mais il n'y a pas un tel opérateur dans la requête, nous n'avons rien à souligner, rien à quoi attacher un indice. C'est pareil avec la "virgule" quand tu écris A, B.
Mais, dans la plupart des cas, presque tous les nœuds peuvent être « déliés » et vous pouvez obtenir ce type de profilage sur la gauche à temps – littéralement, comme dans Google Chrome lorsque vous analysez du code JavaScript. Vous pouvez voir combien de temps il a fallu à chaque ligne et à chaque instruction pour « s’exécuter ».
Et pour vous permettre d'utiliser tout cela plus facilement, nous avons réalisé un stockage l'archive, où vous pouvez enregistrer et retrouver plus tard vos projets ainsi que les demandes associées ou partager le lien avec quelqu'un.
Si vous avez juste besoin de mettre une requête illisible sous une forme adéquate, utilisez notre « normalisateur ».