Ano, můj starý notebook je několikrát výkonnější než váš produkční server.

Přesně tyto stížnosti jsem slyšel od našich vývojářů. Nejzajímavější na tom je, že se to ukázalo jako pravda, což vedlo k dlouhému vyšetřování. Budeme mluvit o SQL serverech, které běží na VMware.

Ano, můj starý notebook je několikrát výkonnější než váš produkční server.

Ve skutečnosti je snadné zajistit, aby produkční server byl beznadějně za notebookem. Spusťte (ne na tempdb a ne na databázi s povolenou zpožděnou trvanlivostí) 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 mém desktopu to trvá 5 sekund a na produkčním serveru to trvá 28 sekund. Protože SQL musí čekat na fyzický konec záznamu transakčního protokolu a my zde provádíme velmi krátké transakce. Zhruba řečeno, vjeli jsme do městského provozu velkým, výkonným náklaďákem a sledovali, jak ho svižně předjíždějí doručovatelé pizzy na skútrech – propustnost zde není důležitá, důležitá je pouze latence. A žádné síťové úložiště, bez ohledu na to, kolik nul je v jeho ceně, nemůže porazit místní SSD z hlediska latence.

(v komentářích se ukázalo, že jsem lhal - měl jsem zpožděnou trvanlivost na obou místech. Bez opožděné trvanlivosti se ukazuje:
Desktop – 39 sekund, 15 kB tr/s, 0.065 ms/io zpáteční cesta
PROD - 360 sekund, 1600 tr/s, 0.6 ms
Měl jsem si všimnout, že to bylo příliš rychlé)

V tomto případě však máme co do činění s triviálními nulami Riemannovy zeta funkce s triviálním příkladem. V příkladu, který mi vývojáři přinesli, to bylo jiné. Byl jsem přesvědčen, že mají pravdu, a začal jsem z příkladu odstraňovat všechna jejich specifika související s obchodní logikou. V určitém okamžiku jsem si uvědomil, že bych mohl úplně zahodit jejich kód a napsat svůj vlastní - což ukazuje stejný problém - ve výrobě to běží 3-4krát pomaleji:

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

Pokud je vše v pořádku, bude kontrola primality čísla trvat 6-7-8 sekund. Stalo se to na několika serverech. Ale u některých trvala kontrola 25-40 sekund. Zajímavé je, že nebyly žádné servery, kde by provedení trvalo řekněme 14 sekund – kód fungoval buď velmi rychle, nebo velmi pomalu, to znamená, že problém byl řekněme černobílý.

Co jsem udělal? Použité metriky VMware. Všechno tam bylo v pořádku - zdrojů dostatek, Ready time = 0, všeho bylo dost, při testu na rychlých i pomalých serverech CPU = 100 na jednom vCPU. Udělal jsem test na výpočet čísla Pi - test ukázal stejné výsledky na jakémkoli serveru. Vůně černé magie byla stále silnější.

Jakmile jsem se dostal na farmu DEV, začal jsem hrát se servery. Ukázalo se, že vMotion z hostitele na hostitele může „vyléčit“ server, ale může také změnit „rychlý“ server na „pomalý“. Zdá se, že je to tak - někteří hostitelé mají problém... ale... ne. Některý virtuální stroj byl pomalý na hostiteli, řekněme A, ale pracoval rychle na hostiteli B. A jiný virtuální stroj naopak pracoval rychle na A a zpomalil na B! Na hostiteli se často točily „rychlé“ i „pomalé“ stroje!

Od té chvíle byl ve vzduchu cítit zřetelný zápach síry. Koneckonců, problém nelze připsat virtuálnímu stroji (například záplaty Windows) - koneckonců se s vMotion změnil na „rychlý“. Problém však také nelze připsat hostiteli - koneckonců mohl mít „rychlé“ i „pomalé“ stroje. Také to nesouviselo se zátěží - podařilo se mi dostat „pomalý“ stroj na hostitele, kde kromě něj nebylo vůbec nic.

Ze zoufalství jsem spustil Process Explorer od Sysinternals a podíval se na SQL stack. Na pomalých strojích mě linka okamžitě 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
... přeskočeno
sqldk.dll!SystemThread::MakeMiniSOSThread+0xa54
KERNEL32.DLL!BaseThreadInitThunk+0x14
ntdll.dll! RtlUserThreadStart + 0x21

Tohle už bylo něco. Program byl napsán:

    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 předvedl ještě výraznější zpomalení - na „rychlých“ strojích ukazuje 16-18 milionů cyklů za sekundu, zatímco na pomalých strojích ukazuje jeden a půl milionu nebo dokonce 700 tisíc. To znamená, že rozdíl je 10-20krát (!!!). To už bylo malé vítězství: každopádně nehrozilo, že se mezi Microsoftem a podporou VMware zaseknete tak, že by si navzájem točily šipky.

Pak se pokrok zastavil – dovolené, důležité záležitosti, virová hysterie a prudký nárůst pracovní zátěže. Často jsem se o magickém problému zmiňoval svým kolegům, ale občas se zdálo, že mi ani vždy nevěřili - prohlášení, že VMware zpomaluje kód 10-20krát, bylo příliš obludné.

Snažil jsem se vydolovat to, co mě zpomalovalo. Občas se mi zdálo, že jsem našel řešení – zapínání a vypínání Hot plugs, změna velikosti paměti nebo počtu procesorů často proměnila stroj v „rychlý“. Ale ne navždy. Co se ale ukázalo jako pravda, stačí vyjet a zaklepat na volant – tedy převléknout každý parametr virtuálního stroje

Konečně moji američtí kolegové náhle našli hlavní příčinu.

Ano, můj starý notebook je několikrát výkonnější než váš produkční server.

Domácí se lišili ve frekvenci!

  • Zpravidla nejde o nic velkého. Ale: při přechodu z „nativního“ hostitele na hostitele s „jinou“ frekvencí musí VMware upravit výsledek GetTimePrecise.
  • Zpravidla to není problém, pokud neexistuje aplikace, která požaduje přesný čas milionkrát za sekundu, jako je SQL server.
  • Ale to není děsivé, protože SQL server to vždy nedělá (viz Závěr)

Jsou ale případy, kdy toto hrábě zasáhne tvrdě. A přesto ano, klepnutím na kolečko (změnou něčeho v nastavení VM) jsem přinutil VMware „přepočítat“ konfiguraci a frekvence aktuálního hostitele se stala „nativní“ frekvencí stroje.

rozhodnutí

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

Když zakážete virtualizaci TSC, čtení TSC z virtuálního stroje vrátí hodnotu TSC fyzického stroje a zápis TSC z virtuálního stroje nemá žádný vliv. Migrace virtuálního počítače na jiného hostitele, jeho obnovení z pozastaveného stavu nebo návrat ke snímku způsobí, že TSC bude přerušovaně přeskakovat. Některé hostované operační systémy se nespustí nebo vykazují jiné problémy s měřením času, když je virtualizace TSC zakázána. V minulosti byla tato funkce někdy doporučována pro zlepšení výkonu aplikací, které často čtou TSC, ale výkon virtuálního TSC byl u současných produktů podstatně vylepšen. Tato funkce byla také doporučena pro použití při provádění měření, která vyžadují přesný zdroj reálného času ve virtuálním počítači.

Stručně řečeno, musíte přidat parametr

monitor_control.virtual_rdtsc = FALSE

Závěr

Pravděpodobně máte otázku: proč SQL volá GetTimePrecise tak často?

Nemám zdrojový kód SQL serveru, ale logika říká toto. SQL je téměř operační systém s kooperativní souběžností, kde se každé vlákno musí čas od času „poddat“. Kde je nejlepší místo k tomu? Kde je přirozené čekání - zámek nebo IO. Dobře, ale co když točíme výpočetní smyčky? Pak je zřejmé a téměř jediné místo v tlumočníku (ve skutečnosti to není tlumočník), po provedení dalšího příkazu.

Obecně se SQL server nepoužívá pro čistě výpočetní přibíjení a to není problém. Ale smyčky, které pracují s nejrůznějšími dočasnými tabulkami (které se okamžitě ukládají do mezipaměti), mění kód na sekvenci velmi rychle provedených příkazů.

Mimochodem, pokud funkci zabalíte do NATIVNĚ KOMPILOVANÉ, tak přestane žádat o čas a její rychlost se zvýší 10x. Co kooperativní multitasking? Ale pro nativně zkompilovaný kód jsme museli provést PREEMPTIVNÍ MULTITASKING v SQL.

Zdroj: www.habr.com

Přidat komentář