Ja, mein alter Laptop ist mehrere Male leistungsfÀhiger als Ihr Produktionsserver.

Genau solche Beschwerden habe ich von unseren Entwicklern gehört. Am interessantesten ist, dass sich das als wahr herausstellte und ein langwieriges Ermittlungsverfahren einleitete. Es geht um SQL-Server, die bei uns auf VMware laufen.

Ja, mein alter Laptop ist mehrere Male leistungsfÀhiger als Ihr Produktionsserver.

Es ist tatsĂ€chlich einfach, dafĂŒr zu sorgen, dass der Production-Server hoffnungslos hinter dem Laptop zurĂŒckbleibt. FĂŒhren Sie (nicht auf tempdb und nicht auf einer Datenbank mit aktivierter verzögerter Dauerhaftigkeit) den 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 Langsamster!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

Auf meinem Desktop dauert es 5 Sekunden, auf dem Production-Server jedoch 28 Sekunden. Weil SQL auf das physische Ende der Aufzeichnung im Transaktionsprotokoll warten muss, wĂ€hrend wir hier sehr kurze Transaktionen durchfĂŒhren. Grob gesagt haben wir einen großen, leistungsstarken Lkw in den Stadtverkehr gelenkt und beobachten, wie ihn Pizzalieferanten auf Scootern geschickt ĂŒberholen – hier spielt der Durchsatz keine Rolle, nur die Latenz. Und kein Netzwerk-Speicher, egal wie viele Nullen er in seinem Preis hat, kann in Bezug auf die Latenz mit einem lokalen SSD mithalten.

(In den Kommentaren stellte sich heraus, dass ich gelogen habe – sowohl in meinem Desktop als auch im Production-Umfeld war verzögerte Dauerhaftigkeit aktiv. Ohne verzögerte Dauerhaftigkeit ergibt sich folgendes:
Desktop – 39 Sekunden, 15K tr/sec, 0.065ms /io roundtrip
PROD – 360 Sekunden, 1600 tr/sec, 0.6ms
Ich hÀtte darauf achten sollen, dass es viel zu schnell war)

Wir haben es jedoch in diesem Fall mit trivialen Nullen dieser Riemannschen Funktion mit einem trivialen Beispiel zu tun. In dem Beispiel, das mir die Entwickler gebracht haben, war es anders. Ich stellte fest, dass sie Recht hatten, und begann, die ganze spezifische Logik, die mit der GeschĂ€ftslogik verbunden war, aus dem Beispiel zu entfernen. Irgendwann bemerkte ich, dass ich ihren Code vollstĂ€ndig wegwerfen und meinen eigenen schreiben konnte – der das gleiche Problem demonstriert – im Production-Umfeld 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 -- gerade Zahlen bis zur Wurzel ĂŒberprĂŒfen
  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 gut lĂ€uft, wird die PrimzahlprĂŒfung 6-7-8 Sekunden dauern. So war es an mehreren Server. Aber an einigen Orten dauerte die PrĂŒfung 25-40 Sekunden. Interessanterweise gab es keine Server, bei denen die AusfĂŒhrung, sagen wir, 14 Sekunden dauerte – der Code arbeitete entweder sehr schnell oder sehr langsam, das heißt, das Problem war, um es so zu sagen, schwarz-weiß.

Was habe ich gemacht? Ich habe in die VMware-Metriken geschaut. Dort war alles in Ordnung – es gab reichlich Ressourcen, Ready-Zeit = 0, alles war ausreichend, wĂ€hrend des Tests waren die CPU-Lasten auf einem vCPU bei 100 auf sowohl schnellen als auch langsamen Servern. Ich nahm einen Test zur Berechnung der Zahl Pi – der Test zeigte auf allen Servern die gleichen Ergebnisse. Es roch immer stĂ€rker nach schwarzer Magie.

Als ich auf die DEV-Farm kam, begann ich mit den Servern zu experimentieren. Es stellte sich heraus, dass vMotion von Host zu Host den Server "heilen" kann, aber auch den "schnellen" Server in einen "langsamen" verwandeln kann. Es scheint, das ist es – einige Hosts haben ein Problem
 aber
 nein. Eine bestimmte virtuelle Maschine war auf Host A langsam, arbeitete aber schnell auf Host B. Eine andere virtuelle Maschine dagegen arbeitete schnell auf A und war auf B langsam! Auf den Hosts waren oft sowohl "schnelle" als auch "langsame" Maschinen im Einsatz!

Von diesem Moment an roch es klar nach Schwefel in der Luft. Denn das Problem konnte weder der virtuellen Maschine (Windows-Patches zum Beispiel) zugeschrieben werden – sie wurde bei vMotion zu einer "schnellen". Aber das Problem konnte auch nicht dem Host zugeordnet werden – denn darauf konnten sowohl "schnelle" als auch "langsame" Maschinen sein. Es hatte auch nichts mit der Last zu tun – ich konnte eine "langsame" Maschine auf einem Host bekommen, wo außer ihr nichts war.

In meiner Verzweiflung startete ich den Process Explorer von Sysinternals und schaute mir den SQL-Stack an. Auf den langsamen Maschinen fiel mir sofort 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 schon etwas. Ein 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  0)
                {
                    Console.WriteLine("{0}", counter);
                }
            }
        }
    }

Dieses Programm zeigte eine noch drastischere Verlangsamung – auf den „schnellen“ Maschinen zeigte es 16-18 Millionen Zyklen pro Sekunde, wĂ€hrend es auf den langsamen eineinhalb Millionen oder sogar 700.000 anzeigte. Das heißt, der Unterschied betrĂ€gt 10-20 Mal (!!!). Das war bereits ein kleiner Sieg: jedenfalls gab es keine Gefahr, zwischen dem Microsoft- und dem VMware-Support festzusitzen, sodass sie sich gegenseitig die Schuld zuschoben.

Danach stellte der Fortschritt ein – Urlaub, wichtige Angelegenheiten, virale Hysterie und ein deutlicher Anstieg der Belastung. Ich erwĂ€hnte das magische Problem oft meinen Kollegen, aber manchmal schien es, als wĂŒrden sie mir nicht immer glauben – die Aussage, dass VMware den Code 10-20 Mal verlangsamt, war einfach zu absurd.

Ich versuchte selbst herauszufinden, was eigentlich bremst. Manchmal hatte ich das GefĂŒhl, dass ich die Lösung gefunden hatte – das Ein- und Ausschalten von Hot Plugs, das Ändern des Speichervolumens oder der Anzahl der Prozessoren verwandelte oft die Maschine in eine „schnelle“. Aber nicht fĂŒr immer. Was jedoch wahr war, ist, dass es genĂŒgt, hinauszugehen und auf das Rad zu klopfen – also zu Ă€ndern kann diese den Parameter der virtuellen Maschine

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

Ja, mein alter Laptop ist mehrere Male leistungsfÀhiger als Ihr Produktionsserver.

Die Hosts unterschieden sich in der Frequenz!

  • Im Allgemeinen ist das nicht schlimm. Aber: beim Umzug von einem 'heimischen' Host zu einem Host mit 'anderer' Frequenz muss VMware das Ergebnis von GetTimePrecise korrigieren.
  • Im Allgemeinen ist das nicht schlimm, es sei denn, es gibt eine Anwendung, die Millionen von Malen pro Sekunde die genaue Zeit abfragt, wie z.B. SQL-Server.
  • Aber das ist auch nicht schlimm, denn SQL-Server tut dies lĂ€ngst nicht immer (siehe Schlussfolgerung).

Es gibt jedoch FÀlle, in denen diese Stolpersteine schmerzhaft sind. Und ja, als ich auf das Rad klopfte (etwas in den VM-Einstellungen Ànderte), zwang ich VMware, die Konfiguration 'neu zu berechnen', und die Frequenz des aktuellen Hosts wurde zur 'heimischen' 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 von der virtuellen Maschine hat keine Auswirkungen. Der Umzug der virtuellen Maschine zu einem anderen Host, das Fortsetzen aus dem Suspendierten Zustand oder das ZurĂŒcksetzen auf einen Snapshot fĂŒhrt dazu, dass der TSC sprunghaft springt. Einige Gastbetriebssysteme können nicht booten oder zeigen 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 den aktuellen Produkten erheblich verbessert. Die Funktion wurde auch empfohlen, wenn Messungen durchgefĂŒhrt werden, die eine prĂ€zise Quelle fĂŒr die reale Zeit in der virtuellen Maschine erfordern.

Kurz gesagt, es muss ein Parameter hinzugefĂŒgt werden.

monitor_control.virtual_rdtsc = FALSE

Fazit

Sie haben sich sicherlich gefragt: Warum wird GetTimePrecise so oft in SQL aufgerufen?

Ich habe keinen Zugang zu den SQL-Server-Quellcodes, aber die Logik sagt Folgendes. SQL ist fast ein Betriebssystem mit kooperativer NebenlĂ€ufigkeit, in dem jeder Thread von Zeit zu Zeit „nachgeben“ muss. Wo wĂ€re das besser? Dort, wo es ein natĂŒrliches Warten gibt – Lock oder IO. Gut, und was, wenn wir Berechnungsschleifen haben? Dann ist der offensichtlichste und fast einzige Ort – im Interpreter (es ist nicht ganz ein Interpreter), nach der AusfĂŒhrung der nĂ€chsten Anweisung.

In der Regel wird SQL-Server nicht fĂŒr reine Berechnungsarbeiten verwendet und das ist kein Problem. Aber Schleifen mit der Arbeit an temporĂ€ren Tabellen (die sofort zwischengespeichert werden) verwandeln den Code in eine Abfolge von sehr schnell ausfĂŒhrbaren Anweisungen.

Übrigens, wenn man die Funktion in NATIVELY COMPILED einwickelt, hört sie auf, Zeit abzufragen, und ihre Geschwindigkeit erhöht sich um das Zehnfache. Und wie steht es mit kooperativem Multitasking? Nun, fĂŒr nativ kompilierte Codes musste in SQL PREEMPTIVE MULTITASKING implementiert werden.

Quelle: habr.com

60GB SSD 8Gb DDR4