Ovo su tvrdnje koje sam čuo od naših programera. Najzanimljivije je da se to pokazalo točnim, što je dovelo do dugotrajne istrage. Govorit ćemo o SQL poslužiteljima koji rade na VMware-u.
Zapravo, lako je postaviti produkcijski poslužitelj beznadno iza prijenosnog računala. Pokrenite (ne na tempdb i ne na bazi podataka s omogućenom odgođenom izdržljivošću) kod:
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
Potrebno je 5 sekundi na mojoj radnoj površini i 28 sekundi na proizvodnom poslužitelju. Budući da SQL mora čekati fizički kraj pisanja u dnevnik transakcija, a mi ovdje radimo vrlo kratke transakcije. Ugrubo rečeno, veliki moćni kamion smo uletjeli u gradsku gužvu i gledamo kako ga famozno pretječu dostavljači pizze na skuterima - tu nije bitna propusnost, bitna je samo latencija. I niti jedna mrežna pohrana, koliko god nula bila u njenoj cijeni, neće moći nadmašiti lokalni SSD u latenciji.
(u komentarima se pokazalo da sam lagao - imao sam odgođenu trajnost na oba mjesta. Bez odgođene trajnosti ispada:
Stolno računalo - 39 sekundi, 15K tr/s, 0.065 ms /io povratno putovanje
PROD - 360 sekundi, 1600 tr/sek, 0.6 ms
Trebao sam primijetiti da je prebrzo)
Međutim, u ovom slučaju radi se o trivijalnim nulama Riemannove zeta funkcije s trivijalnim primjerom. U primjeru koji su mi programeri donijeli bilo je drugačije. Uvjerio sam se da su u pravu i počeo iz primjera čistiti sve njihove specifičnosti vezane uz poslovnu logiku. U nekom trenutku sam shvatio da mogu potpuno odbaciti njihov kod, i napisati svoj - što pokazuje isti problem - u produkciji radi 3-4 puta sporije:
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
Ako je s vama sve u redu, tada će provjera jednostavnosti broja trajati 6-7-8 sekundi. To se dogodilo na brojnim poslužiteljima. Ali na nekima je provjera trajala 25-40 sekundi. Zanimljivo, nije bilo servera na kojima bi izvršenje trajalo recimo 14 sekundi - kod je radio ili jako brzo ili jako sporo, odnosno problem je bio, ajmo reći, crno-bijeli.
Što sam učinio? Ušao u VMware metrics. Tu je sve bilo u redu - bilo je dosta resursa, Ready time = 0, svega je bilo dovoljno, tijekom testa i na brzim i na sporim poslužiteljima CPU = 100 na jednom vCPU-u. Napravio sam test za izračunavanje broja Pi - test je pokazao iste rezultate na svim poslužiteljima. Miris crne magije postajao je sve jači i jači.
Nakon što sam izašao na DEV farmu, počeo sam se igrati s poslužiteljima. Pokazalo se da vMotion od hosta do hosta može “izliječiti” server, ali isto tako može “brzi” server pretvoriti u “spori”. Čini se da je to to - neki hostovi imaju problem ... ali ... ne. Neki virtualni stroj je usporio na hostu, recimo A, ali je radio brzo na hostu B. A drugi virtualni stroj je, naprotiv, radio brzo na A i usporio na B! Na domaćinu su se često vrtjeli i “brzi” i “spori” automobili!
Od tog trenutka u zraku se osjećao jasan miris sumpora. Uostalom, problem se ne može pripisati nijednom virtualnom stroju (npr. zakrpe za Windows) - uostalom, pretvorio se u "brzi" s vMotionom. Ali problem se također ne može pripisati hostu - na kraju krajeva, mogao je imati i "brze" i "spore" strojeve. Također nije bilo povezano s opterećenjem - uspio sam dobiti "spori" stroj na hostu, gdje nije bilo ničega osim njega.
Iz očaja sam pokrenuo Sysinternalsov Process Explorer i pogledao SQL stog. Na sporim strojevima linija mi je odmah zapela za oko:
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
… preskočeno
sqldk.dll!SystemThread::MakeMiniSOSThread+0xa54
KERNEL32.DLL!BaseThreadInitThunk+0x14
ntdll.dll! RtlUserThreadStart + 0x21
To je već bilo nešto. Program je napisan:
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);
}
}
}
}
Ovaj program pokazao je još izraženije usporavanje - na "brzim" strojevima pokazuje 16-18 milijuna ciklusa u sekundi, dok na sporim - milijun i pol, ili čak 700 tisuća. Odnosno, razlika je 10-20 puta (!!!). To je već bila mala pobjeda: u svakom slučaju nije prijetilo da se zaglavimo između Microsoftove i VMware podrške pa da jedna drugoj prebacuju strelice.
Zatim je napredak stao - odmor, važne stvari, virusna histerija i naglo povećanje opterećenja. Kolegama sam često spominjao magični problem, ali ponekad se činilo da mi čak ni ne vjeruju uvijek - izjava da je VMware usporio kod za 10-20 puta bila je previše monstruozna.
Pokušao sam sam iskopati što ga usporava. Na trenutke mi se činilo da sam pronašao rješenje - paljenjem i gašenjem Hot plugova, mijenjanjem količine memorije ili broja procesora često je stroj postajao “brzi”. Ali ne zauvijek. No ono što se pokazalo točnim jest da je dovoljno izaći van i pokucati po volanu – odnosno promijeniti bilo koji parametar virtualnog stroja
Napokon su moji američki kolege iznenada pronašli temeljni uzrok.
Domaćini su se razlikovali po učestalosti!
- U pravilu, to nije zastrašujuće. Ali: prilikom prelaska s 'nativnog' hosta na host s 'različitom' frekvencijom, VMware mora prilagoditi GetTimePrecise rezultat.
- U pravilu to nije problem, osim ako ne postoji aplikacija koja traži točno vrijeme milijune puta u sekundi, poput SQL servera.
- Ali ni to nije strašno, jer SQL poslužitelj to ne radi uvijek (pogledajte zaključak)
Ali postoje slučajevi kada ove grablje boli. I da, kuckanjem na kotačiću (promjenom nečega u postavkama VM-a), natjerao sam VMware da 'preračuna' konfiguraciju, a frekvencija trenutnog hosta postala je 'native' frekvencija stroja.
odluka
Kada onemogućite virtualizaciju TSC-a, čitanje TSC-a unutar virtualnog stroja vraća vrijednost TSC-a fizičkog stroja, a pisanje TSC-a unutar virtualnog stroja nema učinka. Migracija virtualnog stroja na drugo glavno računalo, njegovo ponovno pokretanje iz suspendiranog stanja ili vraćanje na snimku uzrokuje nekontinuirano skakanje TSC-a. Neki gostujući operativni sustavi ne mogu se pokrenuti ili pokazuju druge probleme s mjerenjem vremena kada je TSC virtualizacija onemogućena. U prošlosti se ova značajka ponekad preporučivala za poboljšanje performansi aplikacija koje često čitaju TSC, ali izvedba virtualnog TSC-a znatno je poboljšana u trenutnim proizvodima. Značajka se također preporučuje za korištenje pri izvođenju mjerenja koja zahtijevaju precizan izvor stvarnog vremena u virtualnom stroju.
Ukratko, trebate dodati parametar
monitor_control.virtual_rdtsc = FALSE
Zaključak
Vjerojatno imate pitanje: zašto bi SQL tako često pozivao GetTimePrecise?
Nemam izvore SQL servera, ali logika kaže ovako. SQL je gotovo operativni sustav s kooperativnom konkurentnošću, gdje svaka nit mora "popustiti" s vremena na vrijeme. Gdje je to najbolje učiniti? Gdje postoji prirodno očekivanje - zaključavanje ili IO. U redu, ali što ako vrtimo računalne cikluse? Tada je očito i gotovo jedino mjesto u interpreteru (ovo i nije baš interpreter), nakon izvršenja sljedećeg operatora.
SQL poslužitelj se u pravilu ne koristi za čisto računalstvo i to nije problem. Ali ciklusi s radom sa svim vrstama privremenih tablica (koje se odmah pohranjuju u predmemoriju) pretvaraju kod u slijed vrlo brzo izvršenih naredbi.
Usput, ako je funkcija umotana u NATIVELY COMPILED, tada prestaje zahtijevati vrijeme, a njezina brzina raste 10 puta. Ali što je s kooperativnim multitaskingom? Ali za nativno kompilirani kod, morao sam napraviti PREEMPTIVE MULTITASKING u SQL-u.
Izvor: www.habr.com