Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Le rapport prĂ©sente certaines approches permettant de surveiller les performances des requĂȘtes SQL, lorsqu'il y en a des millions par jour,, et des serveurs PostgreSQL contrĂŽlĂ©s - des centaines.

Quelles solutions techniques nous permettent de traiter efficacement un tel volume d'informations, et comment cela simplifie-t-il la vie des développeurs ordinaires.

Lire la vidéo

À qui cela pourrait-il intĂ©resser l'analyse de problĂšmes spĂ©cifiques et les diffĂ©rentes techniques d'optimisations des requĂȘtes SQL et les solutions aux problĂšmes DBA typiques dans PostgreSQL - vous pouvez Ă©galement consulter une sĂ©rie d'articles Ă  ce sujet.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)
Je m'appelle Kirill Borovikov, je représente la société « Tensor ». Plus précisément, je me spécialise dans le travail avec des bases de données dans notre entreprise.

Aujourd'hui, je vais vous expliquer comment nous optimisons les requĂȘtes, lorsque vous devez non pas « dĂ©terrer » les performances d'une seule requĂȘte, mais rĂ©soudre le problĂšme de maniĂšre massive. Quand il y a des millions de requĂȘtes et que vous devez trouver quelques approches pour rĂ©soudre ce grand problĂšme.

En fait, « Tensor » pour un million de nos clients, c'est Sbis - notre application: un rĂ©seau social d'entreprise, des solutions pour la vidĂ©oconfĂ©rence, pour la circulation des documents internes et externes, des systĂšmes de comptabilitĂ© et de gestion d'entrepĂŽts,
 C'est donc un « mĂ©ga-combine » pour la gestion complĂšte des affaires, avec plus de 100 projets internes diffĂ©rents.

Pour que tout cela fonctionne correctement et se développe - nous avons 10 centres de développement à travers le pays, avec plus de 1000 développeurs.

Nous travaillons avec PostgreSQL depuis 2008 et avons accumulé un grand volume de ce que nous traitons - ce sont des données client, des données statistiques, analytiques, provenant de systÚmes d'information externes - plus de 400 To. Il y a environ 250 serveurs « en production », et au total, nous surveillons environ 1000 serveurs de bases de données.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Le SQL est un langage déclaratif. Vous décrivez non pas « comment » quelque chose doit fonctionner, mais « ce que » vous voulez obtenir. La SGBD sait mieux comment faire un JOIN - comment relier vos tables, quelles conditions appliquer, ce qui passera par l'index, et ce qui ne le fera pas...

Certaines SGBD acceptent des suggestions : « Non, relie ces deux tables dans tel ordre », mais PostgreSQL ne sait pas faire cela. C'est une position dĂ©libĂ©rĂ©e des dĂ©veloppeurs principaux : « Mieux vaut que nous amĂ©liorions l'optimiseur de requĂȘte que de permettre aux dĂ©veloppeurs d'utiliser des hints quelconques ».

Cependant, bien que PostgreSQL n'offre pas de gestion « externe », il permet d'explorer ce qui se passe « en interne » lorsque vous exĂ©cutez votre requĂȘte, et oĂč se trouvent ses problĂšmes.En gĂ©nĂ©ral, quels problĂšmes classiques un dĂ©veloppeur [vers le DBA] amĂšne-t-il habituellement ? « Nous avons exĂ©cutĂ© une requĂȘte, et

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

tout est lent , tout est bloquĂ©, il se passe quelque chose
 C'est la catastrophe ! »Les raisons sont presque toujours les mĂȘmes :

un algorithme de requĂȘte inefficace

  • DĂ©veloppeur : « Je fais actuellement un JOIN sur 10 tables dans le SQL... » — et s'attend Ă  ce que ses conditions se « dĂ©nouent » de maniĂšre magique et qu'il obtienne tout rapidement. Mais il n'y a pas de miracles, et tout systĂšme, avec une telle variabilitĂ© (10 tables dans un FROM), prĂ©sente toujours une certaine marge d'erreur. [
    des statistiques obsolĂštesarticle]
  • Ce point est particuliĂšrement important pour PostgreSQL, lorsque vous avez « dĂ©versĂ© » un grand ensemble de donnĂ©es sur le serveur, vous faites une requĂȘte — et celui-ci effectue une « scan sĂ©quentielle » sur la table. Parce qu'hier, il y avait 10 enregistrements, et aujourd'hui 10 millions, mais PostgreSQL n'est pas encore au courant, et il faut le lui signaler. [
    un « goulot d'étranglement » au niveau des ressourcesarticle]
  • Vous avez installĂ© une grande base de donnĂ©es lourde sur un serveur faible, manquant d'espace disque, de mĂ©moire et de puissance processeur. Et voilà
 Il y a un seuil de performance au-delĂ  duquel vous ne pouvez plus sauter.
    Un point complexe, mais il est le plus pertinent pour diverses requĂȘtes modifiant les donnĂ©es (INSERT, UPDATE, DELETE) — c'est un sujet distinct.
  • de blocage
    Obtention du plan


 Et pour tout le reste, nous

avons besoin d'un plan ! Nous devons voir ce qui se passe Ă  l'intĂ©rieur du serveur.Le plan d'exĂ©cution des requĂȘtes pour PostgreSQL est un arbre reprĂ©sentant l'algorithme d'exĂ©cution de la requĂȘte sous forme textuelle. C'est justement cet algorithme qui a Ă©tĂ© reconnu comme le plus efficace suite Ă  l'analyse effectuĂ©e par le planificateur.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Chaque nƓud de l'arbre reprĂ©sente une opĂ©ration : extraction de donnĂ©es d'une table ou d'un index, construction d'une carte binaire, jointure de deux tables, union, intersection ou exclusion de jeux de rĂ©sultats. L'exĂ©cution de la requĂȘte consiste Ă  parcourir les nƓuds de cet arbre.

Pour obtenir un plan de requĂȘte, le moyen le plus simple est d'exĂ©cuter l'instruction

EXPLAIN . Pour obtenir tous les attributs rĂ©els, c'est-Ă -dire exĂ©cuter rĂ©ellement la requĂȘte sur la base —EXPLAIN (ANALYZE, BUFFERS) SELECT ... EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Mauvais moment : lorsque vous l'exĂ©cutez, cela se passe « ici et maintenant », donc c'est uniquement adaptĂ© pour le dĂ©bogage local. Si vous prenez un serveur trĂšs chargĂ©, qui subit un fort flux de modifications de donnĂ©es, et que vous voyez : « AĂŻe ! Voici un point oĂč cela s'exĂ©cute lentement.se la requĂȘte. » Une demi-heure, une heure auparavant — pendant que vous couriez et que vous rĂ©cupĂ©riez cette requĂȘte dans les journaux, la statistique de votre ensemble de donnĂ©es avait complĂštement changĂ©. Vous l'exĂ©cutez pour le dĂ©bogage — et elle s'exĂ©cute rapidement ! Et vous ne pouvez pas comprendre « pourquoi », pourquoi c'Ă©tait lent.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Pour comprendre ce qui se passait exactement au moment oĂč la requĂȘte est exĂ©cutĂ©e sur le serveur, des esprits Ă©clairĂ©s ont Ă©crit le module auto_explain. Il est prĂ©sent dans presque toutes les distributions les plus courantes de PostgreSQL et peut simplement ĂȘtre activĂ© dans le fichier de configuration.

S'il comprend qu'une requĂȘte s'exĂ©cute au-delĂ  de la limite que vous lui avez donnĂ©e, il fait un « instantanĂ© » du plan de cette requĂȘte et l'Ă©crit avec les logs..

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Tout semble bien maintenant, nous allons dans le log et voyons là
 [portĂ©e de texte]. Mais nous ne pouvons rien dire Ă  ce sujet, Ă  part le fait que c'est un excellent plan, car il s'est exĂ©cutĂ© en 11ms.

Tout semble bien — mais rien n'est clair sur ce qui se passait rĂ©ellement. Hormis le temps gĂ©nĂ©ral, nous ne voyons pas grand-chose. Parce que regarder un tel « texte brut » n'est pas du tout visuel.

Mais mĂȘme si ce n'est pas visuel, en effet peu pratique, il y a des problĂšmes plus fondamentaux :

  • Dans le nƓud, est indiquĂ©e la somme des ressources de tout l'arbre sous-jacent en dessous. Donc, il n'est pas possible de simplement savoir combien de temps a Ă©tĂ© consacrĂ© ici spĂ©cifiquement sur cet Index Scan — si en dessous il y a une condition imbriquĂ©e. Nous devons vĂ©rifier dynamiquement s'il y a des « enfants » et des variables conditionnelles, CTE — et dĂ©duire tout cela « dans notre tĂȘte ».
  • DeuxiĂšme point : le temps indiquĂ© dans le nƓud — c'est le temps d'exĂ©cution unique du nƓud.Si ce nƓud a Ă©tĂ© exĂ©cutĂ© en consĂ©quence, par exemple, d'une boucle sur les enregistrements d'une table, plusieurs fois, alors dans le plan augmente le nombre de boucles — cycles de ce nƓud. Mais le temps d'exĂ©cution atomique reste le mĂȘme dans le plan. Donc, pour comprendre combien de temps ce nƓud a Ă©tĂ© exĂ©cutĂ© au total, il faut multiplier un par l'autre — encore une fois « dans notre tĂȘte ».

Dans de telles conditions, comprendre « Qui est le maillon faible ? » est pratiquement impossible. C'est pourquoi mĂȘme les dĂ©veloppeurs eux-mĂȘmes Ă©crivent dans le « manuel » que « Comprendre le plan est un art qui s'apprend, c'est une expĂ©rience... ».

Mais nous avons 1000 dĂ©veloppeurs, et on ne peut pas transmettre cette expĂ©rience Ă  chacun d'eux. Moi, toi, lui — savent, mais quelqu'un lĂ -bas — ne sait pas. Peut-ĂȘtre qu'il va apprendre, ou peut-ĂȘtre pas, mais il doit dĂ©jĂ  travailler — alors d'oĂč pourrait-il obtenir cette expĂ©rience.

Visualisation du plan

C'est pourquoi nous avons compris que pour résoudre ces problÚmes, nous avons besoin d'une bonne visualisation du plan. [article]

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Nous avons d'abord « explorĂ© le marchĂ© » — allons chercher sur Internet ce qui existe.

Mais, il s'est avĂ©rĂ© qu'il y a trĂšs peu de solutions « vivantes », qui se dĂ©veloppent plus ou moins — littĂ©ralement, une seule : explain.depesz.com de Hubert Lubaczewski. Dans le champ d'entrĂ©e, tu « alimenes » une reprĂ©sentation textuelle du plan, et il te montre un tableau avec les donnĂ©es analysĂ©es :

  • le temps d'exĂ©cution propre au nƓud
  • le temps total pour tout le sous-arbre
  • le nombre d'enregistrements qui ont Ă©tĂ© extraits et qui Ă©taient statistiquement attendus
  • le corps mĂȘme du nƓud

Ce service a également la possibilité de partager un archive de liens. Tu y mets ton plan et dis : « Hé, Vasya, voici le lien, quelque chose ne va pas là-bas ».

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Mais il y a aussi quelques petits problĂšmes.

Tout d'abord, une quantité énorme de « copiés-collés ». Tu prends un morceau de log, tu le mets ici, encore, et encore.

DeuxiĂšmement, il n'y a pas d'analyse du nombre de donnĂ©es lues — ces fameux buffers, qui sont affichĂ©s par EXPLAIN (ANALYZE, BUFFERS), lĂ , nous ne les voyons pas. Il ne sait tout simplement pas les analyser, comprendre et travailler avec. Quand tu lis beaucoup de donnĂ©es et comprends que tu peux mal « te rĂ©partir » sur le disque et le cache en mĂ©moire, cette information est trĂšs importante.

Le troisiĂšme point nĂ©gatif — le dĂ©veloppement de ce projet est trĂšs faible. Les commits sont trĂšs petits, Ă  peine une fois par semestre, et le code est en Perl.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Mais tout cela est « lyrique », on pourrait vivre avec ça d'une certaine maniĂšre, mais il y a une chose qui nous a fortement Ă©loignĂ©s de ce service. Ce sont les erreurs d'analyse des Common Table Expressions (CTE) et des diffĂ©rents nƓuds dynamiques comme InitPlan/SubPlan.

Si l'on croit cette image, alors le temps total d'exĂ©cution de chaque nƓud individuel est supĂ©rieur au temps total d'exĂ©cution de toute la requĂȘte. C'est simple — nous n'avons pas soustrait le temps de gĂ©nĂ©ration de ce CTE du nƓud CTE Scan.. Donc, nous ne savons plus combien de temps a rĂ©ellement pris le scan CTE.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Ici, nous avons compris qu'il était temps d'écrire le nÎtre - youpi ! Chaque développeur dit : « Maintenant, nous allons écrire le nÎtre, ce sera super simple ! »

Nous avons pris une pile typique pour les services web : le noyau en Node.js + Express, ajouté Bootstrap et pour des beaux diagrammes - D3.js. Et nos attentes ont totalement été satisfaites - nous avons obtenu le premier prototype en 2 semaines :

  • notre propre parseur de plan
    C'est-à-dire que désormais nous pouvons analyser n'importe quel plan généré par PostgreSQL.
  • analyse correcte des nƓuds dynamiques — CTE Scan, InitPlan, SubPlan
  • analyse de la distribution des buffers — oĂč les pages de donnĂ©es sont lues de la mĂ©moire, oĂč depuis le cache local, oĂč depuis le disque
  • nous avons obtenu une clartĂ©
    Pour ne pas avoir à « creuser » cela dans les logs, mais pour voir « le point faible » directement sur l'image.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Nous avons obtenu une image similaire — directement avec la mise en Ă©vidence de la syntaxe. Mais en gĂ©nĂ©ral, nos dĂ©veloppeurs n travaillent pas avec la prĂ©sentation complĂšte du plan, mais avec une version plus courte. En effet, tous les chiffres ont dĂ©jĂ  Ă©tĂ© analysĂ©s et nous les avons mis de cĂŽtĂ©, ne laissant que la premiĂšre ligne, indiquant quel nƓud il s'agit : CTE Scan, gĂ©nĂ©ration CTE ou Seq Scan pour une certaine table.

Cette version abrégée, nous l'appelons modÚle de plan.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Quoi d'autre serait pratique ? Il serait utile de voir quelle part de temps total est attribuĂ©e Ă  chaque nƓud - et nous l'avons simplement « collĂ© » sur le cĂŽtĂ© diagramme circulaire.

Nous survolons le nƓud et voyons - en fait, Seq Scan a pris moins d'un quart du temps total, tandis que les 3/4 restants Ă©taient pour CTE Scan. Horrible ! C'est une petite remarque concernant la « rapiditĂ© » de CTE Scan, si vous les utilisez activement dans vos requĂȘtes. Ils ne sont pas trĂšs rapides - ils sont mĂȘme plus lents qu'un scan de table normal. [article] [article]

Mais ces diagrammes sont généralement plus intéressants et plus complexes, lorsque nous survolons un segment et voyons, par exemple, que plus de la moitié de tout le temps a été « absorbé » par un certain Seq Scan. Et encore à l'intérieur, il y avait un filtre, un tas d'enregistrements ont été rejetés par celui-ci... On peut directement envoyer cette image au développeur en disant : « Vasya, ici tout va trÚs mal ! Regarde, il y a quelque chose qui ne va pas ! »

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Naturellement, sans « couacs », cela ne s'est pas fait.

La premiĂšre chose sur laquelle nous avons "butĂ©" est le problĂšme d'arrondi. Le temps d'un nƓud dans chaque plan est indiquĂ© avec une prĂ©cision de 1”s. Et lorsque le nombre de cycles d'un nƓud dĂ©passe, par exemple, 1000 — aprĂšs exĂ©cution, PostgreSQL a divisĂ© "avec prĂ©cision", donc dans le calcul inverse, nous obtenons un temps total "quelque part entre 0,95 ms et 1,05 ms". Quand on parle de microsecondes, cela va encore, mais quand cela atteint les [millis]secondes — il faut prendre en compte cette information lors de la "dĂ©mĂȘlage" des ressources par nƓud dans le plan "qui a consommĂ© combien".

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Le deuxiĂšme point, plus complexe, est la distribution des ressources (ces fameux buffers) entre les nƓuds dynamiques. Cela nous a coĂ»tĂ© environ 4 semaines supplĂ©mentaires au cours des 2 premiĂšres semaines sur le prototype.

Le problĂšme est assez simple Ă  obtenir — nous faisons un CTE et y lisons soi-disant quelque chose. En rĂ©alitĂ©, PostgreSQL est "intelligent" et ne lira rien directement lĂ -bas. Ensuite, nous prenons le premier enregistrement, et celui-ci — le cent-uniĂšme du mĂȘme CTE.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Nous examinons le plan et comprenons — c'est Ă©trange, nous avons consommĂ© 3 buffers (pages de donnĂ©es) dans Seq Scan, 1 dans CTE Scan, et encore 2 dans le second CTE Scan. Donc si nous additionnons simplement, nous pourrions obtenir 6, mais nous n'avons en fait lu que 3 depuis la table ! CTE Scan ne lit rien d'ailleurs, mais travaille directement avec la mĂ©moire du processus. Donc ici, il y a clairement quelque chose qui cloche !

En réalité, il s'avÚre que ces 3 pages de données, qui ont été demandées par Seq Scan, ont d'abord été demandées par le premier CTE Scan, puis le second, et ce dernier a encore demandé 2 de plus. Donc au total, seulement 3 pages de données ont été lues, et non 6.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Et cette image nous a conduits Ă  comprendre que l'exĂ©cution d'un plan n'est plus un arbre, mais simplement un graphique acyclique. Et nous avons obtenu un diagramme Ă  peu prĂšs comme celui-ci, afin de comprendre "d'oĂč vient quoi". Donc ici, nous avons créé un CTE Ă  partir de pg_class, et l'avons demandĂ© deux fois, et presque tout notre temps s'est Ă©coulĂ© sur la branche oĂč nous l'avons demandĂ© la deuxiĂšme fois. Évidemment, lire la 101e entrĂ©e est beaucoup plus coĂ»teux que de lire simplement la premiĂšre depuis la table.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Nous avons respiré un instant. Nous avons dit : "Maintenant, Neo, tu sais le kung-fu ! Maintenant, notre expérience est directement sur ton écran. Tu peux maintenant l'utiliser." [article]

Consolidation des logs

Nos 1000 dĂ©veloppeurs ont poussĂ© un soupir de soulagement. Mais nous comprenions que nous n'avions que des centaines de serveurs « opĂ©rationnels », et que tout ce « copier-coller » de la part des dĂ©veloppeurs n'est vraiment pas pratique. Nous avons rĂ©alisĂ© qu'il fallait rassembler cela nous-mĂȘmes.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

En fait, il existe un module standard qui peut collecter des statistiques, mais il doit Ă©galement ĂȘtre activĂ© dans la configuration — c'est le module pg_stat_statements. Mais cela ne nous a pas convenu.

PremiĂšrement, ce module attribue des QueryId diffĂ©rentsaux mĂȘmes requĂȘtes selon diffĂ©rents schĂ©mas au sein d'une mĂȘme base. Donc, si je commence par SET search_path = '01'; SELECT * FROM user LIMIT 1;, puis SET search_path = '02'; et la mĂȘme requĂȘte, alors dans les statistiques de ce module, il y aura des enregistrements diffĂ©rents, et je ne pourrai pas collecter de statistiques globales prĂ©cisĂ©ment en fonction de ce profil de requĂȘte, sans tenir compte des schĂ©mas.

DeuxiĂšme point qui a empĂȘchĂ© son utilisation — l'absence de plans. C'est-Ă -dire qu'il n'y a pas de plan — il n'y a que la requĂȘte elle-mĂȘme. Nous voyons ce qui a ralenti, mais nous ne comprenons pas pourquoi. Et lĂ , nous revenons au problĂšme du jeu de donnĂ©es en rapide Ă©volution.

Et le dernier point — l'absence de « faits ». Il n'est pas possible de faire rĂ©fĂ©rence Ă  une instance spĂ©cifique de l'exĂ©cution d'une requĂȘte — elle n'existe pas, il n'y a que des statistiques agrĂ©gĂ©es. On peut y travailler, mais c'est juste trĂšs difficile.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

C'est pourquoi nous avons décidé de lutter contre le « copier-coller » et de commencer à écrire un collecteur.

. Le collecteur se connecte via SSH, crĂ©e une connexion protĂ©gĂ©e jusqu'au serveur de la base grĂące Ă  un certificat et tail -F s'accroche Ă  son fichier log. Ainsi, dans cette session nous obtenons un miroir complet de tout le fichier log, qui est gĂ©nĂ©rĂ© par le serveur. La charge sur le serveur lui-mĂȘme est minimale, car nous ne faisons rien d'autre que de miroiter le trafic.

Puisque nous avons déjà commencé à écrire l'interface en Node.js, nous avons également continué à écrire le collecteur dessus. Et cette technologie s'est révélée efficace, car pour travailler avec des données textuelles mal formatées, comme celles des logs, il est trÚs pratique d'utiliser JavaScript. Et l'infrastructure Node.js en tant que plateforme backend permet de travailler facilement et confortablement avec des connexions réseau, ainsi que sur divers flux de données.

Ainsi, nous "établissons" deux connexions : la premiÚre pour "écouter" le log et le récupérer, et la seconde pour interroger périodiquement la base. "Eh bien, le log a enregistré qu'une table avec l'oid 123 a été bloquée", mais cela ne dit rien au développeur, et il serait bon de demander à la base "Qu'est-ce que c'est que OID = 123 ?" Et ainsi nous interrogeons périodiquement la base sur ce que nous ne savons pas encore.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

"Il n'y a qu'une chose que tu n'as pas prise en compte, il existe une espĂšce d'abeilles ressemblant Ă  des Ă©lĂ©phants..." Nous avons commencĂ© Ă  dĂ©velopper ce systĂšme lorsque nous voulions surveiller 10 serveurs. Les plus critiques selon notre comprĂ©hension, oĂč des problĂšmes surgissaient, et qui Ă©taient difficiles Ă  gĂ©rer. Mais dĂšs le premier trimestre, nous avons obtenu des centaines de serveurs Ă  surveiller — car le systĂšme a Ă©tĂ© adoptĂ©, tout le monde en a voulu, tout le monde a trouvĂ© cela pratique.

Tout cela doit ĂȘtre accumulĂ©, il y a un grand flux de donnĂ©es, actif. En gros, ce que nous surveillons, sur quoi nous sommes capables d'agir — c'est ce que nous utilisons. Nous utilisons Ă©galement PostgreSQL comme entrepĂŽt de donnĂ©es. Et rien n'est plus rapide pour "injecter" des donnĂ©es que l'opĂ©rateur COPY pas encore.

Mais simplement "injecter" des donnĂ©es — ce n'est pas tout Ă  fait notre technologie. Parce que si vous avez une centaine de serveurs, gĂ©nĂ©rant environ 50k requĂȘtes par seconde, cela vous produit 100-150 Go de logs par jour. Donc, nous avons dĂ» façonner la base avec prĂ©caution.

Tout d'abord, nous avons créé un partitionnement par jours, parce qu'au fond, personne ne s'intĂ©resse Ă  la corrĂ©lation entre les jours. Quelle diffĂ©rence cela fait-il ce qui s'est passĂ© hier, si cette nuit vous avez dĂ©ployĂ© une nouvelle version de l'application — et dĂ©jĂ  une nouvelle statistique.

DeuxiĂšmement, nous avons appris (nous avons dĂ») Ă  Ă©crire trĂšs trĂšs rapidement Ă  l'aide de COPY. C’est-Ă -dire pas seulement COPY, parce qu'il est plus rapide que INSERT, mais encore plus rapide.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

TroisiĂšme point — nous avons dĂ» renoncer aux triggers, donc aux clĂ©s Ă©trangĂšres. Cela signifie que nous n’avons pas de cohĂ©rence rĂ©fĂ©rentielle du tout. Parce que si vous avez une table avec une paire de FK, et que vous dites dans la structure de la base de donnĂ©es que "cette entrĂ©e dans le log fait rĂ©fĂ©rence par FK, par exemple, Ă  un groupe d'entrĂ©es", alors quand vous insĂ©rez, PostgreSQL n'a d'autre choix que de prendre et d'exĂ©cuter honnĂȘtement SELECT 1 FROM master_fk1_table WHERE ... avec l'identifiant que vous essayez d'insĂ©rer — juste pour vĂ©rifier que cette entrĂ©e est prĂ©sente, que vous ne "brisez" pas cette clĂ© Ă©trangĂšre avec votre insertion.

Nous recevons, au lieu d'un seul enregistrement dans la table cible et de ses index, un plus de lecture dans toutes les tables auxquelles il fait rĂ©fĂ©rence. Et cela ne nous est pas du tout nĂ©cessaire — notre objectif est d'Ă©crire le plus possible et le plus rapidement avec le minimum de charge. Donc, FK — dehors !

Le point suivant — l'agrĂ©gation et le hachage. Au dĂ©part, nous les avons rĂ©alisĂ©s dans la base de donnĂ©es — car c'est pratique de pouvoir, dĂšs qu'un enregistrement arrive, le faire dans une certaine table. « plus un » directement dans le trigger.. Bien, pratique, mais mauvais en mĂȘme temps — vous insĂ©rez un enregistrement et vous devez encore lire et Ă©crire quelque chose d'une autre table. De plus, non seulement vous devez lire et Ă©crire, mais encore le faire Ă  chaque fois.

Et maintenant, imaginez que vous avez une table dans laquelle vous vous contentez de compter le nombre de requĂȘtes passĂ©es par un hĂŽte spĂ©cifique : +1, +1, +1, ..., +1. Et cela ne vous est pas vraiment nĂ©cessaire — tout cela peut ĂȘtre sommĂ© en mĂ©moire sur le collecteur et envoyĂ© Ă  la base en une fois. +10.

Oui, dans le cas de problĂšmes, votre intĂ©gritĂ© logique peut « s'effondrer », mais c'est pratiquement un cas irrĂ©el — parce que vous avez un serveur normal, avec une batterie dans le contrĂŽleur, vous avez un journal des transactions, un journal sur le systĂšme de fichiers
 En gros, ça n'en vaut pas la peine. La perte de performance que vous subissez Ă  cause du travail des triggers/FK, les frais associĂ©s, ne valent pas ce prix.

Il en va de mĂȘme pour le hachage. Une certaine requĂȘte arrive Ă  vous, vous calculez dans la base un certain identifiant, vous l'Ă©crivez dans la base et ensuite vous le communiquez Ă  tous. Tout va bien, jusqu'Ă  ce qu'Ă  l'instant de l'Ă©criture, vous ayez un deuxiĂšme souhaitant Ă©crire le mĂȘme identifiant — et vous aurez un blocage, ce qui est dĂ©jĂ  mauvais. Donc, si vous pouvez dĂ©placer la gĂ©nĂ©ration de certains ID vers le client (relativement Ă  la base), il est prĂ©fĂ©rable de le faire.

Il nous a simplement parfaitement convenu d'utiliser MD5 du texte — de la requĂȘte, du plan, du modĂšle,
 Nous le calculons du cĂŽtĂ© du collecteur et « l'injectons » dans la base dĂ©jĂ  sous forme d'ID prĂȘt Ă  l'emploi. La longueur de MD5 et le partitionnement journalier nous permettent de ne pas nous soucier des colisions potentielles.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Mais pour tout cela soit enregistrĂ© rapidement, nous avons dĂ» modifier la procĂ©dure d'enregistrement elle-mĂȘme.

Comment les donnĂ©es sont-elles gĂ©nĂ©ralement Ă©crites ? Nous avons un ensemble de donnĂ©es, nous le rĂ©partissons sur plusieurs tables, puis nous effectuons un COPY — d'abord dans la premiĂšre, puis dans la deuxiĂšme, puis dans la troisiĂšme
 Ce n'est pas pratique, car nous semblons Ă©crire un flux de donnĂ©es en trois Ă©tapes consĂ©cutives. Pas agrĂ©able. Est-il possible d'accĂ©lĂ©rer cela ? Oui !

Pour cela, il suffit de faire passer ces flux en parallĂšle. Ainsi, nous avons des erreurs, des requĂȘtes, des modĂšles, des blocages qui circulent dans des flux distincts,
 — et nous Ă©crivons tout cela en parallĂšle. Pour cela, il suffit de maintenir un canal COPY constamment ouvert pour chaque table cible distincte.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

C'est-Ă -dire que le collecteur a toujours un flux, dans lequel je peux Ă©crire les donnĂ©es dont j'ai besoin. Mais pour que la base voie ces donnĂ©es, sans que quelqu'un ne soit bloquĂ© en attendant que ces donnĂ©es soient Ă©crites, il faut interrompre le COPY Ă  intervalles rĂ©guliers. Pour nous, le dĂ©lai le plus efficace s'est avĂ©rĂ© ĂȘtre d'environ 100 ms — nous fermons et rouvrons immĂ©diatement la mĂȘme table. Et si nous avons besoin de plus d'un flux pendant certains pics, nous faisons un poll jusqu'Ă  un certain seuil.

De plus, nous avons découvert que pour ce type de profil de charge, toute agrégation, lorsqu'enregistrements sont regroupés par paquets, est nuisible. Le mal classique est INSERT ... VALUES et ensuite 1000 enregistrements. Car à ce moment-là, vous atteignez un pic d'écriture sur le support, et tous les autres qui tentent d'écrire sur le disque devront attendre.

Pour Ă©viter de telles anomalies, il suffit de ne rien agrĂ©ger, de ne pas tamponner du tout. Et si un tampon sur le disque se produit quand mĂȘme (heureusement, le Stream API dans Node.js permet de le savoir) — retarde cette connexion. Quand vous recevez l'Ă©vĂ©nement qu'elle est de nouveau libre — Ă©crivez-y depuis la file d'attente accumulĂ©e. Pendant qu'elle est occupĂ©e — prenez le suivant, qui est libre et Ă©crivez y.

Avant la mise en Ɠuvre de cette approche pour l'Ă©criture des donnĂ©es, nous avions environ 4000 opĂ©rations d'Ă©criture, et grĂące Ă  cette mĂ©thode, nous avons rĂ©duit la charge par quatre. Maintenant, nous avons encore augmentĂ© notre capacitĂ© de six fois grĂące aux nouvelles bases observables — jusqu'Ă  100 Mo/s. Et maintenant, nous stockons les journaux des trois derniers mois dans un volume d'environ 10-15 To, en espĂ©rant que pendant trois mois, tout problĂšme pourra ĂȘtre rĂ©solu par n'importe quel dĂ©veloppeur.

Nous comprenons les problĂšmes

Mais rassembler toutes ces donnĂ©es — c'est bien, utile et pertinent, mais insuffisant — il faut les comprendre. Parce que ce sont des millions de diffĂ©rents plans par jour.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Mais des millions, c'est ingérable, il faut d'abord réduire à « moins ». Et, en premier lieu, il faut décider comment vous allez organiser ce « moins ».

Nous avons identifié trois points clés :

  • qui Cette requĂȘte a Ă©tĂ© envoyĂ©e par
    C'est-Ă -dire de quelle application elle "est venue": interface web, backend, systĂšme de paiement ou autre.
  • oĂč cela s'est produit
    Sur quel serveur prĂ©cis. Parce que si vous avez plusieurs serveurs pour une mĂȘme application, et qu’un d’eux subit soudainement un problĂšme (parce que « le disque est mort », « la mĂ©moire a fuit », ou autre problĂšme), il faut cibler prĂ©cisĂ©ment le serveur.
  • comment la problĂ©matique se manifestait dans tel ou tel plan

Pour comprendre « qui » nous a envoyĂ© la requĂȘte, nous utilisons un outil standard — avec l'installation d'une variable de session : SET application_name = '{bl-host}:{bl-method}'; — nous extrayons le nom de l'hĂŽte de la logique mĂ©tier Ă  partir duquel la requĂȘte est faite, et le nom de la mĂ©thode ou de l'application qui l'a initiĂ©e.

Une fois que nous avons passĂ© « le propriĂ©taire » de la requĂȘte, il faut l'enregistrer dans le log — pour cela, nous configurons la variable log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Pour ceux qui sont intĂ©ressĂ©s, ils peuvent consulter le manuel, pour voir ce que cela signifie. Donc, dans le log, nous voyons :

  • du temps
  • les identifiants de processus et de transaction
  • le nom de la base de donnĂ©es
  • l'IP de celui qui a envoyĂ© cette requĂȘte
  • et le nom de la mĂ©thode

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Ensuite, nous avons compris qu'il n'Ă©tait pas trĂšs intĂ©ressant de regarder la corrĂ©lation d'une seule requĂȘte entre diffĂ©rents serveurs. Il est rare que vous ayez une application qui Ă©choue de la mĂȘme maniĂšre Ă  deux endroits diffĂ©rents. Mais mĂȘme si c'est le cas - regardez l'un de ces serveurs.

Donc, nous avons trouvĂ© que la coupure « un serveur — un jour » Ă©tait suffisante pour toute analyse.

Le premier angle d'analyse — c'est le fameux « modĂšle » — une forme abrĂ©gĂ©e de la reprĂ©sentation du plan, nettoyĂ©e de tous les indicateurs numĂ©riques. Le deuxiĂšme angle — l'application ou la mĂ©thode, et le troisiĂšme — c'est le nƓud spĂ©cifique du plan qui a causĂ© des problĂšmes.

Quand nous sommes passés des instances concrÚtes aux modÚles, nous avons immédiatement obtenu deux avantages :

  • une rĂ©duction significative du nombre d'objets Ă  analyser
    Il faut maintenant examiner le problĂšme non pas par milliers de requĂȘtes ou de plans, mais par dizaines de modĂšles.
  • timeline
    En d'autres termes, en rĂ©sumant les « faits » dans le cadre d'une certaine coupe, vous pouvez montrer leur apparition tout au long de la journĂ©e. Et ici, vous pouvez comprendre que si vous avez un certain modĂšle qui se produit, par exemple, une fois par heure, alors qu'il devrait se produire une fois par jour, il vaut la peine de se demander ce qui ne va pas — qui l'a provoquĂ© et pourquoi, peut-ĂȘtre qu'il n'a pas sa place ici. C'est un autre moyen non numĂ©rique, purement visuel, d'analyse.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Les autres méthodes se basent sur les indicateurs que nous extrayons du plan : combien de fois ce modÚle s'est produit, le temps total et moyen, combien de données ont été lues depuis le disque, et combien depuis la mémoire


Parce que par exemple, vous arrivez sur la page d'analyse de l'hîte, vous regardez — il y a trop de lectures du disque. Le disque sur le serveur ne suit pas — mais qui lit là-dessus ?

Et vous pouvez trier par n'importe quelle colonne et dĂ©cider sur quoi vous allez vous concentrer en ce moment — sur la charge du processeur ou celle du disque, ou sur le nombre total de requĂȘtes
 Vous avez triĂ©, regardĂ© les « meilleurs », corrigĂ© — vous avez dĂ©ployĂ© une nouvelle version de l'application.
[vidéolesson]

Et immĂ©diatement, vous pouvez voir diffĂ©rentes applications qui suivent le mĂȘme modĂšle Ă  partir de la requĂȘte de type SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, traitement
 Et vous vous demandez pourquoi le traitement devrait lire l'utilisateur s'il n'interagit pas avec lui.

L'approche inverse consiste Ă  voir immĂ©diatement ce que fait l'application. Par exemple, le frontend — c'est ça, ça, et ça, et puis ça encore une fois par heure (le timeline aide justement). Et la question se pose — il semble que ce ne soit pas le rĂŽle du frontend de faire quelque chose une fois par heure


Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Au bout d'un certain temps, nous avons compris qu'il nous manquait des statistiques agrĂ©gĂ©es. Nous avons extrait des plans uniquement les noeuds qui effectuent des opĂ©rations sur les donnĂ©es des tableaux eux-mĂȘmes (les lisent/Ă©crivent selon l'index ou non). En gros, par rapport Ă  l'image prĂ©cĂ©dente, un seul aspect est ajoutĂ© —combien d'enregistrements ce noeud nous a apportĂ©s , et combien il en a rejetĂ©s (Rows Removed by Filter).Vous n'avez pas d'index appropriĂ© sur la table, vous exĂ©cutez une requĂȘte, elle passe Ă  cĂŽtĂ© de l'index, elle tombe dans Seq Scan
 vous avez filtrĂ© tous les enregistrements, sauf un. Pourquoi auriez-vous besoin de 100 millions d'enregistrements filtrĂ©s en un jour, ne serait-il pas mieux d'appliquer un index ?

Vous n'avez pas d'index appropriĂ© sur la table, vous effectuez une requĂȘte, elle passe Ă  cĂŽtĂ© de l'index, tombe en Seq Scan... toutes les entrĂ©es sauf une ont Ă©tĂ© filtrĂ©es. Et pourquoi avez-vous besoin de 100 millions d'enregistrements filtrĂ©s en une journĂ©e ? Ne serait-il pas prĂ©fĂ©rable de crĂ©er un index ?

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

AprĂšs avoir analysĂ© tous les plans par nƓuds, nous avons compris qu'il existe certaines structures typiques dans les plans qui semblent trĂšs suspectes. Il serait utile de conseiller au dĂ©veloppeur : « Mon ami, tu lis d'abord par index, ensuite tu trier, puis tu coupes » — en gĂ©nĂ©ral, il n'y a qu'un seul enregistrement lĂ .

Tous ceux qui ont fait des requĂȘtes avec un tel modĂšle ont sĂ»rement rencontrĂ© cela : « Donne-moi la derniĂšre commande pour Vassia, sa date » Et si vous n'avez pas d'index par date, ou si la date n'est pas dans l'index utilisĂ©, alors c'est exactement sur ces « rĂąteaux » que vous allez marcher.

Mais nous savons que ce sont des « rĂąteaux » — alors pourquoi ne pas dire immĂ©diatement au dĂ©veloppeur ce qu'il devrait faire. Ainsi, en ouvrant maintenant le plan, notre dĂ©veloppeur voit immĂ©diatement une belle image avec des conseils, oĂč il est directement indiquĂ© : « Tu as des problĂšmes ici et ici, et ils se rĂ©solvent comme ça et ça. »

En consĂ©quence, le volume de l’expĂ©rience nĂ©cessaire pour rĂ©soudre les problĂšmes au dĂ©but et maintenant a chutĂ© de plusieurs fois. C'est l'outil que nous avons obtenu.

Optimisation massive des requĂȘtes PostgreSQL. Kirill Borovikov (Tensor)

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster