Oui, mon ancien ordinateur portable est plusieurs fois plus puissant que votre serveur de production.

Ce sont exactement les plaintes que j’ai entendues de la part de nos développeurs. Le plus intéressant est que cela s’est avéré vrai, donnant lieu à une longue enquête. Nous parlerons des serveurs SQL qui fonctionnent sur VMware.

Oui, mon ancien ordinateur portable est plusieurs fois plus puissant que votre serveur de production.

En fait, il est facile de s’assurer que le serveur de production est désespérément derrière l’ordinateur portable. Exécutez (pas sur tempdb et pas sur une base de données avec la durabilité différée activée) le code :

set nocount on
create table _t (v varchar(100))
declare @n int=300000
while @n>0 begin 
  insert into _t select 'What a slowpoke!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

Sur mon bureau, cela prend 5 secondes et sur le serveur de production, cela prend 28 secondes. Parce que SQL doit attendre la fin physique de l'entrée du journal des transactions, et nous effectuons ici des transactions très courtes. En gros, nous avons conduit un gros et puissant camion dans la circulation urbaine et l'avons vu se faire dépasser en toute vitesse par des livreurs de pizza sur des scooters - le débit n'est pas important ici, seule la latence est importante. Et aucun stockage réseau, quel que soit le nombre de zéros dans son prix, ne peut battre le SSD local en termes de latence.

(dans les commentaires, il s'est avéré que j'avais menti - j'avais retardé la durabilité aux deux endroits. Sans retard de durabilité, il s'avère :
Bureau - 39 secondes, 15 0.065 tr/sec, XNUMX ms/io aller-retour
PROD - 360 secondes, 1600 tr/sec, 0.6 ms
J'aurais dû remarquer que c'était trop rapide)

Cependant, dans ce cas, nous avons affaire à des zéros triviaux de la fonction zêta de Riemann avec un exemple trivial. Dans l’exemple que les développeurs m’ont apporté, c’était différent. J'étais convaincu qu'ils avaient raison et j'ai commencé à supprimer de l'exemple toutes leurs spécificités liées à la logique métier. À un moment donné, j'ai réalisé que je pouvais complètement jeter leur code et écrire le mien - ce qui démontre le même problème - en production, il fonctionne 3 à 4 fois plus lentement :

create function dbo.isPrime (@n bigint)
returns int
as
  begin
  if @n = 1 return 0
  if @n = 2 return 1
  if @n = 3 return 1
  if @n % 2 = 0 return 0
  declare @sq int
  set @sq = sqrt(@n)+1 -- check odds up to sqrt
  declare @dv int = 1
  while @dv < @sq 
    begin
	set @dv=@dv+2
	if @n % @dv = 0 return 0
	end
  return 1
  end
GO
declare @dt datetime set @dt=getdate()
select dbo.isPrime(1000000000000037)
select datediff(ms,@dt,getdate()) as ms
GO

Si tout va bien, la vérification de la primalité d'un nombre prendra 6-7-8 secondes. Cela s'est produit sur un certain nombre de serveurs. Mais sur certains, le contrôle a pris 25 à 40 secondes. Fait intéressant, il n'y avait pas de serveurs sur lesquels l'exécution prendrait, disons, 14 secondes - le code fonctionnait soit très rapidement, soit très lentement, c'est-à-dire que le problème était, disons, en noir et blanc.

Ce que j'ai fait? Métriques VMware utilisées. Tout allait bien là-bas - il y avait une abondance de ressources, Temps de préparation = 0, il y avait assez de tout, pendant le test sur des serveurs rapides et lents CPU = 100 sur un vCPU. J'ai fait un test pour calculer le nombre Pi - le test a montré les mêmes résultats sur n'importe quel serveur. L'odeur de la magie noire devenait de plus en plus forte.

Une fois arrivé à la ferme DEV, j'ai commencé à jouer avec les serveurs. Il s'est avéré que vMotion d'hôte à hôte peut « guérir » un serveur, mais il peut également transformer un serveur « rapide » en un serveur « lent ». Il semblerait que ce soit le cas : certains hôtes ont un problème... mais... non. Certaines machines virtuelles étaient lentes sur l'hôte, disons A, mais fonctionnaient rapidement sur l'hôte B. Et une autre machine virtuelle, au contraire, fonctionnait rapidement sur A et ralentissait sur B ! Les machines « rapides » et « lentes » tournaient souvent sur l'hôte !

A partir de ce moment, une odeur distincte de soufre se répandit dans l’air. Après tout, le problème ne pouvait pas être attribué à la machine virtuelle (les correctifs Windows, par exemple) - après tout, il est devenu « rapide » avec vMotion. Mais le problème ne pouvait pas non plus être attribué à l'hôte - après tout, il pouvait avoir des machines à la fois « rapides » et « lentes ». De plus, cela n'était pas lié à la charge - j'ai réussi à obtenir une machine « lente » sur l'hôte, où il n'y avait rien d'autre à part.

En désespoir de cause, j'ai lancé Process Explorer depuis Sysinternals et examiné la pile SQL. Sur les machines lentes, la ligne a immédiatement attiré mon attention :

ntoskrnl.exe!KeSynchronizeExecution+0x5bf6
ntoskrnl.exe!KeWaitForMultipleObjects+0x109d
ntoskrnl.exe!KeWaitForMultipleObjects+0xb3f
ntoskrnl.exe!KeWaitForSingleObject+0x377
ntoskrnl.exe!KeQuerySystemTimePrecise+0x881 < — !!!
ntoskrnl.exe!ObDereferenceObjectDeferDelete+0x28a
ntoskrnl.exe!KeSynchronizeExecution+0x2de2
sqllang.dll!CDiagThreadSafe ::PxlvlReplace+0x1a20
... sauté
sqldk.dll!SystemThread::MakeMiniSOSThread+0xa54
KERNEL32.DLL! BaseThreadInitThunk + 0x14
ntdll.dll! RtlUserThreadStart + 0x21

C'était déjà quelque chose. Le programme a été écrit :

    class Program
    {
        [DllImport("kernel32.dll")]
        static extern void GetSystemTimePreciseAsFileTime(out FILE_TIME lpSystemTimeAsFileTime);

        [StructLayout(LayoutKind.Sequential)]
        struct FILE_TIME
        {
            public int ftTimeLow;
            public int ftTimeHigh;
        }

        static void Main(string[] args)
        {
            for (int i = 0; i < 16; i++)
            {
                int counter = 0;

                var stopwatch = Stopwatch.StartNew();

                while (stopwatch.ElapsedMilliseconds < 1000)
                {
                    GetSystemTimePreciseAsFileTime(out var fileTime);
                    counter++;
                }

                if (i > 0)
                {
                    Console.WriteLine("{0}", counter);
                }
            }
        }
    }

Ce programme a démontré un ralentissement encore plus prononcé : sur les machines « rapides », il affiche 16 à 18 millions de cycles par seconde, tandis que sur les machines lentes, il affiche un million et demi, voire 700 10. Autrement dit, la différence est de 20 à XNUMX fois (!!!). C'était déjà une petite victoire : de toute façon, il n'y avait aucune menace de se retrouver coincé entre le support Microsoft et VMware pour qu'ils se tournent les flèches l'un contre l'autre.

Puis les progrès se sont arrêtés - vacances, affaires importantes, hystérie virale et forte augmentation de la charge de travail. J'ai souvent évoqué le problème magique avec mes collègues, mais parfois il semblait qu'ils ne me croyaient même pas toujours - l'affirmation selon laquelle VMware ralentissait le code de 10 à 20 fois était trop monstrueuse.

J'ai essayé de découvrir moi-même ce qui me ralentissait. Parfois, il me semblait que j'avais trouvé une solution : activer et désactiver les hot plugs, modifier la quantité de mémoire ou le nombre de processeurs transformaient souvent la machine en une machine « rapide ». Mais pas pour toujours. Mais ce qui s'est avéré vrai, c'est qu'il suffit de sortir et de frapper au volant, c'est-à-dire de changer tout paramètre de machine virtuelle

Finalement, mes collègues américains en ont soudainement découvert la cause profonde.

Oui, mon ancien ordinateur portable est plusieurs fois plus puissant que votre serveur de production.

Les hôtes différaient en fréquence !

  • En règle générale, ce n’est pas grave. Mais : lors du passage d'un hôte « natif » à un hôte avec une fréquence « différente », VMware doit ajuster le résultat GetTimePrecise.
  • En règle générale, cela ne pose pas de problème, sauf s'il existe une application qui demande l'heure exacte des millions de fois par seconde, comme le serveur SQL.
  • Mais ce n'est pas effrayant, puisque SQL Server ne le fait pas toujours (voir Conclusion)

Mais il y a des cas où ce râteau frappe fort. Et pourtant, oui, en tapant sur la molette (en modifiant quelque chose dans les paramètres de la VM) j'ai forcé VMware à 'recalculer' la configuration, et la fréquence de l'hôte actuel est devenue la fréquence 'native' de la machine.

décision

www.vmware.com/files/pdf/techpaper/Timekeeping-In-VirtualMachines.pdf

Lorsque vous désactivez la virtualisation du TSC, la lecture du TSC depuis la machine virtuelle renvoie la valeur TSC de la machine physique et l'écriture du TSC depuis la machine virtuelle n'a aucun effet. La migration de la machine virtuelle vers un autre hôte, sa reprise à partir d'un état suspendu ou le retour à un instantané entraîne un saut discontinu du TSC. Certains systèmes d'exploitation invités ne parviennent pas à démarrer ou présentent d'autres problèmes de chronométrage lorsque la virtualisation TSC est désactivée. Dans le passé, cette fonctionnalité a parfois été recommandée pour améliorer les performances des applications qui lisent fréquemment le TSC., mais les performances du TSC virtuel ont été considérablement améliorées dans les produits actuels. Cette fonctionnalité a également été recommandée pour une utilisation lors de l'exécution de mesures nécessitant une source précise de temps réel dans la machine virtuelle.

Bref, il faut ajouter le paramètre

monitor_control.virtual_rdtsc = FAUX

Conclusion

Vous avez probablement une question : pourquoi SQL appelle-t-il GetTimePrecise si souvent ?

Je n'ai pas le code source du serveur SQL, mais la logique le dit. SQL est presque un système d'exploitation à concurrence coopérative, où chaque thread doit « céder » de temps en temps. Quel est le meilleur endroit pour faire cela ? Là où il y a une attente naturelle - verrouillage ou IO. D'accord, mais que se passe-t-il si nous faisons tourner des boucles informatiques ? Ensuite, la place évidente et presque unique est dans l'interprète (ce n'est pas vraiment un interprète), après avoir exécuté l'instruction suivante.

En règle générale, le serveur SQL n'est pas utilisé à des fins informatiques pures et ce n'est pas un problème. Mais les boucles qui fonctionnent avec toutes sortes de tables temporaires (qui sont immédiatement mises en cache) transforment le code en une séquence d'instructions exécutées très rapidement.

À propos, si vous enveloppez la fonction dans NATIVELY COMPILED, elle cesse de demander du temps et sa vitesse est multipliée par 10. Qu'en est-il du multitâche coopératif ? Mais pour le code compilé nativement, nous avons dû faire du MULTITÂCHE PRÉEMPTIVE en SQL.

Source: habr.com

Ajouter un commentaire