Optimisation des requĂȘtes de base de donnĂ©es sur l'exemple de service B2B pour les constructeurs

Comment faire croĂźtre 10 fois le nombre de requĂȘtes de base de donnĂ©es sans passer Ă  un serveur plus efficace et maintenir le systĂšme en marche? Je vais vous dire comment nous avons luttĂ© avec la dĂ©gradation des performances de notre base de donnĂ©es, comment nous avons optimisĂ© les requĂȘtes SQL pour servir le plus d'utilisateurs possible et ne pas augmenter le coĂ»t des ressources informatiques.

Je fais un service de gestion des processus d'affaires dans les entreprises de construction. Environ 3 000 entreprises travaillent avec nous. Plus de 10 000 personnes travaillent quotidiennement avec notre systĂšme pendant 4 Ă  10 heures. Il rĂ©sout diverses tĂąches de planification, alertes, avertissements, validations ... Nous utilisons PostgreSQL 9.6. Nous avons environ 300 tables dans la base de donnĂ©es et chaque jour, jusqu'Ă  200 millions de demandes (10 000 diffĂ©rentes) lui sont envoyĂ©es. En moyenne, nous avons 3 Ă  4 000 demandes par seconde, dans les moments les plus actifs, plus de 10 000 demandes par seconde. La plupart des demandes sont OLAP. Il y a beaucoup moins d'ajouts, de modifications et de suppressions, c'est-Ă -dire que la charge OLTP est relativement faible. J'ai donnĂ© tous ces chiffres afin que vous puissiez Ă©valuer la portĂ©e de notre projet et comprendre comment notre expĂ©rience peut vous ĂȘtre utile.

La premiĂšre photo. Lyrique


Lorsque nous avons commencĂ© le dĂ©veloppement, nous ne pensions pas vraiment au type de charge qui tomberait sur la base de donnĂ©es et Ă  ce que nous ferions si le serveur arrĂȘtait de tirer. Lors de la conception de la base de donnĂ©es, nous avons suivi les recommandations gĂ©nĂ©rales et essayĂ© de ne pas nous tirer dans le pied, mais au-delĂ  des conseils gĂ©nĂ©raux tels que «n'utilisez pas le modĂšle de valeurs d'attribut d'entitĂ© , nous n'y sommes pas allĂ©s. Conçu sur la base des principes de normalisation Ă©vitant la redondance des donnĂ©es et ne se souciant pas d'accĂ©lĂ©rer certaines requĂȘtes. DĂšs l'arrivĂ©e des premiers utilisateurs, nous avons rencontrĂ© un problĂšme de performances. Comme d'habitude, nous n'Ă©tions absolument pas prĂ©parĂ©s Ă  cela. Les premiers problĂšmes Ă©taient simples. En rĂšgle gĂ©nĂ©rale, tout a Ă©tĂ© dĂ©cidĂ© en ajoutant un nouvel index. Mais il est arrivĂ© un moment oĂč de simples correctifs ne fonctionnaient plus. Ayant rĂ©alisĂ© qu'il n'y a pas assez d'expĂ©rience et qu'il devient de plus en plus difficile de comprendre quelle est la cause des problĂšmes, nous avons embauchĂ© des spĂ©cialistes qui nous ont aidĂ©s Ă  configurer correctement le serveur, Ă  connecter la surveillance, Ă  montrer oĂč chercher pour obtenir des statistiques .

La deuxiĂšme photo. Statistiques


Nous avons donc environ 10 000 requĂȘtes diffĂ©rentes qui sont exĂ©cutĂ©es par jour dans notre base de donnĂ©es. Sur ces 10 000, il y a des monstres qui s'exĂ©cutent 2 Ă  3 millions de fois avec un temps d'exĂ©cution moyen de 0,1 Ă  0,3 ms et il y a des requĂȘtes avec un temps d'exĂ©cution moyen de 30 secondes qui sont appelĂ©es 100 fois par jour.

Il n'a pas Ă©tĂ© possible d'optimiser les 10 000 requĂȘtes, nous avons donc dĂ©cidĂ© de dĂ©terminer oĂč diriger les efforts afin d'amĂ©liorer correctement les performances de la base de donnĂ©es. AprĂšs plusieurs itĂ©rations, nous avons commencĂ© Ă  diviser les demandes en types.

TOP requĂȘtes


Ce sont les requĂȘtes les plus difficiles qui prennent le plus de temps (temps total). Ce sont des requĂȘtes qui sont soit appelĂ©es trĂšs souvent, soit des requĂȘtes qui prennent trĂšs longtemps (les requĂȘtes longues et frĂ©quentes ont Ă©tĂ© optimisĂ©es dĂšs les premiĂšres itĂ©rations de la lutte pour la vitesse). En consĂ©quence, le serveur passe le plus de temps Ă  leur exĂ©cution au total. De plus, il est important de sĂ©parer les requĂȘtes principales par le temps d'exĂ©cution total et sĂ©parĂ©ment par le temps d'E / S. Les moyens d'optimiser ces requĂȘtes sont lĂ©gĂšrement diffĂ©rents.

La pratique habituelle de toutes les entreprises est de traiter les demandes TOP. Il y en a peu, l'optimisation d'une seule demande peut libĂ©rer jusqu'Ă  5 Ă  10% des ressources. Cependant, Ă  mesure que le projet vieillit, l'optimisation des requĂȘtes TOP devient une tĂąche de plus en plus simple. Toutes les mĂ©thodes simples ont dĂ©jĂ  Ă©tĂ© Ă©laborĂ©es, et mĂȘme la demande la plus «difficile» ne prend «que» 3 Ă  5% des ressources. Si les requĂȘtes TOP prennent au total moins de 30 Ă  40% du temps, alors vous avez probablement dĂ©jĂ  fait des efforts pour qu'elles fonctionnent rapidement et il est temps de passer Ă  l'optimisation des requĂȘtes du groupe suivant.
Il reste Ă  rĂ©pondre Ă  la question du nombre de requĂȘtes principales Ă  inclure dans ce groupe. Je prends habituellement pas moins de 10, mais pas plus de 20. J'essaie de faire en sorte que l'heure du premier et du dernier dans le groupe TOP ne diffĂšre pas plus de 10 fois. Autrement dit, si le temps d'exĂ©cution de la requĂȘte chute fortement de 1 Ă  10, alors je prends TOP-10, si la chute est plus fluide, alors j'augmente la taille du groupe Ă  15 ou 20.
image

Paysans moyens (moyen)


Ce sont toutes des demandes qui vont immĂ©diatement aprĂšs TOP, Ă  l'exception des 5-10% derniers. Habituellement, dans l'optimisation de ces demandes particuliĂšres rĂ©side la possibilitĂ© d'augmenter considĂ©rablement les performances du serveur. Ces requĂȘtes peuvent «peser» jusqu'Ă  80%. Mais mĂȘme si leur part a dĂ©passĂ© 50%, il est temps de les regarder de plus prĂšs.

Queue


Comme cela a Ă©tĂ© dit, ces demandes vont Ă  la fin et prennent 5 Ă  10% du temps. Vous ne pouvez les oublier que si vous n'utilisez pas d'outils d'analyse automatique des requĂȘtes, leur optimisation peut Ă©galement ĂȘtre bon marchĂ©.

Comment évaluer chaque groupe?

J'utilise une requĂȘte SQL qui aide Ă  faire une telle Ă©valuation pour PostgreSQL (je suis sĂ»r que pour de nombreux autres SGBD, vous pouvez Ă©crire une requĂȘte similaire)

RequĂȘte SQL pour estimer la taille des groupes TOP-MEDIUM-TAIL
SELECT sum(time_top) AS sum_top, sum(time_medium) AS sum_medium, sum(time_tail) AS sum_tail FROM ( SELECT CASE WHEN rn <= 20 THEN tt_percent ELSE 0 END AS time_top, CASE WHEN rn > 20 AND rn <= 800 THEN tt_percent ELSE 0 END AS time_medium, CASE WHEN rn > 800 THEN tt_percent ELSE 0 END AS time_tail FROM ( SELECT total_time / (SELECT sum(total_time) FROM pg_stat_statements) * 100 AS tt_percent, query, ROW_NUMBER () OVER (ORDER BY total_time DESC) AS rn FROM pg_stat_statements ORDER BY total_time DESC ) AS t ) AS ts 


Le rĂ©sultat de la requĂȘte est composĂ© de trois colonnes, chacune contenant un pourcentage du temps consacrĂ© au traitement des demandes de ce groupe. Dans la requĂȘte, il y a deux nombres (dans mon cas, 20 et 800) qui sĂ©parent les demandes d'un groupe d'un autre.

C'est ainsi que les proportions de demandes au moment du démarrage des travaux d'optimisation sont à peu prÚs en corrélation.



Le diagramme montre que la part des demandes TOP a fortement diminué, mais les «paysans moyens» ont augmenté.
Initialement, les erreurs TOP ont touchĂ© les requĂȘtes TOP. Au fil du temps, les maladies infantiles ont disparu, la part des demandes TOP a Ă©tĂ© rĂ©duite et davantage d'efforts ont dĂ» ĂȘtre faits pour accĂ©lĂ©rer les demandes difficiles.

Pour obtenir le texte des demandes, nous utilisons une telle demande
 SELECT * FROM ( SELECT ROW_NUMBER () OVER (ORDER BY total_time DESC) AS rn, total_time / (SELECT sum(total_time) FROM pg_stat_statements) * 100 AS tt_percent, query FROM pg_stat_statements ORDER BY total_time DESC ) AS T WHERE rn <= 20 -- TOP -- rn > 20 AND rn <= 800 -- MEDIUM -- rn > 800 -- TAIL 


Voici une liste des astuces les plus couramment utilisĂ©es qui nous ont aidĂ©s Ă  accĂ©lĂ©rer les requĂȘtes TOP:

  • Refonte des systĂšmes, par exemple, traitement de la logique de notification sur le courtier de messages au lieu des requĂȘtes de base de donnĂ©es pĂ©riodiques
  • Ajout ou modification d'index
  • Réécrire les requĂȘtes ORM en SQL pur
  • Réécrire la logique de chargement des donnĂ©es paresseuses
  • Mise en cache via la dĂ©normalisation des donnĂ©es. Par exemple, nous avons un lien entre les tables Livraison -> Facture -> Demande -> Demande. En d'autres termes, chaque livraison est associĂ©e Ă  l'application via d'autres tables. Afin de ne pas lier toutes les tables dans chaque demande, nous avons dupliquĂ© le lien vers l'application dans la table de livraison.
  • Mise en cache des tables statiques avec des rĂ©pertoires et rarement des tables changeantes dans la mĂ©moire du programme.

Parfois, les changements entraĂźnaient une refonte impressionnante, mais ils donnaient 5 Ă  10% du dĂ©chargement du systĂšme et Ă©taient justifiĂ©s. Au fil du temps, l'Ă©chappement est devenu de moins en moins, et la refonte devait ĂȘtre de plus en plus sĂ©rieuse.

Nous avons ensuite attirĂ© l'attention sur le deuxiĂšme groupe de demandes - le groupe des paysans moyens. Il a beaucoup plus de demandes et il semblait que cela prendrait beaucoup de temps pour analyser l'ensemble du groupe. Cependant, la plupart des requĂȘtes se sont avĂ©rĂ©es trĂšs simples pour l'optimisation, et de nombreux problĂšmes ont Ă©tĂ© rĂ©pĂ©tĂ©s des dizaines de fois dans diffĂ©rentes variantes. Voici des exemples d'optimisations typiques que nous avons appliquĂ©es Ă  des dizaines de requĂȘtes similaires et chaque groupe de requĂȘtes optimisĂ©es a dĂ©chargĂ© la base de donnĂ©es de 3 Ă  5%.

  • Au lieu de vĂ©rifier la prĂ©sence d'enregistrements avec COUNT et une analyse complĂšte de la table, EXISTS
  • Nous nous sommes dĂ©barrassĂ©s de DISTINCT (il n'y a pas de recette gĂ©nĂ©rale, mais parfois vous pouvez facilement vous en dĂ©barrasser en accĂ©lĂ©rant la requĂȘte 10 Ă  100 fois).

    Par exemple, au lieu d'interroger pour sélectionner tous les pilotes sur une grande table de livraison (LIVRAISON)

     SELECT DISTINCT P.ID, P.FIRST_NAME, P.LAST_NAME FROM DELIVERY D JOIN PERSON P ON D.DRIVER_ID = P.ID 

    a fait une demande pour une table PERSON relativement petite

     SELECT P.ID, P.FIRST_NAME, P.LAST_NAME FROM PERSON WHERE EXISTS(SELECT D.ID FROM DELIVERY WHERE D.DRIVER_ID = P.ID) 

    Il semblerait que nous ayons utilisĂ© une sous-requĂȘte corrĂ©lative, mais cela donne une accĂ©lĂ©ration de plus de 10 fois.
  • Dans de nombreux cas, COUNT et
    remplacé par le calcul de la valeur approximative
  • au lieu de

     UPPER(s) LIKE JOHN%' 

    utiliser

     s ILIKE “John%” 


Chaque demande spĂ©cifique a parfois Ă©tĂ© accĂ©lĂ©rĂ©e de 3 Ă  1 000 fois. MalgrĂ© les performances impressionnantes, au dĂ©but, il nous semblait inutile d'optimiser la requĂȘte, qui Ă©tait exĂ©cutĂ©e pendant 10 ms, incluse dans la 3e centaine des requĂȘtes les plus lourdes et dans le temps total de chargement de la base de donnĂ©es, elle prenait des centiĂšmes de pour cent. Mais en appliquant la mĂȘme recette Ă  un groupe de demandes similaires, nous avons regagnĂ© plusieurs pour cent. Afin de ne pas perdre de temps Ă  visualiser manuellement les centaines de requĂȘtes, nous avons Ă©crit plusieurs scripts simples qui, Ă  l'aide d'expressions rĂ©guliĂšres, ont trouvĂ© des requĂȘtes similaires. Par consĂ©quent, la recherche automatique de groupes de requĂȘtes nous a permis d'amĂ©liorer encore nos performances en dĂ©ployant des efforts modestes.

En consĂ©quence, nous travaillons sur le mĂȘme matĂ©riel depuis trois ans maintenant. La charge journaliĂšre moyenne est d'environ 30%, aux pointes elle atteint 70%. Le nombre de demandes ainsi que le nombre d'utilisateurs a augmentĂ© d'environ 10 fois. Et tout cela grĂące au suivi constant de ces mĂȘmes groupes de requĂȘtes TOP-MEDIUM. DĂšs qu'une nouvelle demande apparaĂźt dans le groupe TOP, nous l'analysons immĂ©diatement et essayons de l'accĂ©lĂ©rer. Nous passons en revue le groupe MEDIUM une fois par semaine Ă  l'aide de scripts d'analyse de requĂȘte. Si vous rencontrez de nouvelles demandes que nous savons dĂ©jĂ  optimiser, nous les modifions rapidement. Parfois, nous trouvons de nouvelles mĂ©thodes d'optimisation qui peuvent ĂȘtre appliquĂ©es Ă  plusieurs requĂȘtes Ă  la fois.

Selon nos prĂ©visions, le serveur actuel supportera une augmentation du nombre d'utilisateurs de 3 Ă  5 fois. Certes, nous avons un atout de plus dans la pochette; nous n'avons toujours pas traduit les requĂȘtes SELECT dans le miroir, comme recommandĂ©. Mais nous ne le faisons pas consciemment, car nous voulons d'abord Ă©puiser pleinement les possibilitĂ©s d'optimisation «intelligente» avant d'activer «l'artillerie lourde».
Un examen critique du travail effectué peut suggérer l'utilisation d'une mise à l'échelle verticale. Achetez un serveur plus puissant, au lieu de perdre le temps des spécialistes. Le serveur peut ne pas coûter si cher, d'autant plus que les limites de la mise à l'échelle verticale n'ont pas encore été épuisées. Cependant, seul le nombre de demandes a augmenté 10 fois. Depuis plusieurs années, la fonctionnalité du systÚme a augmenté et il existe désormais plus de variétés de demandes. La fonctionnalité qui était due à la mise en cache est réalisée par moins de demandes, de plus, des demandes plus efficaces. Vous pouvez donc multiplier en toute sécurité par 5 autres pour obtenir le coefficient d'accélération réel. Ainsi, selon les estimations les plus prudentes, nous pouvons dire que l'accélération a été de 50 fois ou plus. Secouer verticalement le serveur 50 fois coûterait plus cher. Surtout si l'on considÚre qu'une fois l'optimisation est effectuée tout le temps, et une facture pour un serveur loué vient chaque mois.

Source: https://habr.com/ru/post/fr461071/


All Articles