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

Ce sont les affirmations que j'ai entendues de nos développeurs. Plus intéressant encore, cela s'est avéré vrai, donnant lieu à une longue enquête. Il s'agira de serveurs SQL, qui tournent sur notre VMware.



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 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, il fonctionne pendant 5 secondes et sur le serveur de production - 28 secondes. Parce que SQL doit attendre la fin physique de l'entrée du journal des transactions, et ici nous faisons des transactions très courtes. En gros, nous avons conduit un gros camion puissant dans le trafic urbain, et nous voyons comment il est dépassé par les livreurs de pizza sur des scooters - le débit n'est pas important ici, seule la latence est importante. Et pas un seul stockage réseau, quel que soit le nombre de zéros dans son prix, ne pourra gagner de la latence par rapport au SSD local.

(dans les commentaires, il s'est avéré que j'ai menti - j'avais une durabilité retardée dans les deux endroits. Sans durabilité retardée, il s'avère:
Bureau - 39 secondes, 15K tr / sec, 0.065ms / io aller-retour
PROD - 360 secondes, 1600 tr / sec, 0.6ms
I aurait dû prêter attention trop tôt)


Cependant, dans ce cas, nous traitons les zéros triviaux de la fonction zeta Riemann avec un exemple trivial. Dans l'exemple que les développeurs m'ont apporté, il y en avait un autre. Je suis devenu convaincu qu'ils avaient raison et j'ai commencé à effacer 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 - il fonctionne 3 à 4 fois plus lentement en production:

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 pour vous, la vérification de la simplicité du numéro sera effectuée pendant 6-7-8 secondes. C'était donc 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 où l'exécution prendrait, disons, 14 secondes - le code fonctionnait très rapidement ou très lentement, c'est-à-dire que le problème était, disons, noir et blanc.

Ce que j'ai fait? Utile dans les métriques VMware. Tout allait bien là-bas - il y avait beaucoup de ressources, Ready time = 0, tout était suffisant, pendant le test sur les serveurs rapides et lents CPU = 100 sur un vCPU. J'ai fait un test pour calculer le nombre de Pi - le test a montré les mêmes résultats sur tous les serveurs. Ça sentait de plus en plus la magie noire.

Après être sorti sur la ferme DEV, j'ai commencé à jouer en tant que serveurs. Il s'est avéré que vMotion d'hôte à hôte peut "guérir" le serveur, mais peut et vice versa, transformer un serveur "rapide" en un serveur "lent". Il semble que cela - certains hôtes ont un problème ... mais ... non. Certaines machines virtuelles ont ralenti sur l'hôte, par exemple A, mais ont fonctionné rapidement sur l'hôte B. Et l'autre machine virtuelle, au contraire, a fonctionné rapidement sur A et a ralenti sur B! Les voitures «rapides» et «lentes» tournaient souvent sur l'hôte!

À partir de ce moment, l'air sentait distinctement le soufre. Après tout, le problème ne pouvait être attribué à aucune machine virtuelle (correctifs Windows, par exemple) - car il s'est transformé en un «rapide» avec vMotion. Mais le problème ne pouvait pas non plus être attribué à l'hôte - car il pouvait avoir à la fois des machines «rapides» et «lentes». Il n'était pas non plus lié à la charge - j'ai réussi à obtenir une machine «lente» sur l'hôte, où il n'y avait rien de plus.

Par désespoir, j'ai lancé Process Explorer depuis Sysinternals et j'ai regardé 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
ntosKrnx3 0!
ntoskrnl.exe! KeQuerySystemTimePrecise + 0x881 <- !!!
ntoskrnl.exe! ObDereferenceObjectDeferDelete + 0x28a
ntoskrnl.exe! KeSynchronizeExecution + 0x2de2
sqllang.dll! CDiagThreadSafe :: PxlvlReplace + 0x1a20
... sautée
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 montré une décélération encore plus vive - sur les machines «rapides», il affiche 16 à 18 millions de cycles par seconde, tandis que sur les machines lentes, il en est un million et demi, voire 700 000. Autrement dit, la différence est de 10 à 20 fois (!!!). C'était déjà une petite victoire: en tout cas, il n'y avait aucune menace de se coincer entre le support Microsoft et VMware pour qu'ils se transfèrent des flèches.

De plus, les progrès ont cessé - vacances, questions importantes, hystérie virale et forte augmentation de la charge. J'ai souvent mentionné un problème magique à mes collègues, mais il semblait parfois qu'ils ne me faisaient pas toujours confiance - la déclaration selon laquelle VMware ralentissait le code 10 à 20 fois était trop monstrueuse.

J'ai essayé de me dénicher ce qui ralentissait. Parfois, il me semblait que je trouvais une solution - allumer et éteindre les hot-plugs, changer la taille de la mémoire ou le nombre de processeurs transformait 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 à la roue, c'est-à-dire de modifier n'importe quel paramètre de la machine virtuelle.

Enfin, mes collègues américains ont soudainement trouvé la cause profonde.



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

Mais il y a des cas où ce râteau frappe douloureusement. Et pourtant, oui, en appuyant sur la roue (en changeant quelque chose dans les paramètres de la machine virtuelle), j'ai forcé VMware à `` recompter '' 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

When you disable virtualization of the TSC, reading the TSC from within the virtual machine returns the physical machine’s TSC value, and writing the TSC from within the virtual machine has no effect. Migrating the virtual machine to another host, resuming it from suspended state, or reverting to a snapshot causes the TSC to jump discontinuously. Some guest operating systems fail to boot, or exhibit other timekeeping problems, when TSC virtualization is disabled. In the past, this feature has sometimes been recommended to improve performance of applications that read the TSC frequently, but performance of the virtual TSC has been improved substantially in current products. The feature has also been recommended for use when performing measurements that require a precise source of real time in the virtual machine.

En bref, ajoutez le paramètre

monitor_control.virtual_rdtsc = FALSE

Conclusion


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

Je n'ai pas la source du serveur SQL, mais la logique le dit. SQL est presque un OS à concurrence simultanée, où chaque thread doit céder le pas de temps en temps. Où est-il préférable de le faire? Là où il y a une attente naturelle - verrouillage ou E / S. Et si on faisait tourner les cycles de calcul? Ensuite, l'évidence et presque le seul endroit est dans l'interpréteur (ce n'est pas tout à fait un interprète), après l'exécution de l'opérateur suivant.

En règle générale, SQL Server n'est pas utilisé pour clouer des calculs propres et ce n'est pas un problème. Mais des cycles de travail avec toutes sortes de tablettes temporaires (qui sont immédiatement mises en cache) transforment le code en une séquence d'instructions exécutées très rapidement.

Soit dit en passant, si vous encapsulez la fonction dans NATIVELY COMPILED, elle cesse de demander du temps et sa vitesse augmente d'un facteur 10. Mais qu'en est-il du multitâche coopératif? Mais pour le code compilé en mode natif, j'ai dû faire du MULTI-TÂCHE PRÉEMPTIF en SQL.

All Articles