Ja, min gamla bärbara dator är flera gånger kraftfullare än din produktionsserver.

Det är exakt de klagomål jag hörde från våra utvecklare. Det mest intressanta är att detta visade sig vara sant, vilket gav upphov till en lång utredning. Vi kommer att prata om SQL-servrar som körs på VMware.

Ja, min gamla bärbara dator är flera gånger kraftfullare än din produktionsserver.

Egentligen är det lätt att se till att produktionsservern är hopplöst bakom den bärbara datorn. Kör (inte på tempdb och inte på en databas med Delayed Durability aktiverat) koden:

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

På mitt skrivbord tar det 5 sekunder, och på produktionsservern tar det 28 sekunder. Eftersom SQL måste vänta på det fysiska slutet av transaktionsloggposten, och vi gör mycket korta transaktioner här. Grovt sett körde vi en stor, kraftfull lastbil in i stadstrafiken och såg hur den blev omkörd av pizzabud på skotrar – genomströmningen är inte viktig här, bara latens är viktig. Och ingen nätverkslagring, oavsett hur många nollor det finns i priset, kan slå den lokala SSD:n när det gäller latens.

(i kommentarerna visade det sig att jag ljög - jag hade försenad hållbarhet på båda ställena. Utan fördröjd hållbarhet visar det sig:
Desktop - 39 sekunder, 15K tr/sek, 0.065ms /io tur och retur
PROD - 360 sekunder, 1600 tr/sek, 0.6ms
Jag borde ha märkt att det gick för snabbt)

Men i det här fallet har vi att göra med triviala nollor i Riemanns zeta-funktion med ett trivialt exempel. I exemplet som utvecklarna tog med mig var det annorlunda. Jag var övertygad om att de hade rätt och började ta bort från exemplet alla deras detaljer relaterade till affärslogik. Vid något tillfälle insåg jag att jag helt kunde slänga deras kod och skriva min egen - vilket visar samma problem - i produktionen går den 3-4 gånger långsammare:

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

Om allt är bra tar det 6-7-8 sekunder att kontrollera ett nummers primat. Detta hände på ett antal servrar. Men på vissa tog kontrollen 25-40 sekunder. Intressant nog fanns det inga servrar där exekveringen skulle ta, låt oss säga, 14 sekunder - koden fungerade antingen väldigt snabbt eller väldigt långsamt, det vill säga problemet var, låt oss säga, svart och vitt.

Vad jag har gjort? Använde VMware-mått. Allt var bra där - det fanns ett överflöd av resurser, Klartid = 0, det fanns tillräckligt med allt, under testet på både snabba och långsamma servrar CPU = 100 på en vCPU. Jag tog ett test för att beräkna talet Pi - testet visade samma resultat på vilken server som helst. Lukten av svart magi blev starkare och starkare.

När jag väl kom till DEV-farmen började jag spela med servrarna. Det visade sig att vMotion från värd till värd kan "bota" en server, men den kan också förvandla en "snabb" server till en "långsam". Det verkar som att det här är det - vissa värdar har ett problem... men... nej. Någon virtuell maskin var långsam på värd, säg A, men fungerade snabbt på värd B. Och en annan virtuell maskin, tvärtom, fungerade snabbt på A och saktade ner på B! Både "snabba" och "långsamma" maskiner snurrade ofta på värden!

Från det ögonblicket var det en tydlig lukt av svavel i luften. Trots allt kunde problemet inte tillskrivas den virtuella maskinen (till exempel Windows-patchar) - trots allt blev det "snabbt" med vMotion. Men problemet kunde inte heller tillskrivas värden - trots allt kan den ha både "snabba" och "långsamma" maskiner. Detta var inte heller relaterat till belastningen - jag lyckades få en "långsam" maskin på värden, där det inte fanns något alls förutom den.

Av desperation startade jag Process Explorer från Sysinternals och tittade på SQL-stacken. På långsamma maskiner fångade linjen omedelbart mitt öga:

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

Det här var redan något. Programmet skrevs:

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

Detta program visade en ännu mer uttalad avmattning - på "snabba" maskiner visar det 16-18 miljoner cykler per sekund, medan det på långsamma maskiner visar en och en halv miljon, eller till och med 700 tusen. Det vill säga skillnaden är 10-20 gånger (!!!). Detta var redan en liten seger: det fanns i alla fall inget hot om att fastna mellan Microsoft och VMware-support så att de skulle vända pilar mot varandra.

Sedan stannade framstegen - semester, viktiga frågor, viral hysteri och en kraftig ökning av arbetsbelastningen. Jag nämnde ofta det magiska problemet för mina kollegor, men ibland verkade det som att de inte ens alltid trodde mig - påståendet att VMware saktar ner koden med 10-20 gånger var för monstruöst.

Jag försökte själv gräva fram det som bromsade mig. Ibland verkade det för mig att jag hade hittat en lösning - att slå på och av Hot Plugs, ändra mängden minne eller antalet processorer gjorde ofta maskinen till en "snabb" sådan. Men inte för alltid. Men det som visade sig stämma är att det räcker med att gå ut och knacka på ratten – alltså byta någon virtuell maskin parameter

Äntligen hittade mina amerikanska kollegor plötsligt grundorsaken.

Ja, min gamla bärbara dator är flera gånger kraftfullare än din produktionsserver.

Värdarna skilde sig åt i frekvens!

  • Som regel är detta ingen stor sak. Men: när man flyttar från en "inbyggd" värd till en värd med en "annan" frekvens måste VMware justera GetTimePrecise-resultatet.
  • Som regel är detta inte ett problem, såvida det inte finns en applikation som begär den exakta tiden miljontals gånger per sekund, som SQL-server.
  • Men detta är inte skrämmande, eftersom SQL-server inte alltid gör detta (se slutsats)

Men det finns fall då denna rake slår hårt. Och ändå, ja, genom att knacka på hjulet (genom att ändra något i VM-inställningarna) tvingade jag VMware att "beräkna om" konfigurationen, och frekvensen för den nuvarande värden blev maskinens "native" frekvens.

beslutet

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

När du inaktiverar virtualisering av TSC:n, returnerar läsning av TSC från den virtuella maskinen den fysiska maskinens TSC-värde, och att skriva TSC från den virtuella maskinen har ingen effekt. Att migrera den virtuella maskinen till en annan värd, återuppta den från vilande tillstånd eller återgå till en ögonblicksbild gör att TSC hoppar diskontinuerligt. Vissa gästoperativsystem startar inte, eller uppvisar andra tidtagningsproblem, när TSC-virtualisering är inaktiverad. Tidigare har den här funktionen ibland rekommenderats för att förbättra prestandan för applikationer som läser TSC ofta, men prestandan för den virtuella TSC har förbättrats avsevärt i nuvarande produkter. Funktionen har också rekommenderats för användning när man utför mätningar som kräver en exakt källa för realtid i den virtuella maskinen.

Kort sagt, du måste lägga till parametern

monitor_control.virtual_rdtsc = FALSK

Slutsats

Du har förmodligen en fråga: varför anropar SQL GetTimePrecise så ofta?

Jag har inte SQL-serverns källkod, men logiken säger detta. SQL är nästan ett operativsystem med kooperativ samtidighet, där varje tråd måste "ge sig" då och då. Var är det bästa stället att göra detta? Där det finns en naturlig väntan - lås eller IO. Okej, men vad händer om vi snurrar beräkningsslingor? Sedan är den uppenbara och nästan enda platsen i tolken (det här är egentligen inte en tolk), efter att ha utfört nästa påstående.

Generellt sett används inte SQL-server för ren beräkningsspikning och detta är inget problem. Men loopar som fungerar med alla möjliga temporära tabeller (som omedelbart cachelagras) förvandlar koden till en sekvens av mycket snabbt exekverade satser.

Förresten, om du slår in funktionen i NATIVELY COMPILED, så slutar den att fråga efter tid, och dess hastighet ökar med 10 gånger. Vad sägs om cooperativ multitasking? Men för inbyggt kompilerad kod var vi tvungna att göra PREEMPTIVE MULTITASKING i SQL.

Källa: will.com

Lägg en kommentar