Tout un réseau de sites web a ralenti d'un coup, et pas un seul serveur n'était occupé.
Un réseau de sites d'offres d'emploi a semblé surchargé d'un coup. Chaque serveur était inactif. Une seule recherche de ville représentait plus de la moitié du temps total de la base de données. Voici comment nous l'avons trouvée et corrigée avec un seul index posé en ligne.
Dans cet article 8 sections
- Chaque site d'un réseau d'offres d'emploi a ralenti en même temps alors que les serveurs restaient inactifs, et l'instinct était d'en acheter de plus gros.
- Chaque niveau était inactif sauf un : les load balancers et les serveurs web travaillaient à peine. Toute la ferme attendait la base de données, et la base attendait une requête.
- Cette requête, une simple recherche de ville, représentait plus de la moitié de tout le temps de la base, parcourant une table entière à chaque appel pour renvoyer une seule ligne.
- Un index posé en ligne, appliqué sans interrompre les sites, a transformé le scan en seek. Pas besoin de serveurs plus gros.
Un réseau de sites d'offres d'emploi s'est ramolli au même instant. Pas une page, pas un site : tous, ensemble, cette brume à l'échelle de la ferme qui fait supposer à un opérateur que les serveurs se noient et qu'il est temps d'en acheter de plus gros. Cette supposition coûte cher, elle est fréquente, et dans ce cas elle était fausse. Chaque machine de la ferme s'ennuyait. Toute la ferme attendait la base de données, et la base attendait une seule requête.
Chaque niveau était inactif sauf un
Lors d'un ralentissement de toute une ferme, lisez le taux d'utilisation de chaque niveau avant toute autre chose, et voyez lequel est réellement saturé. Les load balancers tournaient à vide. Les serveurs web s'ennuyaient. Rien dans le chemin de la requête ne travaillait assez dur pour expliquer un site lent.
- Load balancers
- inactifs
- Serveurs web
- travaillant à peine
- Base de données
- en attente d'une requête
C'est l'indice qui clôt le débat sur la capacité avant qu'il ne commence. Si chaque niveau est inactif sauf la base de données, un serveur web plus gros vous achète une machine inactive plus rapide. La contrainte n'a jamais été le calcul, et aucune commande de matériel n'allait y remédier.
La réponse coûteuse était d'acheter plus de machines
| L'instinct | La mesure |
|---|---|
| Toute la ferme est lente, donc les serveurs sont surchargés. Achetez-en de plus gros. | Chaque niveau sauf la base de données était inactif. La capacité n'a jamais été la contrainte. |
| CE QUE DISAIENT LES DONNÉESC'est forcément un problème de matériel. | Le matériel était sain, et de façon prouvable : load balancers et serveurs web travaillant à peine. |
| Répartissez la charge sur plus de machines. | Une requête représentait plus de la moitié de tout le temps de la base. Plus de machines l'exécuteraient plus souvent, toujours en scan. |
Plus de matériel aurait mis le gaspillage à l'échelle, pas supprimé. La seule étape honnête était de découvrir à quoi la base de données passait réellement son temps.
Nous avons agrégé l'historique des requêtes par empreinte
Une application écrit rarement la même requête deux fois de la même façon. La même recherche apparaît paramétrée de plusieurs manières différentes, de sorte que compter le texte brut des requêtes cache le coupable dans le bruit. La solution est d'agréger par une empreinte qui survit à cette incohérence, puis de classer chaque instruction selon sa part du temps total de la base. Nous l'avons exécuté sur tout l'historique des requêtes.
-- reconstructed, de-identified rendering of cleared facts. not a real capture.
-- full query history, grouped by query_hash
-- (a fingerprint that survives inconsistent parameterisation)
SELECT TOP 5
query_hash,
SUM(execution_count) AS calls,
SUM(total_logical_reads) AS pages_read,
SUM(total_elapsed_time) * 100.0
/ SUM(SUM(total_elapsed_time)) OVER () AS pct_of_db_time
FROM sys.dm_exec_query_stats
GROUP BY query_hash
ORDER BY pct_of_db_time DESC;Une ligne ressortait : plus de la moitié de tout ce que faisait la base, dépensée sur une seule instruction. La méthode est volontairement ennuyeuse, elle ne devine pas, et elle est la même à chaque fois.
Une requête représentait plus de la moitié de la base
L'instruction qui en est sortie est du genre qu'on ne regarderait jamais deux fois. Une recherche de ville, deux colonnes, un filtre.
-- reconstructed, de-identified. table/column names renamed from the real schema.
SELECT CityID, CityName
FROM Cities
WHERE RoutingPath = @p;
-- Execution plan: Clustered Index Scan (a full scan)
-- Returns: ONE row
-- Filtered column: RoutingPath --> no indexLa colonne sur laquelle elle filtrait n'avait pas d'index. Chaque appel parcourait donc la table entière pour rendre une seule ligne. En soi, c'est un petit coût ; répété à chaque affichage de page sur tout le réseau, c'est devenu la plus grosse chose que la base faisait.
Une réponse d'une ligne doit lire deux ou trois pages, pas toute la table
“Une réponse d'une ligne doit lire deux ou trois pages, pas des centaines.”
Cette phrase est tout le diagnostic, compressé en quelque chose que vous pouvez dire à la personne qui possède le code. Rien d'autre ne fait lire autant à une recherche d'une seule ligne. Ni un disque lent, ni une CPU occupée, ni une mauvaise nuit. Une réponse d'une ligne qui lit toute la table est un full scan, et un full scan sur une colonne filtrée est une signature sans équivoque : la colonne a besoin d'un index. Nous n'avons pas eu à débattre de matériel, nous avons simplement montré les lectures.
Un index posé en ligne, du scan au seek
- Un full scan de la table, pour une ligne
- Aucun index sur la colonne filtrée
- Plus de la moitié de tout le temps de la base
- Un index seek, deux ou trois pages pour la même ligne
- Une petite fraction du travail par appel
- L'index posé en ligne, sans interrompre les sites
-- reconstructed, de-identified. index name illustrative, on the renamed identifiers.
-- Index the column in the WHERE clause; cover the two columns it returns
CREATE NONCLUSTERED INDEX IX_Cities_RoutingPath
ON Cities (RoutingPath)
INCLUDE (CityID, CityName)
WITH (ONLINE = ON); -- applied without interrupting the site
-- New execution plan: Index SeekUn index, sur la seule colonne sur laquelle l'application filtrait réellement. Il a été appliqué en ligne, sans interrompre les sites, et la recherche qui avait dominé la base est devenue l'une des choses les moins coûteuses qu'elle faisait.
L'ingénieur du client a enrichi le diagnostic
Le meilleur de cette collaboration n'était pas notre index. C'est ce qui s'est passé quand l'ingénieur base de données du client lui-même s'est joint à la discussion et a enrichi le diagnostic : il a apporté un schéma de blocage lecteur/écrivain de son côté du système. Nous avons intégré son correctif au nôtre, et les deux sont passés ensemble.
“L'ingénieur du client a quitté l'appel en collaborateur.”
C'est la différence entre un ticket de support et un diagnostic. Quand la découverte est vraie et que vous montrez les preuves, les deux côtés cessent de défendre des positions et commencent à améliorer la plateforme ensemble.
Quand chaque niveau est inactif sauf un, arrêtez de regarder la capacité
La leçon est courte et c'est l'inverse du réflexe. Une ferme lente avec des machines inactives n'est pas un problème de capacité, c'est une question logicielle déguisée en problème de capacité. Voici ce qu'il faut vérifier avant de signer une commande de matériel.
Lisez d'abord le taux d'utilisation de chaque niveau.
Load balancers, web, base de données. Si tout est inactif sauf un niveau, la capacité n'est pas votre problème et un serveur plus gros n'y changera rien.
Agrégez les statistiques de requêtes par empreinte.
Regroupez votre historique par un hash qui survit au paramétrage incohérent, pour que la même instruction écrite de plusieurs façons compte pour une. Le pire coupable ressort d'habitude avec une large marge.
Comparez les pages lues aux lignes renvoyées.
Une réponse d'une ligne qui lit des centaines de pages scanne, elle ne cherche pas. Cet écart est la signature d'un index manquant.
Indexez la colonne sur laquelle l'application filtre réellement.
Pas toutes les colonnes. Celle du WHERE qui s'exécute des millions de fois. Appliquez-le en ligne, sans interrompre le site.
Mettez en cache les minuscules tables chaudes.
Les petites tables de référence qui changent rarement ont leur place dans le cache, pas sur le chemin critique de la base pour chaque affichage de page.
Pendant tout ce temps, la machine est restée calme. Les load balancers tournaient à vide, les serveurs web s'ennuyaient, et le correctif était un index sur la colonne sur laquelle l'application filtrait. Le serveur de base de données était sain ; une instruction à l'intérieur avait besoin d'un index.
Réponses directes
Comment une seule requête peut-elle représenter plus de la moitié de la charge d'une base de données ?+
Quand une requête s'exécute assez souvent et fait trop de travail à chaque fois, elle domine tout le reste. Celle-ci s'exécutait à chaque affichage de page et parcourait une table entière à chaque appel pour renvoyer une seule ligne, parce que la colonne sur laquelle elle filtrait n'avait pas d'index. Multipliez un petit gaspillage par des millions d'appels et il devient toute la base.
Pourquoi les serveurs étaient-ils inactifs si les sites étaient lents ?+
Parce que le goulet était dans la base de données, en attente d'une requête. Les load balancers et les serveurs web travaillaient à peine. Toute la ferme attendait la base, et la base attendait une recherche qui aurait dû être instantanée. Ajouter des serveurs n'aurait fait qu'ajouter des machines inactives.
Comment trouvez-vous la requête qui est vraiment le problème ?+
On agrège tout l'historique des requêtes par une empreinte qui survit au paramétrage incohérent, puis on classe par part du temps total de la base. La méthode est ennuyeuse et elle ne devine pas.
Quelle est la signature d'un index manquant ?+
Une réponse d'une ligne qui lit des centaines de pages. Si une requête renvoie une ligne et que le plan montre un scan sur toute la table, la colonne sur laquelle elle filtre n'est pas indexée.
La correction a-t-elle exigé une interruption ?+
Non. L'index a été posé en ligne, appliqué sans interrompre les sites, et il a transformé le scan en seek.
Qui l'a corrigé, vous ou le client ?+
Les deux, ensemble. Nous avons trouvé la recherche et ajouté l'index ; l'ingénieur base de données du client lui-même s'est joint au diagnostic et a apporté un schéma de blocage lecteur/écrivain de son côté. Nous avons intégré son correctif au nôtre.
Site lent, serveurs inactifs ? Nous trouverons la vraie cause.
Parlez à un ingénieur qui lit les plans d'exécution, pas seulement les tableaux de bord, pour savoir ce qui ralentit réellement votre plateforme.
Parler à un ingénieurL'instinct face à une ferme lente est de s'en sortir en achetant. La réponse honnête est presque toujours moins chère : lisez chaque niveau, trouvez celui qui est saturé, et allez lire ses requêtes.
Vous êtes arrivé à la fin.
Mettez la lecture en pratique
Que faites-vous tourner ?
Parlez-nous de la charge de travail, du trafic et des exigences de reprise. Commencez par le configurateur de serveur, ou passez en revue les détails avec un ingénieur.
Écrit par les ingénieurs qui construisent et exploitent l'infrastructure qu'il décrit.Parler à un ingénieur
