web-dev-qa-db-fra.com

Requête à exécution longue sur un réplica en lecture seule qui prend des instants sur le serveur principal

J'ai une configuration AG à 4 nœuds comme suit:

Configuration matérielle VM de tous les nœuds:

  • Microsoft SQL Server 2017 Enterprise Edition (RTM-CU14) (KB4484710)
  • 16 processeurs virtuels
  • 356 Go RAM (longue histoire à celui-ci ...)
  • degré maximal de parallélisme: 1 (tel que requis par le fournisseur de l'application)
  • seuil de coût pour le parallélisme: 50
  • mémoire serveur maximale (Mo): 338944 (331 Go)

Configuration AG:

  • Noeud 1: validation principale ou synchrone secondaire non lisible, configuré pour le basculement automatique
  • Noeud 2: validation primaire ou synchrone secondaire non lisible, configuré pour le basculement automatique
  • Noeud 3: ensemble secondaire lisible avec validation asynchrone, configuré pour le basculement manuel
  • Noeud 4: ensemble secondaire lisible avec validation asynchrone, configuré pour le basculement manuel

La requête en question:

Il n'y a rien de terriblement fou dans cette requête, elle fournit un résumé des éléments de travail en suspens dans diverses files d'attente au sein de l'application. Vous pouvez voir le code à partir de l'un des liens du plan d'exécution ci-dessous.

Comportement d'exécution sur le nœud principal:

Lorsqu'il est exécuté sur le nœud principal, le temps d'exécution se situe généralement autour de la marque 1 seconde. Voici le plan d'exécution , et ci-dessous sont les statistiques capturées à partir des STATISTIQUES IO et STATISTICS TIME du nœud principal:

(347 rows affected)
Table 'Worktable'. Scan count 647, logical reads 2491, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'workitemlc'. Scan count 300, logical reads 7125, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'schedulertask'. Scan count 1, logical reads 29, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'wfschedulertask'. Scan count 1, logical reads 9, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'schedulerservice'. Scan count 1, logical reads 12, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'schedulerworkerpool'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'itemlc'. Scan count 1, logical reads 26372, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 500 ms,  elapsed time = 656 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Comportement d'exécution sur le nœud secondaire en lecture seule:

Lors de l'exécution sur un secondaire en lecture seule Node (ie Node 3 ou Node 4), cette requête utilise le même plan d'exécution (il s'agit d'un lien de plan différent) et à peu près les mêmes statistiques d'exécution sont affichées (par exemple, il peut y avoir quelques analyses de page supplémentaires car ces résultats changent toujours), mais à l'exception du temps CPU , ils sont très similaires. Voici les statistiques capturées à partir de STATISTICS IO et STATISTICS TIME à partir du nœud secondaire en lecture seule:

(347 rows affected)
Table 'Worktable'. Scan count 647, logical reads 2491, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'workitemlc'. Scan count 300, logical reads 7125, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'schedulertask'. Scan count 1, logical reads 29, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'wfschedulertask'. Scan count 1, logical reads 9, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'schedulerservice'. Scan count 1, logical reads 12, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'schedulerworkerpool'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'itemlc'. Scan count 1, logical reads 26372, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row affected)

 SQL Server Execution Times:
   CPU time = 55719 ms,  elapsed time = 56335 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Autres détails:

J'ai également exécuté les scripts sp_WhoIsActive et WaitingTasks.sql de Paul Randal sur le secondaire pendant l'exécution de cette requête, mais je ne vois pas toute attente se produisant quoi que ce soit, ce qui est franchement frustrant:

enter image description here

Cela ne semble pas non plus être un cas de latence AG car l'état de synchronisation est en fait assez bon:

--https://sqlperformance.com/2015/08/monitoring/availability-group-replica-sync

SELECT 
       ar.replica_server_name, 
       adc.database_name, 
       ag.name AS ag_name, 
       drs.is_local, 
       drs.synchronization_state_desc, 
       drs.synchronization_health_desc, 
       --drs.last_hardened_lsn, 
       --drs.last_hardened_time, 
       drs.last_redone_time, 
       drs.redo_queue_size, 
       drs.redo_rate, 
       (drs.redo_queue_size / drs.redo_rate) / 60.0 AS est_redo_completion_time_min,
       drs.last_commit_lsn, 
       drs.last_commit_time
FROM sys.dm_hadr_database_replica_states AS drs
INNER JOIN sys.availability_databases_cluster AS adc 
       ON drs.group_id = adc.group_id AND 
       drs.group_database_id = adc.group_database_id
INNER JOIN sys.availability_groups AS ag
       ON ag.group_id = drs.group_id
INNER JOIN sys.availability_replicas AS ar 
       ON drs.group_id = ar.group_id AND 
       drs.replica_id = ar.replica_id
ORDER BY 
       ag.name, 
       ar.replica_server_name, 
       adc.database_name;

enter image description here

Cette requête semble être le pire délinquant. Les autres requêtes qui prennent également moins d'une seconde sur le nœud principal Node peut prendre de 1 à 5 secondes sur le nœud secondaire, et bien que le comportement ne soit pas aussi grave, il semble causer des problèmes.

Enfin, j'ai également regardé les serveurs et vérifié les processus externes tels que les analyses A/V, les travaux externes générant des E/S inattendues, etc. et je suis venu les mains vides. Je ne pense pas que cela soit causé par quoi que ce soit en dehors du processus SQL Server.

La question:

Il est seulement midi où je suis et la journée a déjà été longue, donc je soupçonne que je manque quelque chose d'évident ici. Soit cela, soit nous avons quelque chose de mal configuré, ce qui est possible car nous avons eu un certain nombre d'appels au fournisseur et à la MS liés à cet environnement.

Pour toute mon enquête, je n'arrive pas à trouver ce qui cause cette différence de performances. Je m'attendrais à voir une sorte d'attente se produire sur les nœuds secondaires, mais rien. Comment puis-je résoudre davantage ce problème pour identifier la cause première? Quelqu'un a-t-il déjà vu ce comportement et trouvé un moyen de le résoudre?

MISE À JOUR # 1 Après avoir échangé les états du troisième nœud (l'une des répliques en lecture seule) sur non-lisible, puis de nouveau sur lisible en tant que test, cette réplique est toujours bloquée par une transaction ouverte, avec toutes les requêtes clientes affichant l'attente HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING .

L'exécution d'une commande DBCC OPENTRAN Donne les résultats suivants:

Oldest active transaction:
    SPID (server process ID): 420s
    UID (user ID) : -1
    Name          : QDS nested transaction
    LSN           : (941189:33148:8)
    Start time    : May  7 2019 12:54:06:753PM
    SID           : 0x0
DBCC execution completed. If DBCC printed error messages, contact your system administrator.

Lorsque vous recherchez ce SPID dans sp_who2, Il l'affiche comme un processus BACKGROUND avec QUERY STORE BACK Répertorié comme commande.

Alors que nous sommes capables de prendre des sauvegardes TLog, je soupçonne que nous rencontrons des fonctionnalités similaires de ce bug résol , donc je prévois d'ouvrir un ticket avec MS à ce sujet problème aujourd'hui.

Selon le résultat de ce ticket, j'essaierai de capturer une trace de pile d'appels selon la suggestion de Joe et de voir où nous allons.

Mise à jour finale (problème auto-résolu)

Après avoir éclipsé la marque de 52 heures d'ouverture de la transaction Query Store (comme identifié ci-dessus), l'AG a décidé de basculer automatiquement. Avant cela, j'ai extrait quelques mesures supplémentaires. Par ce lien , fourni par Sean, la base de données en question avait un très grand magasin de versions dédié à cette base de données, en particulier à un moment donné, j'avais enregistré 1651360 pages dans le champ reserved_page_count Et 13210880 pour la valeur reserved_space_kb.

Selon les ERRORLOG, le basculement s'est produit après un déluge de 5 minutes d'échecs de durcissement des transactions liés aux transactions QDS base transaction Et QDS nested transaction.

Le basculement a provoqué une panne d'environ 10 minutes dans mon cas. La base de données a une taille de ~ 6 To et est très active, ce qui était en fait assez bon à mon avis. Pendant que le nouveau nœud principal était en ligne pendant cette période, aucune requête client ne pouvait se terminer car ils attendaient tous sur le type d'attente QDS_LOADDB .

Après le basculement, les numéros de magasin de versions ont été réduits à 176 pour reserved_page_count Et 1408 pour reserved_space_kb. Les requêtes sur les réplicas en lecture seule secondaires ont également commencé à s'exécuter aussi rapidement que si elles étaient exécutées à partir du serveur principal, il semble donc que le comportement ait complètement disparu à la suite du basculement.

8
John Eisbrener

Cette réponse s'ajoute à la réponse de Joe car je ne peux pas être sûr à 100% qu'il s'agit du magasin de versions, mais il y a suffisamment de preuves jusqu'à présent pour impliquer que cela fait partie du problème.

Lorsqu'un réplica secondaire est marqué comme lisible, un bon état stable pour les informations de version doit d'abord être atteint afin qu'il existe un bon point de départ connu pour toutes les opérations de lecture sur le secondaire. Lorsque cela attend la transition et qu'il reste des transactions ouvertes sur le serveur principal, cela se manifestera par HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING et est également un bon indicateur que le primaire passe par un certain nombre de désabonnements de données (ou au moins quelqu'un a une très longue transaction ouverte qui n'est pas bonne non plus). Plus les transactions sont ouvertes et plus il y a de changements de données, plus le versioning se produira.

Les réplicas secondaires atteignent un état lisible en utilisant l'isolement de cliché sous les couvertures de la session, même si si vous vérifiez vos informations de session, vous verrez qu'elles apparaissent à la lecture par défaut validée. Étant donné que l'isolement des instantanés est optimiste et utilise le magasin de versions, toutes les modifications devront être versionnées. Ceci est exacerbé lorsqu'il existe de nombreuses requêtes en cours d'exécution (et potentiellement longues) sur le secondaire alors que le taux de désabonnement des données est élevé sur le primaire. Généralement, cela ne se manifeste que dans quelques tableaux pour un système OLTP mais il dépend entièrement de l'application et de la charge de travail.

Le magasin de versions lui-même est mesuré en générations et lorsqu'une requête est exécutée qui nécessite l'utilisation du magasin de versions, le pointeur d'enregistrement de version est utilisé pour pointer vers la chaîne TempDB de cette ligne. Je dis chaîne, car c'est une liste de versions pour cette ligne et la chaîne entière doit être parcourue séquentiellement pour trouver la version appropriée en fonction de l'horodatage de début de la transaction afin que les résultats soient en ligne avec les données à ce moment donné.

Si le magasin de versions a plusieurs générations pour ces lignes en raison de transactions de longue durée sur les réplicas principal et secondaire, cela entraînera des délais d'exécution des requêtes plus longs que la moyenne et généralement sous la forme d'un processeur plus élevé, tandis que tous les autres éléments resteront exactement les mêmes. - tels que le plan d'exécution, les statistiques, les lignes retournées, etc. La marche de la chaîne est presque une opération purement CPU, donc lorsque les chaînes deviennent très longues et que le nombre de lignes retournées est très élevé, vous obtenez un (non linéaire, mais peut être proche) augmentation du temps pour la requête.

La seule chose qui peut être faite est de limiter la longueur des transactions sur le primaire et le secondaire pour s'assurer que le magasin de versions ne devient pas trop volumineux dans TempDB tout en ayant plusieurs générations. Les tentatives de nettoyage du magasin de versions se produisent environ une fois par minute, mais le nettoyage nécessite que toutes les versions de la même génération ne soient plus nécessaires avant de pouvoir être supprimées et que toutes les versions futures ne puissent pas être nettoyées tant que la version la plus ancienne n'est plus nécessaire. Ainsi, une requête de longue durée peut entraîner l'impossibilité de nettoyer efficacement de nombreuses générations inutilisées.

Le fait de basculer la réplique en mode lisible et hors tension effacera également le magasin de versions car il n'est plus lisible.

Il existe d'autres éléments qui pourraient également être en jeu, mais cela semble le plus plausible compte tenu des données actuelles et de la réaction de la réplique.

TempDB Versioning DMVs (à ne pas confondre avec le versioning ADR).

9
Sean Gallardy

Avertissement: je ne sais rien des groupes de disponibilité, mais je connais un peu le dépannage des requêtes qui semblent utiliser plus de CPU qu'elles ne le devraient.

Vous avez un problème de processeur dans la mesure où vous en utilisez trop. Une chose importante à dire sur les attentes est que presque toutes ne sont pas occupées par le processeur. Lorsqu'un travailleur entre dans un état d'attente, il a cédé et ne s'exécute plus sur le planificateur dans SQLOS. Donc, si vous avez une requête MAXDOP 1 avec les statistiques d'exécution suivantes:

Temps CPU = 55719 ms, temps écoulé = 56335 ms.

Vous avez atteint près de 99% d'utilisation du processeur pour la requête. Pourquoi devrait-il y avoir des statistiques d'attente significatives pour cette requête? Vous pouvez en voir si vous avez certaines des attentes d'occupation du processeur telles que les attentes externes ou préemptives, mais ce n'est pas garanti non plus. L'essentiel est que les statistiques d'attente peuvent ne pas être très utiles ici.

Il y a certaines choses à vérifier dans un ordre approximatif (l'ordre dépend de ce que vous savez sur l'environnement):

  • Le serveur secondaire a-t-il une surveillance coûteuse en cours, comme des événements étendus, des traces ou un profilage?
  • Le matériel du serveur secondaire correspond-il à peu près au serveur principal?
  • Y a-t-il des problèmes de configuration ou de logiciel avec le serveur secondaire?
  • Des attentes ou des verrous importants? Peut ne pas être applicable à votre requête, mais peut néanmoins fournir des indices.
  • Des verrous importants?
  • Y a-t-il d'autres DMV ou choses que vous pouvez vérifier dans SQL Server qui pourraient donner des indices? Vous avez mentionné que les groupes de disponibilité sont probablement un élément clé du problème.
  • Que vous dit le traçage ETW?
  • Quel type d'accords de support avez-vous?

La plupart de ce qui précède est bien couvert divers articles de blog et documentation, mais je développerai le traçage ETW. Si vous voulez savoir pourquoi SQL Server utilise autant de CPU pour une requête particulière et que vous avez accès à l'hôte, vous pouvez toujours effectuer le traçage ETW pour afficher les piles d'appels et voir combien de CPU font différentes piles d'appels. En d'autres termes, le système d'exploitation hôte est heureux de vous dire à quoi sert le processeur si vous savez comment le demander. Les méthodes courantes pour effectuer le traçage ETW incluent Enregistreur de performances Windows et PerfView .

Comprendre ces résultats nécessite une connaissance approfondie des internes et il est facile d'arriver à une conclusion erronée. Dans de nombreux cas, il est préférable de collecter les données brutes et de demander à des experts de les consulter. Lorsque vous effectuez une trace, vous souhaitez que le moins d'activité possible se poursuive dans SQL Server. Voici quelques réponses publiées ici qui utilisent le suivi ETW pour tirer des conclusions sur SQL Server:

Je soupçonne que dans votre cas, si vous êtes en mesure de collecter des piles d'appels pendant l'exécution de la requête de 45 secondes, vous obtiendrez des indices très utiles sur la nature du problème.

8
Joe Obbish

Comme le problème s'est résolu de lui-même, je me dois de spéculer sur sa cause (rimes non intentionnelles). D'après la publication de Sean et le fait qu'une transaction de magasin de requêtes ouverte semble avoir été la cause principale de l'augmentation de la taille du magasin de versions (par exemple, la cause de HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING attend), je ne peux que supposer que le magasin de requêtes a joué un rôle dans le comportement présenté. Cette base de données est plus grande (~ 6 To), assez active, et la majorité des requêtes qui la frappent sont générées par le client et non paramétrées (c'est-à-dire des requêtes ad hoc), donc je ne pense pas que le magasin de requêtes se prête à fournir beaucoup d'utilisation dans ce scénario. Pour cette raison, nous désactiverons le magasin de requêtes dans cet environnement lors d'une future fenêtre de maintenance, après quoi je pense que nous ne verrons plus ce comportement.

Nous avons ouvert un ticket avec Microsoft, mais le timing n'était pas en notre faveur car le problème a été résolu avant que nous puissions effectuer une analyse détaillée via une trace PSSDIAG ou similaire. J'espère qu'ils seront en mesure de faire des tests localisés et de reproduire ce problème au cas où il s'agirait d'un bogue que nous avons rencontré. Si d'autres mises à jour sur une résolution plus permanente sont identifiées, je serai sûr de mettre à jour cette réponse également.

5
John Eisbrener