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...

PostgreSQL Query Profiler : comment faire correspondre le plan et la requête
... dans une requête magnifiquement conçue avec des astuces contextuelles pour les nœuds de plan correspondants :

PostgreSQL Query Profiler : comment faire correspondre le plan et la requête
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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

CTE

Si vous le regardez attentivement, jusqu'à la version 12 (ou à partir de celle-ci avec le mot-clé MATERIALIZED)formation Le CTE est un obstacle absolu pour le planificateur.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête
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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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 »

PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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

Result  (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
  One-Time Filter: false

Function Scan « mapper » aux SRF du même nom.

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

INSCRIPTION

Des difficultés surgissent lorsque l'on veut combiner JOIN Entre elles. Ce n'est pas toujours possible, mais c'est possible.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.

Prenons notre arbre syntaxique, prenons notre plan, regardons-les... pas pareil !
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

Redessinons-le sous forme de graphiques - oh, ça ressemble déjà à quelque chose !
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

Regardons à nouveau. Nous avons maintenant des nœuds avec des enfants A et des paires (B + C) - également compatibles avec eux.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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.
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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 ».
PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

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 ».

PostgreSQL Query Profiler : comment faire correspondre le plan et la requête

Source: habr.com

Ajouter un commentaire