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.

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
GOOm 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.

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
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
