Ja, min gamla laptop Àr flera gÄnger kraftfullare Àn din produktionsserver

Det var just sÄdana klagomÄl jag hörde frÄn vÄra utvecklare. Det intressanta var att det visade sig vara sant, vilket inledde en lÄngvarig utredning. Det handlar om SQL-servrar som körs pÄ vÄr VMware.

Ja, min gamla laptop Àr flera gÄnger kraftfullare Àn din produktionsserver

För att faktiskt fÄ produktionsservern att vara hopplöst efter bÀrbara datorer Àr enkelt. Exekvera (inte pÄ tempdb och inte pÄ databasen med aktiverad Delayed Durability) koden:

set nocount on
create table _t (v varchar(100))
declare @n int=300000
while @n>0 begin 
  insert into _t select 'Vilken lÄngsam typ!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

PĂ„ min stationĂ€ra dator körs den pĂ„ 5 sekunder, men pĂ„ produktionsservern — 28 sekunder. För SQL mĂ„ste vĂ€nta pĂ„ den fysiska avslutningen av skrivningen i transaktionsloggen, och hĂ€r gör vi mycket korta transaktioner. För att sĂ€tta det pĂ„ ett grovt sĂ€tt, vi har kört en stor kraftfull lastbil i stadstrafiken, och ser hur pizzaleveransarna pĂ„ skotrar snabbt kör om den — hĂ€r spelar inte genomströmning nĂ„gon roll, bara latens. Ingen nĂ€tverkslagring, oavsett hur mĂ„nga nollor den har i priset, kan vinna pĂ„ latens mot en lokal SSD.

(i kommentarerna visade det sig att jag ljög — jag hade delayed durability pĂ„ bĂ„da stĂ€llena. Utan delayed durability blir det:
Desktop — 39 sekunder, 15K tr/sec, 0.065ms /io roundtrip
PROD — 360 sekunder, 1600 tr/sec, 0.6ms
Jag borde ha uppmÀrksammat att det var alltför snabbt)

Men i det hĂ€r fallet har vi att göra med triviala nollor av zeta-funktionen av Riemann med ett trivialt exempel. I det exempel jag fick frĂ„n utvecklarna fanns det nĂ„got annat. Jag försĂ€krade mig om att de hade rĂ€tt och började rensa bort all deras specifika affĂ€rslogik. Vid ett tillfĂ€lle insĂ„g jag att jag helt kunde kasta bort deras kod och skriva min egen — som demonstrerar samma problem — pĂ„ produktion körs 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 gĂ„r bra, kommer kontrollen av primtalet att ta 6-7-8 sekunder. SĂ„ var det i flera fall. servrarMen pĂ„ vissa tog det 25-40 sekunder. Det intressanta var att det inte fanns servrar dĂ€r körningen tog, sĂ€g, 14 sekunder — koden fungerade antingen mycket snabbt eller helt lĂ„ngsamt, sĂ„ att sĂ€ga, problemet var svart eller vitt.

Vad gjorde jag? Jag kollade VMware-metrik. Allt sĂ„g bra ut — resurserna var i överflöd, Ready time = 0, allt rĂ€ckte, under testet hade bĂ„de snabba och lĂ„ngsamma servrar CPU=100 pĂ„ en vCPU. Jag tog ett test för berĂ€kning av Pi — testet visade samma resultat pĂ„ alla servrar. Det luktade mer och mer av svart magi.

NÀr jag kom ut pÄ DEV-farmen började jag leka med servrarna. Det visade sig att vMotion mellan vÀrdar kan "lÀka" en server, men kan ocksÄ tvÀrtom, förvandla en "snabb" server till en "lÄngsam". Det verkar som om vissa vÀrdar har problem... men... nej. NÄgon slags virtuell maskin bromsade ner pÄ vÀrden, lÄt oss sÀga, A men fungerade snabbt pÄ vÀrd B. Och en annan virtuell maskin Ä sin sida, fungerade snabbt pÄ A och bromsade ner pÄ B! PÄ vÀrden körde ofta bÄde "snabba" och "lÄngsamma" maskiner!

FrĂ„n och med nu började det tydligt lukta svavel i luften. För problemet kunde inte tillskrivas nĂ„gon virtuell maskin (windows patches, till exempel) — eftersom den blev "snabb" vid vMotion. Men problemet kunde inte heller tillskrivas vĂ€rden — eftersom det kunde finnas bĂ„de "snabba" och "lĂ„ngsamma" maskiner dĂ€r. Det var inte heller relaterat till belastningen — jag lyckades fĂ„ en "lĂ„ngsam" maskin pĂ„ en vĂ€rd dĂ€r det förutom den inte fanns nĂ„got annat.

I desperation startade jag Process Explorer frÄn Sysinternals och kollade SQL-stacken. PÄ de lÄngsamma maskinerna sÄg jag genast en rad:

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

 hoppade över
sqldk.dll!SystemThread::MakeMiniSOSThread+0xa54
KERNEL32.DLL!BaseThreadInitThunk+0x14
ntdll.dll!RtlUserThreadStart+0x21

Det hÀr var redan nÄgot. Ett program hade skrivits:

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

Det hĂ€r programmet visade en Ă€nnu mer mĂ€rkbar nedgĂ„ng — pĂ„ "snabba" maskiner visar den 16-18 miljoner cykler per sekund, medan pĂ„ lĂ„ngsamma maskiner Ă€r det 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: Ă„tminstone fanns det ingen risk att fastna mellan Microsoft och VMware-supporten sĂ„ att de skyllde pĂ„ varandra.

Sedan stannade framsteget — semester, viktiga saker, virushysteri och plötsligt ökad belastning. Jag nĂ€mnde ofta det magiska problemet för mina kollegor, men ibland verkade det som om de inte alltid trodde mig — det verkade för osannolikt att VMware skulle sakta ner koden med 10-20 gĂ„nger.

Jag försökte sjĂ€lv grĂ€va fram vad som saktade ner. Ibland tycktes det som om jag hade hittat en lösning — att aktivera och avaktivera Hot plugs, att Ă€ndra mĂ€ngden minne eller antal processorer gjorde ofta maskinen "snabb". Men inte för alltid. Men vad som visade sig vara sant Ă€r att det rĂ€cker med att gĂ„ ut och knacka pĂ„ hjulet — det vill sĂ€ga att Ă€ndra nĂ„gon parameter för virtuell maskin

Till slut upptÀckte mina amerikanska kollegor plötsligt rotorsaken.

Ja, min gamla laptop Àr flera gÄnger kraftfullare Àn din produktionsserver

VĂ€rdarna skiljde sig i frekvens!

  • Som regel Ă€r det inte farligt. Men: nĂ€r du flyttar frĂ„n en 'inhemsk' vĂ€rd till en vĂ€rd med 'annan' frekvens, mĂ„ste VMware justera resultatet av GetTimePrecise.
  • Som regel Ă€r det inte farligt, om det inte visar sig att det Ă€r applikationer som begĂ€r exakt tid miljontals gĂ„nger per sekund, som SQL-servern.
  • Men det Ă€r inte heller farligt, eftersom SQL-servern inte gör detta hela tiden (se Slutsats)

Men det finns fall dÀr dessa fallgropar kan göra ont. Och ja, genom att knacka pÄ hjulet (Àndra nÄgot i VM-instÀllningarna) fick jag VMware att 'omberÀkna' konfigurationen, och den aktuella vÀrdens frekvens blev 'inhemsk' frekvens för maskinen.

Lösning

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

NÀr du inaktiverar virtualisering av TSC, Äterger lÀsning av TSC frÄn den virtuella maskinen det fysiska maskinens TSC-vÀrde, och skrivning till TSC frÄn den virtuella maskinen har ingen effekt. Migrera den virtuella maskinen till en annan vÀrd, Äteruppta den frÄn knappat lÀge, eller ÄtergÄ till en ögonblicksbild orsakar en diskontinuerlig hopp i TSC. Vissa gÀstoperativsystem kan misslyckas med att starta, eller visa andra tidsmÀtningproblem, nÀr TSC-virtualisering Àr inaktiverad. Tidigare har denna funktion ibland rekommenderats för att förbÀttra prestandan hos applikationer som ofta lÀser TSC., men prestandan för den virtuella TSC har förbÀttrats avsevÀrt i nuvarande produkter. Funktionen har ocksÄ rekommenderats för anvÀndning vid mÀtningar som krÀver en precis kÀlla till realtid i den virtuella maskinen.

För att sammanfatta, mÄste parametern lÀggas till

monitor_control.virtual_rdtsc = FALSE

Slutsats

Du har sÀkert undrat: varför anropas GetTimePrecise sÄ ofta i SQL?

Jag har inte kĂ€llkoden för SQL-servern, men logiken sĂ€ger detta. SQL Ă€r nĂ€stan ett operativsystem med kooperativ samtidighet, dĂ€r varje trĂ„d mĂ„ste ibland "ge vika". Och vart Ă€r det bĂ€st att göra det? DĂ€r det finns ett naturligt vĂ€ntande — lĂ„s eller IO. Okej, men vad hĂ€nder om vi snurrar rĂ€kneslingor? DĂ„ Ă€r det uppenbara och nĂ€stan enda stĂ€llet — i tolkaren (det Ă€r inte riktigt en tolk), efter att ha utfört den senaste kommandot.

Som regel anvÀnds SQL-servern inte för att spika in rena berÀkningar och det Àr inget problem. Men slingor som arbetar med alla möjliga tillfÀlliga tabeller (som sedan cachas) gör koden till en sekvens av mycket snabbt utförda kommandon.

Förresten, om du kapslar in funktionen i NATIVELY COMPILED, slutar den att begÀra tid, och dess hastighet ökar tio gÄnger. Men hur Àr det med kooperativ multitasking? Jo, för natively compiled code var det just dÀrför SQL behövde göra PREEMPTIVE MULTITASKING.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster