Ja, mein alter Laptop ist um ein Vielfaches leistungsfähiger als Ihr Produktionsserver.

Das sind genau die Beschwerden, die ich von unseren Entwicklern gehört habe. Das Interessanteste ist, dass sich herausstellte, dass dies wahr war, was Anlass zu einer langen Untersuchung gab. Wir werden über SQL-Server sprechen, die auf VMware laufen.

Ja, mein alter Laptop ist um ein Vielfaches leistungsfähiger als Ihr Produktionsserver.

Tatsächlich lässt sich leicht sicherstellen, dass der Produktionsserver hoffnungslos hinter dem Laptop steht. Führen Sie den Code aus (nicht auf tempdb und nicht auf einer Datenbank 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 dauert es 5 Sekunden und auf dem Produktionsserver 28 Sekunden. Weil SQL auf das physische Ende des Transaktionsprotokolleintrags warten muss und wir hier sehr kurze Transaktionen durchführen. Grob gesagt fuhren wir mit einem großen, leistungsstarken LKW in den Stadtverkehr und sahen zu, wie er von Pizzalieferanten auf Rollern rasant überholt wurde – hier kommt es nicht auf den Durchsatz an, sondern nur auf die Latenz. Und kein Netzwerkspeicher, egal wie viele Nullen es im Preis gibt, kann die lokale SSD in puncto Latenz schlagen.

(In den Kommentaren stellte sich heraus, dass ich gelogen hatte – ich hatte an beiden Stellen eine verzögerte Haltbarkeit. Ohne verzögerte Haltbarkeit stellte sich heraus:
Desktop – 39 Sekunden, 15 tr/s, 0.065 ms/IO Roundtrip
PROD – 360 Sekunden, 1600 U/Sek., 0.6 ms
Ich hätte merken müssen, dass es zu schnell war)

In diesem Fall handelt es sich jedoch um triviale Nullstellen der Riemannschen Zetafunktion anhand eines trivialen Beispiels. In dem Beispiel, das mir die Entwickler gebracht haben, war das anders. Ich war davon überzeugt, dass sie Recht hatten, und begann, alle ihre Besonderheiten im Zusammenhang mit der Geschäftslogik aus dem Beispiel zu entfernen. Irgendwann wurde mir klar, dass ich ihren Code komplett wegwerfen und meinen eigenen schreiben könnte – was das gleiche Problem zeigt – in der Produktion läuft er drei- bis viermal 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 alles in Ordnung ist, dauert die Überprüfung der Primalität einer Zahl 6-7-8 Sekunden. Dies geschah auf mehreren 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 dauern würde – der Code arbeitete entweder sehr schnell oder sehr langsam, das heißt, das Problem war, sagen wir, schwarz auf weiß.

Was ich getan habe? Verwendete VMware-Metriken. Dort war alles in Ordnung - es gab eine Fülle von Ressourcen, Bereitschaftszeit = 0, es war genug von allem vorhanden, während des Tests sowohl auf schnellen als auch auf langsamen Servern CPU = 100 auf einer vCPU. Ich habe einen Test gemacht, um die Zahl Pi zu berechnen – der Test zeigte auf jedem Server die gleichen Ergebnisse. Der Geruch schwarzer Magie wurde immer stärker.

Als ich auf der DEV-Farm ankam, fing ich an, mit den Servern herumzuspielen. Es stellte sich heraus, dass vMotion von Host zu Host einen Server „heilen“ kann, aber auch einen „schnellen“ Server in einen „langsamen“ verwandeln kann. Es scheint, dass es das ist – einige Hosts haben ein Problem … aber … nein. Eine virtuelle Maschine war auf Host, sagen wir A, langsam, arbeitete aber auf Host B schnell. Und eine andere virtuelle Maschine arbeitete im Gegenteil schnell auf A und wurde auf B langsamer! Sowohl „schnelle“ als auch „langsame“ Maschinen drehten sich oft auf dem Host!

Von diesem Moment an lag ein deutlicher Schwefelgeruch in der Luft. Schließlich konnte das Problem nicht auf die virtuelle Maschine zurückgeführt werden (z. B. Windows-Patches) – schließlich wurde es mit vMotion „schnell“. Das Problem konnte aber auch nicht dem Host zugeschrieben werden – schließlich konnte dieser sowohl „schnelle“ als auch „langsame“ Maschinen haben. Dies hatte auch nichts mit der Auslastung zu tun – ich habe es geschafft, eine „langsame“ Maschine auf dem Host zu bekommen, auf der es sonst überhaupt nichts gab.

Aus Verzweiflung startete ich den Process Explorer von Sysinternals und schaute mir den SQL-Stack an. Bei langsamen Maschinen ist mir sofort die Zeile ins Auge gefallen:

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
... übersprungen
sqldk.dll!SystemThread::MakeMiniSOSThread+0xa54
KERNEL32.DLL!BaseThreadInitThunk+0x14
ntdll.dll!RtlUserThreadStart+0x21

Das 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 deutlichere Verlangsamung: Auf „schnellen“ Maschinen werden 16 bis 18 Millionen Zyklen pro Sekunde angezeigt, während auf langsamen Maschinen eineinhalb Millionen oder sogar 700 Zyklen pro Sekunde angezeigt werden. Das heißt, der Unterschied beträgt das 10-20-fache (!!!). Das war bereits ein kleiner Sieg: Auf jeden Fall bestand keine Gefahr, zwischen dem Microsoft- und dem VMware-Support stecken zu bleiben und sich gegenseitig in die Quere zu kommen.

Dann stoppte der Fortschritt – Urlaub, wichtige Angelegenheiten, virale Hysterie und ein starker Anstieg der Arbeitsbelastung. Ich habe das magische Problem meinen Kollegen gegenüber oft erwähnt, aber manchmal schien es, als hätten sie mir nicht einmal immer geglaubt – die Aussage, dass VMware den Code um das 10- bis 20-fache verlangsamt, war zu ungeheuerlich.

Ich habe versucht, selbst herauszufinden, was mich bremste. Manchmal schien es mir, als hätte ich eine Lösung gefunden: Durch das Ein- und Ausschalten von Hot-Plugs, das Ändern der Speichergröße oder der Anzahl der Prozessoren wurde die Maschine oft zu einer „schnellen“. Aber nicht für immer. Was sich jedoch als wahr herausstellte, ist, dass es ausreicht, rauszugehen und ans Lenkrad zu klopfen – also etwas zu verändern keine Parameter der virtuellen Maschine

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

Ja, mein alter Laptop ist um ein Vielfaches leistungsfähiger als Ihr Produktionsserver.

Die Gastgeber unterschieden sich in der Häufigkeit!

  • In der Regel ist das keine große Sache. Aber: Beim Wechsel von einem „nativen“ Host zu einem Host mit einer „anderen“ Frequenz muss VMware das GetTimePrecise-Ergebnis anpassen.
  • In der Regel stellt dies kein Problem dar, es sei denn, es gibt eine Anwendung, die millionenfach pro Sekunde die genaue Uhrzeit abfragt, wie etwa ein SQL-Server.
  • Dies ist jedoch nicht beängstigend, da SQL Server dies nicht immer tut (siehe Fazit).

Aber es gibt Fälle, in denen dieser Rechen hart zuschlägt. Und doch, ja, indem ich auf das Rad tippte (indem ich etwas in den VM-Einstellungen änderte), zwang ich VMware, die Konfiguration „neu zu berechnen“, und die Frequenz des aktuellen Hosts wurde zur „nativen“ Frequenz der Maschine.

Lösung

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

Wenn Sie die Virtualisierung des TSC deaktivieren, gibt das Lesen des TSC aus der virtuellen Maschine den TSC-Wert der physischen Maschine zurück, und das Schreiben des TSC aus der virtuellen Maschine hat keine Auswirkung. Das Migrieren der virtuellen Maschine auf einen anderen Host, das Fortsetzen aus dem angehaltenen Zustand oder das Zurücksetzen auf einen Snapshot führt dazu, dass der TSC diskontinuierlich springt. Einige Gastbetriebssysteme starten nicht oder weisen andere Zeitprobleme auf, wenn die TSC-Virtualisierung deaktiviert ist. In der Vergangenheit wurde diese Funktion manchmal empfohlen, um die Leistung von Anwendungen zu verbessern, die den TSC häufig lesen, aber die Leistung des virtuellen TSC wurde in aktuellen Produkten erheblich verbessert. Die Funktion wird auch für Messungen empfohlen, die eine präzise Echtzeitquelle in der virtuellen Maschine erfordern.

Kurz gesagt, Sie müssen den Parameter hinzufügen

monitor_control.virtual_rdtsc = FALSE

Abschluss

Sie haben wahrscheinlich eine Frage: Warum ruft SQL GetTimePrecise so oft auf?

Ich habe den SQL-Server-Quellcode nicht, aber die Logik besagt Folgendes. SQL ist quasi ein Betriebssystem mit kooperativer Parallelität, bei dem jeder Thread von Zeit zu Zeit „nachgeben“ muss. Wo kann man das am besten machen? Wo es eine natürliche Wartezeit gibt – Sperre oder IO. Okay, aber was ist, wenn wir Rechenschleifen drehen? Dann liegt der offensichtliche und fast einzige Platz im Interpreter (dies ist nicht wirklich ein Interpreter), nachdem die nächste Anweisung ausgeführt wurde.

Im Allgemeinen wird SQL Server nicht für reine Computerzwecke verwendet, und dies stellt kein Problem dar. Aber Schleifen, die mit allen möglichen temporären Tabellen arbeiten (die sofort zwischengespeichert werden), verwandeln den Code in eine Folge sehr schnell ausgeführter Anweisungen.

Übrigens, wenn Sie die Funktion in NATIVELY COMPILED einschließen, fragt sie nicht mehr nach Zeit und ihre Geschwindigkeit erhöht sich um das Zehnfache. Wie wäre es mit kooperativem Multitasking? Aber für nativ kompilierten Code mussten wir PREEMPTIVE MULTITASKING in SQL durchführen.

Source: habr.com

Kommentar hinzufügen