Áno, môj starý laptop je niekoľkonásobne výkonnejší ako váš produkčný server.

Presne tieto sťažnosti som počul od našich vývojárov. Najzaujímavejšie je, že sa to ukázalo ako pravda, čo viedlo k dlhému vyšetrovaniu. Budeme hovoriť o SQL serveroch, ktoré bežia na VMware.

Áno, môj starý laptop je niekoľkonásobne výkonnejší ako váš produkčný server.

V skutočnosti je ľahké zabezpečiť, aby bol produkčný server beznádejne za notebookom. Vykonajte (nie na tempdb a nie na databáze s povolenou oneskorenou trvanlivosťou) kód:

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

Na mojej pracovnej ploche to trvá 5 sekúnd a na produkčnom serveri to trvá 28 sekúnd. Pretože SQL musí čakať na fyzický koniec záznamu transakčného protokolu a my tu robíme veľmi krátke transakcie. Zhruba povedané, vrazili sme do mestskej premávky veľké, silné nákladné auto a sledovali sme, ako ho razantne predbiehajú doručovatelia pizze na skútroch – priepustnosť tu nie je dôležitá, dôležitá je len latencia. A žiadne sieťové úložisko, bez ohľadu na to, koľko núl je v jeho cene, nedokáže poraziť lokálny SSD z hľadiska latencie.

(v komentároch sa ukázalo, že som klamal - mal som oneskorenú trvanlivosť na oboch miestach. Bez oneskorenej trvanlivosti sa ukazuje:
Desktop – 39 sekúnd, 15 kB tr/s, 0.065 ms/io spiatočná cesta
PROD - 360 sekúnd, 1600 tr/s, 0.6 ms
Mal som si všimnúť, že to bolo príliš rýchle)

V tomto prípade však máme do činenia s triviálnymi nulami Riemannovej zeta funkcie s triviálnym príkladom. V príklade, ktorý mi priniesli vývojári, to bolo iné. Bol som presvedčený, že majú pravdu, a začal som z príkladu odstraňovať všetky ich špecifiká súvisiace s obchodnou logikou. V určitom okamihu som si uvedomil, že by som mohol úplne zahodiť ich kód a napísať svoj vlastný - čo demonštruje rovnaký problém - vo výrobe to beží 3-4 krát pomalšie:

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

Ak je všetko v poriadku, kontrola primárnosti čísla bude trvať 6-7-8 sekúnd. Stalo sa to na viacerých serveroch. Ale u niektorých trvala kontrola 25-40 sekúnd. Zaujímavé je, že neexistovali žiadne servery, kde by spustenie trvalo povedzme 14 sekúnd – kód fungoval buď veľmi rýchlo, alebo veľmi pomaly, to znamená, že problém bol, povedzme, čiernobiely.

Čo som urobil? Použité metriky VMware. Všetko tam bolo v poriadku - dostatok zdrojov, doba pripravenosti = 0, všetkého bolo dosť, počas testu na rýchlych aj pomalých serveroch CPU = 100 na jednom vCPU. Urobil som test na výpočet čísla Pi - test ukázal rovnaké výsledky na akomkoľvek serveri. Vôňa čiernej mágie bola čoraz silnejšia.

Keď som sa dostal na farmu DEV, začal som hrať so servermi. Ukázalo sa, že vMotion z hostiteľa na hostiteľa môže „vyliečiť“ server, ale môže tiež zmeniť „rýchly“ server na „pomalý“. Zdá sa, že je to tak – niektorí hostitelia majú problém... ale... nie. Niektorý virtuálny stroj bol pomalý na hostiteľovi, povedzme A, ale pracoval rýchlo na hostiteľovi B. A iný virtuálny stroj naopak pracoval rýchlo na A a spomalil na B! Na hostiteľovi sa často točili „rýchle“ aj „pomalé“ stroje!

Od tej chvíle bolo vo vzduchu cítiť zreteľný zápach síry. Koniec koncov, problém nemožno pripísať virtuálnemu stroju (napríklad záplaty systému Windows) - koniec koncov sa s vMotion zmenil na „rýchle“. Problém však nemožno pripísať hostiteľovi - koniec koncov, môže mať „rýchle“ aj „pomalé“ stroje. Tiež to nesúviselo so záťažou - podarilo sa mi dostať „pomalý“ stroj na hostiteľa, kde okrem neho nebolo vôbec nič.

Zo zúfalstva som spustil Process Explorer od Sysinternals a pozrel sa na SQL stack. Na pomalých strojoch ma linka okamžite zaujala:

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

Toto už bolo niečo. Program bol napísaný:

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

Tento program ukázal ešte výraznejšie spomalenie - na „rýchlych“ strojoch ukazuje 16-18 miliónov cyklov za sekundu, zatiaľ čo na pomalých strojoch ukazuje jeden a pol milióna alebo dokonca 700 tisíc. To znamená, že rozdiel je 10-20 krát (!!!). To už bolo malé víťazstvo: každopádne nehrozilo, že by sa medzi Microsoftom a podporou VMware zasekli tak, že by si navzájom otáčali šípky.

Potom sa pokrok zastavil – dovolenky, dôležité záležitosti, vírusová hystéria a prudký nárast pracovnej záťaže. Často som sa o magickom probléme zmienil svojim kolegom, ale občas sa zdalo, že mi ani vždy neverili - vyhlásenie, že VMware spomalí kód 10-20 krát, bolo príliš obludné.

Snažil som sa vyhrabať zo seba to, čo ma brzdilo. Občas sa mi zdalo, že som našiel riešenie – zapínanie a vypínanie Hot plugs, zmena množstva pamäte alebo počtu procesorov často zmenili stroj na „rýchly“. Ale nie navždy. Čo sa však ukázalo ako pravda, stačí vyjsť a zaklopať na volant – teda prezliecť každý parameter virtuálneho stroja

Nakoniec moji americkí kolegovia zrazu našli hlavnú príčinu.

Áno, môj starý laptop je niekoľkonásobne výkonnejší ako váš produkčný server.

Domáci sa líšili vo frekvencii!

  • Spravidla to nie je veľký problém. Ale: pri prechode z „natívneho“ hostiteľa na hostiteľa s „inou“ frekvenciou musí VMware upraviť výsledok GetTimePrecise.
  • Spravidla to nie je problém, pokiaľ neexistuje aplikácia, ktorá požaduje presný čas miliónkrát za sekundu, ako napríklad SQL server.
  • Ale to nie je desivé, pretože SQL server to nie vždy robí (pozri Záver)

Ale sú prípady, kedy tento hrable tvrdo zasiahne. A predsa áno, poklepaním na koliesko (zmenou niečoho v nastaveniach VM) som prinútil VMware „prepočítať“ konfiguráciu a frekvencia aktuálneho hostiteľa sa stala „natívnou“ frekvenciou stroja.

rozhodnutie

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

Keď zakážete virtualizáciu TSC, čítanie TSC z virtuálneho stroja vráti hodnotu TSC fyzického stroja a zápis TSC z virtuálneho stroja nemá žiadny vplyv. Migrácia virtuálneho počítača na iného hostiteľa, jeho obnovenie z pozastaveného stavu alebo návrat k snímke spôsobí, že TSC bude prerušovane skákať. Keď je virtualizácia TSC zakázaná, niektoré hosťujúce operačné systémy sa nezavedú alebo vykazujú iné problémy s časomierou. V minulosti sa táto funkcia niekedy odporúčala na zlepšenie výkonu aplikácií, ktoré často čítajú TSC, ale výkon virtuálneho TSC sa v súčasných produktoch podstatne zlepšil. Táto funkcia bola tiež odporúčaná na použitie pri vykonávaní meraní, ktoré vyžadujú presný zdroj reálneho času vo virtuálnom stroji.

Stručne povedané, musíte pridať parameter

monitor_control.virtual_rdtsc = FALSE

Záver

Pravdepodobne máte otázku: prečo SQL tak často volá GetTimePrecise?

Nemám zdrojový kód SQL servera, ale logika hovorí toto. SQL je takmer operačný systém s kooperatívnou súbežnosťou, kde sa každé vlákno musí z času na čas „podvoliť“. Kde je to najlepšie urobiť? Tam, kde je prirodzené čakanie - zámok alebo IO. Dobre, ale čo ak točíme výpočtové slučky? Potom je zrejmé a takmer jediné miesto v tlmočníkovi (v skutočnosti to nie je tlmočník), po vykonaní ďalšieho príkazu.

Vo všeobecnosti sa SQL server nepoužíva na čisto výpočtové pribíjanie a to nie je problém. Ale slučky, ktoré pracujú s najrôznejšími dočasnými tabuľkami (ktoré sa okamžite ukladajú do vyrovnávacej pamäte), menia kód na sekvenciu veľmi rýchlo vykonaných príkazov.

Mimochodom, ak funkciu zabalíte do NATIVELY COMPILED, tak prestane pýtať čas a jej rýchlosť sa zvýši 10-krát.Ako je to kooperatívny multitasking? Ale pre natívne skompilovaný kód sme museli urobiť PREEMPTIVE MULTITASKING v SQL.

Zdroj: hab.com

Pridať komentár