Ja, mein alter Laptop ist um ein Vielfaches leistungsstärker als Ihr Produktionsserver

Dies sind die Behauptungen, die ich von unseren Entwicklern gehört habe. Das Interessanteste ist, dass sich dies als wahr herausstellte und zu einer langwierigen Untersuchung führte. Es geht um SQL Server, die sich auf unserer VMware drehen.



Eigentlich ist es einfach sicherzustellen, dass sich der Produktionsserver hoffnungslos hinter dem Laptop befindet. Führen Sie den folgenden Code aus (nicht auf Tempdb und nicht auf einer Basis mit aktivierter verzögerter Haltbarkeit):

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

Auf meinem Desktop läuft es 5 Sekunden und auf dem Produktionsserver 28 Sekunden. Da SQL auf das physische Ende des Transaktionsprotokolleintrags warten muss und hier sehr kurze Transaktionen ausgeführt werden. Grob gesagt fuhren wir einen großen, leistungsstarken Lastwagen in den Stadtverkehr und beobachten, wie er von Pizzaboten auf Rollern bekanntermaßen überholt wird - der Durchsatz ist hier nicht wichtig, nur die Latenz ist wichtig. Und kein einziger Netzwerkspeicher, egal wie viele Nullen in seinem Preis enthalten sind, kann die Latenz gegenüber der lokalen SSD gewinnen.

(In den Kommentaren stellte sich heraus, dass ich gelogen habe - ich hatte die Haltbarkeit an beiden Stellen verzögert. Ohne verzögerte Haltbarkeit stellte sich heraus:
Desktop - 39 Sekunden, 15K tr / s, 0,065 ms / io Hin-
und Rückfahrt PROD - 360 Sekunden, 1600 tr / s, 0,6 ms
I. hätte zu früh aufpassen sollen)


In diesem Fall handelt es sich jedoch um die trivialen Nullen der Zeta-Riemann-Funktion anhand eines trivialen Beispiels. In dem Beispiel, das mir die Entwickler gebracht haben, gab es ein anderes. Ich war überzeugt, dass sie Recht hatten, und begann, alle Einzelheiten der Geschäftslogik aus dem Beispiel herauszuarbeiten. Irgendwann wurde mir klar, dass ich ihren Code komplett wegwerfen und meinen eigenen schreiben konnte - was das gleiche Problem zeigt - er läuft in der Produktion 3-4 mal langsamer:

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

Wenn bei Ihnen alles in Ordnung ist, wird die Überprüfung der Einfachheit der Nummer für 6-7-8 Sekunden durchgeführt. So war es auf einer Reihe von Servern. Bei einigen dauerte die Überprüfung jedoch 25 bis 40 Sekunden. Interessanterweise gab es keine Server, auf denen die Ausführung beispielsweise 14 Sekunden dauerte - der Code arbeitete entweder sehr schnell oder sehr langsam, dh das Problem war beispielsweise Schwarzweiß.

Was ich getan habe? Nützlich in VMware-Metriken. Dort war alles in Ordnung - es gab viele Ressourcen, Bereitschaftszeit = 0, alles war genug, während des Tests auf schnellen und langsamen Servern CPU = 100 auf einer vCPU. Ich habe einen Test gemacht, um die Anzahl der Pi zu berechnen - der Test zeigte auf allen Servern die gleichen Ergebnisse. Es roch immer mehr nach schwarzer Magie.

Nachdem ich auf der DEV-Farm ausgestiegen war, begann ich als Server zu spielen. Es stellte sich heraus, dass vMotion von Host zu Host den Server "heilen" kann, aber umgekehrt einen "schnellen" Server in einen "langsamen" verwandeln kann. Es scheint so - einige Hosts haben ein Problem ... aber ... nein. Einige virtuelle Maschinen wurden auf dem Host langsamer, z. B. A, aber auf Host B schnell. Und die andere virtuelle Maschine arbeitete auf A schnell und auf B langsamer! Die "schnellen" und "langsamen" Autos drehten sich oft auf dem Host!

Von diesem Moment an roch die Luft deutlich nach Schwefel. Schließlich konnte das Problem keiner virtuellen Maschine zugeordnet werden (z. B. Windows-Patches), da es sich mit vMotion zu einem „schnellen“ Problem entwickelte. Das Problem konnte aber auch nicht dem Host zugeschrieben werden, da er sowohl "schnelle" als auch "langsame" Maschinen haben konnte. Es war auch nicht mit der Last verbunden - ich habe es geschafft, eine "langsame" Maschine auf den Host zu bekommen, auf der es überhaupt nichts anderes gab.

Aus Verzweiflung startete ich Process Explorer von Sysinternals und sah mir den SQL-Stack an. Auf langsamen Maschinen fiel mir sofort die Zeile auf:

ntoskrnl.exe! KeSynchronizeExecution + 0x5bf6
ntoskrnl.exe! KeWaitForMultipleObjects + 0x109d
ntoskrnl.exe! KeWaitForMultipleObjects + 0xb3f
ntosKrn3!
ntoskrnl.exe! KeQuerySystemTimePrecise + 0x881 <- !!!
ntoskrnl.exe! ObDereferenceObjectDeferDelete + 0x28a
ntoskrnl.exe! KeSynchronizeExecution + 0x2de2
sqllang.dll! CDiagThreadSafe :: PxlvlReplace + 0x1a20
... übersprungenen
sqldk.dll! SystemThread :: MakeMiniSOSThread + 0xa54
KERNEL32.DLL! BaseThreadInitThunk + 0x14
ntdll.dll! RtlUserThreadStart + 0x21


Es war schon etwas. Das Programm wurde geschrieben:

    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);
                }
            }
        }
    }

Dieses Programm zeigte eine noch stärkere Verzögerung - auf „schnellen“ Maschinen werden 16 bis 18 Millionen Zyklen pro Sekunde angezeigt, auf langsamen eineinhalb Millionen oder sogar 700.000. Das heißt, der Unterschied beträgt 10-20 mal (!!!). Dies war bereits ein kleiner Sieg: Auf jeden Fall bestand keine Gefahr, zwischen Microsoft- und VMware-Support hängen zu bleiben, damit die Pfeile aufeinander übertragen werden.

Darüber hinaus stoppte der Fortschritt - Urlaub, wichtige Angelegenheiten, virale Hysterie und ein starker Anstieg der Belastung. Ich habe meinen Kollegen gegenüber oft ein magisches Problem erwähnt, aber manchmal schien es, dass sie mir nicht immer vertrauten - die Aussage, dass VMware den Code 10 bis 20 Mal verlangsamte, war zu monströs.

Ich versuchte herauszufinden, was langsamer wurde. Manchmal schien es mir, als hätte ich eine Lösung gefunden - das Ein- und Ausschalten von Hotplugs, das Ändern der Speichergröße oder der Anzahl der Prozessoren machte die Maschine oft zu einer "schnellen". Aber nicht für immer. Aber was sich als wahr herausstellte, war, dass es ausreicht, rauszugehen und auf das Rad zu klopfen - das heißt, jeden Parameter der virtuellen Maschine zu ändern .

Schließlich fanden meine amerikanischen Kollegen plötzlich die Grundursache.



Hosts unterschieden sich in der Häufigkeit!
  • Dies ist normalerweise nicht beängstigend. Aber: Wenn Sie von einem "nativen" Host zu einem Host mit einer "anderen" Frequenz wechseln, sollte VMware das Ergebnis von GetTimePrecise anpassen.
  • In der Regel ist dies nicht beängstigend, es sei denn, es gibt eine Anwendung, die die genaue Zeit millionenfach pro Sekunde anfordert, wie ein SQL Server.
  • Dies ist jedoch nicht beängstigend, da SQL Server dies nicht immer tut (siehe Schlussfolgerung).

Aber es gibt Fälle, in denen dieser Rechen schmerzhaft trifft. Und doch, ja, indem ich auf das Rad tippte (etwas in den VM-Einstellungen änderte), zwang ich VMware, die Konfiguration neu zu erzählen, und die Frequenz des aktuellen Hosts wurde zur "nativen" Frequenz des Computers.

Entscheidung


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.

Kurz gesagt, fügen Sie den Parameter

monitor_control.virtual_rdtsc = FALSE hinzu

Fazit


Sie haben wahrscheinlich eine Frage: Was für SQL ruft GetTimePrecise so oft auf?

Ich habe nicht die SQL Server-Quelle, aber die Logik sagt dies. SQL ist fast ein Betriebssystem mit kooperativer Parallelität, bei dem jeder Thread von Zeit zu Zeit nachgeben muss. Wo ist es besser, es zu tun? Wo es eine natürliche Erwartung gibt - Lock oder IO. Was ist, wenn wir die Rechenzyklen drehen? Dann ist der offensichtliche und fast einzige Platz im Interpreter (dies ist kein richtiger Interpreter) nach der Ausführung des nächsten Operators.

In der Regel wird SQL Server für nicht verwendet Nageln sauber Berechnungen und das ist kein Problem. Zyklen mit der Arbeit mit allen Arten von temporären Tablets (die sofort zwischengespeichert werden) verwandeln den Code jedoch in eine Folge sehr schnell ausgeführter Anweisungen.

Übrigens, wenn Sie die Funktion in NATIVELY COMPILED verpacken, hört sie auf, nach Zeit zu fragen, und ihre Geschwindigkeit erhöht sich um den Faktor 10. Aber was ist mit kooperativem Multitasking? Aber für nativ kompilierten Code musste ich PREEMPTIVE MULTITASKING in SQL durchführen.

All Articles