Ja, mein alter Laptop ist mehrere Male leistungsstärker als Ihr Produktionsserver.

Solche Beschwerden habe ich tatsächlich von unseren Entwicklern gehört. Das Interessante ist, dass sich dies als wahr herausstellte und ein langanhaltendes Verfahren einleitete. Es geht um SQL-Server, die bei uns auf VMware laufen.

Ja, mein alter Laptop ist mehrere Male leistungsstärker als Ihr Produktionsserver.

Es ist wirklich einfach, einen Produktionsserver erheblich langsamer zu machen als ein Laptop. Führen Sie (nicht auf tempdb und nicht auf einer Datenbank mit aktivierter verzögerter Dauerhaftigkeit) den folgenden Code aus:

set nocount on
create table _t (v varchar(100))
declare @n int=300000
while @n>0 begin 
  insert into _t select 'Was für ein Langweiler!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

Auf meinem Desktop dauert die Ausführung 5 Sekunden, auf dem Produktionsserver jedoch 28 Sekunden. Das liegt daran, dass SQL auf das physische Ende der Aufzeichnung im Transaktionsprotokoll warten muss, während wir hier sehr kurze Transaktionen durchführen. Einfach ausgedrückt, wir haben einen großen, leistungsstarken Lastwagen in den Stadtverkehr gebracht und sehen, wie ihn Pizza-Lieferdienste auf Scootern mühelos überholen – hier zählt nicht der Durchsatz, sondern die Latenz. Und kein Netzwerk-Speicher, egal wie viele Nullen er kostet, kann in Bezug auf die Latenz mit einem lokalen SSD mithalten.

(In den Kommentaren stellte sich heraus, dass ich gelogen habe – an beiden Orten hatte ich verzögerte Dauerhaftigkeit aktiviert. Ohne verzögerte Dauerhaftigkeit ergibt sich:
Desktop – 39 Sekunden, 15K transaktionen/Sekunde, 0,065 ms pro IO-Roundtrip
PRODUKTION – 360 Sekunden, 1600 transaktionen/Sekunde, 0,6 ms
Ich hätte darauf achten sollen, dass es zu schnell ging)

In diesem Fall haben wir es jedoch mit den trivialen Nullen dieser Riemannschen Funktion zu tun, die ein triviales Beispiel darstellen. Bei dem Beispiel, das mir die Entwickler gebracht haben, war es anders. Ich habe überprüft, dass sie Recht hatten, und begann, aus dem Beispiel jegliche spezifische Geschäftslogik zu entfernen. Irgendwann stellte ich fest, dass ich ihren Code komplett wegwerfen und meinen eigenen schreiben kann — der das gleiche Problem demonstriert — in der Produktion läuft er 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 gut läuft, sollte die Primzahlprüfung 6-7-8 Sekunden in Anspruch nehmen. So war es bei mehreren Server. Aber bei einigen dauerte es 25-40 Sekunden. Interessanterweise gab es keine Server, bei denen die Ausführung beispielsweise 14 Sekunden dauerte — der Code lief entweder sehr schnell oder sehr langsam, das Problem war also, sagen wir mal, schwarz oder weiß.

Was habe ich gemacht? Ich habe in die VMware-Metriken geschaut. Dort lief alles gut – es gab genügend Ressourcen, Ready Time = 0, alles war ausreichend vorhanden. Während des Tests lag die CPU-Auslastung auf einem vCPU sowohl auf den schnellen als auch auf den langsamen Servern bei 100%. Ich habe einen Test zur Berechnung der Zahl Pi durchgeführt – der Test zeigte auf allen Servern identische Ergebnisse. Es roch zunehmend nach schwarzer Magie.

Als ich auf die DEV-Farm kam, begann ich mit den Servern zu experimentieren. Dabei stellte sich heraus, dass vMotion von Host zu Host einen Server "heilen" kann, aber er kann auch umgekehrt einen "schnellen" Server in einen "langsamen" verwandeln. Es schien, als ob bestimmte Hosts Probleme hätten… aber… nein. Eine bestimmte virtuelle Maschine hatte auf Host A Verzögerungen, lief aber schnell auf Host B. Eine andere virtuelle Maschine hingegen lief schnell auf A und war auf B langsam! Auf dem Host liefen oft sowohl "schnelle" als auch "langsame" Maschinen!

Ab diesem Moment war der Geruch von Rauch deutlich in der Luft. Das Problem konnte schließlich nicht der virtuellen Maschine (z. B. Windows-Patches) zugeschrieben werden — denn sie wurde während vMotion zu einer "schnellen". Aber das Problem konnte auch nicht dem Host zugeschrieben werden — denn es gab sowohl "schnelle" als auch "langsame" Maschinen darauf. Außerdem hing es nicht mit der Last zusammen — ich konnte eine "langsame" Maschine auf einem Host bekommen, auf dem sich sonst niemand befand.

In meiner Verzweiflung startete ich Process Explorer von Sysinternals und schaute mir den SQL-Stack an. Bei den langsamen Maschinen fiel mir sofort die folgende Zeile auf:

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 bereits etwas. Es wurde ein Programm 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  0)
                {
                    Console.WriteLine("{0}", counter);
                }
            }
        }
    }

Dieses Programm zeigte eine noch deutlichere Verlangsamung – auf 'schnellen' Maschinen erreichte es 16-18 Millionen Zyklen pro Sekunde, während es auf langsamen Maschinen nur eineinhalb Millionen oder sogar 700.000 schaffte. Das bedeutet, es gibt einen Unterschied von 10-20 Mal (!!!). Das war bereits ein kleiner Sieg: zumindest gab es keine Gefahr, zwischen dem Microsoft- und dem VMware-Support festzustecken, sodass sie die Schuld gegenseitig auf sich schoben.

Dann stoppte der Fortschritt — Urlaub, wichtige Angelegenheiten, virale Hysterie und ein rapider Anstieg der Last. Oft sprach ich das magische Problem bei meinen Kollegen an, doch manchmal schien es, als würden sie mir nicht immer glauben — die Behauptung, dass VMware den Code 10-20 Mal verlangsamt, war einfach zu erschreckend.

Ich versuchte selbst herauszufinden, was genau die Verzögerungen verursacht. Gelegentlich hatte ich das Gefühl, die Lösung gefunden zu haben — das Ein- und Ausschalten von Hot Plugs, das Ändern des Speichervolumens oder der Anzahl der Prozessoren verwandelte die Maschine oft in eine "schnelle". Aber nicht dauerhaft. Was sich jedoch als wahr herausstellte, war, dass es ausreicht, einmal herauszugehen und auf das Rad zu klopfen — sprich jede die Parameter der virtuellen Maschine

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

Ja, mein alter Laptop ist mehrere Male leistungsstärker als Ihr Produktionsserver.

Die Hosts unterschieden sich in der Frequenz!

  • Im Allgemeinen ist das nicht schlimm. Aber: Bei der Migration von einem 'heimischen' Host zu einem Host mit 'anderer' Frequenz muss VMware das Ergebnis von GetTimePrecise anpassen.
  • Im Allgemeinen ist das nicht schlimm, es sei denn, es gibt eine Anwendung, die Millionen von Malen pro Sekunde die genaue Zeit anfordert, wie der SQL-Server.
  • Aber selbst das ist nicht bedrohlich, da der SQL-Server dies längst nicht immer tut (siehe Fazit).

Aber es gibt Fälle, in denen diese Stolpersteine schmerzhaft werden. Ja, indem ich am Rad klopfte (etwas in den VM-Einstellungen änderte), zwang ich VMware dazu, die Konfiguration "neu zu berechnen", und die Frequenz des aktuellen Hosts wurde zur "ursprünglichen" Frequenz der Maschine.

Lösung

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

Wenn Sie die Virtualisierung der 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 Auswirkungen. Das Migrieren der virtuellen Maschine zu einem anderen Host, das Fortsetzen aus dem Ruhezustand oder das Zurücksetzen auf einen Snapshot führt dazu, dass der TSC sprunghaft springt. Einige Gastbetriebssysteme booten nicht oder zeigen andere Probleme bei der Zeitmessung, wenn die TSC-Virtualisierung deaktiviert ist. Früher wurde dieses Feature manchmal empfohlen, um die Leistung von Anwendungen zu verbessern, die den TSC häufig lesen., aber die Leistung des virtuellen TSC wurde in den aktuellen Produkten erheblich verbessert. Das Feature wurde auch empfohlen, wenn präzise Messungen erforderlich sind, die eine genaue Quelle der Echtzeit in der virtuellen Maschine erfordern.

Kurz gesagt, es muss der Parameter hinzugefügt werden

monitor_control.virtual_rdtsc = FALSE

Fazit

Sie fragen sich sicher: Warum sollte SQL GetTimePrecise so häufig aufrufen?

Ich habe keine SQL-Server-Quellcodes, aber die Logik besagt Folgendes: SQL ist fast ein Betriebssystem mit kooperativer Nebenläufigkeit, bei dem jeder Thread gelegentlich "nachgeben" muss. Und wo wäre das am besten möglich? Dort, wo es natürliche Wartezeiten gibt – bei Locks oder IO. Aber was passiert, wenn wir Rechenzyklen ausführen? Dann bleibt als offensichtlicher und fast einziger Ort der Interpreter (das ist nicht ganz ein Interpreter), nachdem der nächste Befehl ausgeführt wurde.

In der Regel wird SQL-Server nicht für die Bearbeitung reiner Berechnungen verwendet, und das ist kein Problem. Aber Schleifen, die mit verschiedenen temporären Tabellen arbeiten (die sofort zwischengespeichert werden), verwandeln den Code in eine Abfolge von sehr schnell ausgeführten Befehlen.

Übrigens, wenn man eine Funktion in NATIVELY COMPILED einwickelt, hört sie auf, Zeit zu verlangen, und ihre Geschwindigkeit vervielfacht sich um das Zehnfache. Und wie steht es mit kooperativem Multitasking? Für nativ kompilierte Codes musste SQL PREEMPTIVE MULTITASKING implementieren.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster