Takéto obavy 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. Hovoríme o SQL serveroch, ktoré bežia na našom VMware.

V skutočnosti je veľmi jednoduché dosiahnuť, aby production server úplne zaostával za laptopom. Spustite (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 'Aký pomalý!'
delete from _t
set @n=@n-1
end
GO
drop table _t
Na mojom desktopu to trvá 5 sekúnd, zatiaľ čo na production serveri 28 sekúnd. Pretože SQL musí čakať na fyzické dokončenie zápisu do transakčného logu a my tu robíme veľmi krátke transakcie. Zjednodušene povedané, dostali sme veľký silný kamión do mestského premávky a sledujeme, ako ho rýchlo predbehujú kuriéri na skutroch - tu nie je dôležitý throughput, dôležitá je len latencia. A žiadne sieťové úložisko, nech je jeho cena akokoľvek vysoká, nemôže vyhrať v latencii proti miestnemu SSD.
(v komentároch sa ukázalo, že som klamal - v oboch miestach mám oneskorenú trvanlivosť. Bez oneskorenej trvanlivosti to vychádza:
Desktop - 39 sekúnd, 15K tr/sec, 0.065ms /io roundtrip
PROD - 360 sekúnd, 1600 tr/sec, 0.6ms
Mal som si všimnúť, že bolo príliš rýchlo)
V tomto prípade však máme dočinenia s triviálnymi nulami z tejto Riemannovej funkcie s triviálnym príkladom. V tom príklade, ktorý mi priniesli vývojári, bolo niečo iné. Uistil som sa, že majú pravdu, a začal som čistiť z príkladu všetku ich špecifickosť súvisiacu s obchodnou logikou. V istom momente som si uvedomil, že môžem úplne vyhodiť ich kód a napísať svoj - ktorý demonštruje rovnaký problém - na production 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 -- controluj nepárne čísla až do 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
GOAk máte všetko v poriadku, kontrola prvočísel bude trvať 6-7-8 sekúnd. Tak to bolo na niekoľkých serverov. Ale na niektorých trvala kontrola 25-40 sekúnd. Zaujímavé je, že neexistovali servery, na ktorých by vykonávanie trvalo napríklad 14 sekúnd - kód fungoval buď veľmi rýchlo, alebo úplne pomaly, to znamená, že problém bol, povedzme, čierno-biely.
Čo som urobil? Pozrel som do metriky VMware. Všetko vyzeralo v poriadku – zdroje boli dostatočné, Ready time = 0, všetko stačilo, počas testu na rýchlych aj pomalých serveroch CPU=100 na jednom vCPU. Vzal som test na výpočet čísla Pi – test ukazoval rovnaké výsledky na akýchkoľvek serveroch. Čoraz viac to vonia po čiernej mágii.
Keď som sa dostal na DEV farmu, začal som experimentovať so servermi. Zistilo sa, že vMotion z hostiteľa na hostiteľa dokáže "vyliečiť" server, ale môže aj naopak, "rýchly" server premeniť na "pomalý". Zjavne toto bude – niektoré hostiteľské servery majú problém... ale... nie. Nejaká virtuálka spomaľovala na hostiteľovi, povedzme A, ale na hostiteľovi B fungovala rýchlo. A iná virtuálka naopak, fungovala rýchlo na A a pomaly na B! Na hostiteľovi často rotovali aj "rýchle" aj "pomalé" mašinky!
Od tohto momentu vzduch jasne zapáchal sírou. Veď problém nemohol byť pripísaný žiadnej virtuálke (napríklad windows patchy) – veď sa stala "rýchlou" pri vMotion. Ale problém nemohol byť ani na hostiteľovi – veď na ňom mohli byť ako "rýchle", tak aj "pomalé" mašinky. Taktiež to nebolo spojené s zaťažením – podarilo sa mi získať "pomalú" mašinu na hostiteľovi, kde okrem nej nebolo nič.
Z beznádeje som spustil Process Explorer od Sysinternals a pozrel som sa na zásobník SQL. Na pomalých mašinkách mi hneď padla do očí táto línia:
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
To 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 0)
{
Console.WriteLine("{0}", counter);
}
}
}
}Tento program ukázal ještě výraznější zpomalení - na 'rychlých' strojích ukazuje 16-18 milionů cyklů za sekundu, zatímco na pomalých - jeden a půl milionu, ne-li 700 tisíc. To znamená, že rozdíl činí 10-20krát (!!!). To byla malá výhra: v každém případě tu nebyla hrozba uváznout mezi podporou Microsoftu a VMware, tak aby se vzájemně obviňovali.
Dále pokrok zastavil - dovolená, důležité záležitosti, virusová hysterie a náhlé zvýšení zátěže. Často jsem zmiňoval magický problém kolegům, ale občas se zdálo, že mi ani nevěří - příliš monstruózní bylo tvrzení, že VMware zpomaluje kód 10-20krát.
Snažil jsem se sám zjistit, co zpomaluje. Občas jsem měl pocit, že jsem našel řešení - zapnutí a vypnutí hot plugů, změna velikosti paměti nebo počtu procesorů často proměnila stroj v 'rychlý'. Ale ne navždy. Ale co se ukázalo jako pravda - stačilo vyjít a zaklepat na kolo - to znamená změnit ktokoľvek parametr virtuálního stroje
Nakonec moji američtí kolegové náhle našli hlavní příčinu.

Hostitelé se lišili frekvencí!
- Obvykle to není děsivé. Ale: při přesunu z 'domovského' hostitele na hostitele s 'jinou' frekvencí musí VMware upravit výsledek GetTimePrecise.
- Obvykle to není problém, pokud nedochází k aplikaci, která žádá přesný čas milionykrát za sekundu, jako SQL server.
- Ale ani to není problém, protože SQL server to nedělá vždy (viz Závěr)
Ale jsou případy, kdy tyto pastičky bolestivě udeří. A ano, zaklepáním na kolo (změnou něčeho v nastavení VM) jsem donutil VMware 'přepočítat' konfiguraci a frekvence aktuálního hostitele se stala 'domovskou' frekvencí stroje.
Riešenie
Když deaktivujete virtualizaci TSC, čtení TSC z virtuálního stroje vrací hodnotu TSC fyzického stroje a zapisování TSC z virtuálního stroje nemá žádný účinek. Migrace virtuálního stroje na jiný hostitel, obnovení ze zadržovacího stavu nebo návrat k snímku způsobí, že TSC nepřetržitě vyskočí. Některé hostitelské operační systémy se nespustí nebo vykazují další problémy s časoměřením, když je virtualizace TSC zakázána. V minulosti byla tato funkce někdy doporučována k zlepšení výkonu aplikací, které často čtou TSC., ale výkon virtuální TSC byl v aktuálních produktech výrazně zlepšen. Tato funkce byla také doporučena pro měření, která vyžadují přesný zdroj reálného času ve virtuálním stroji.
Stručně řečeno, je potřeba přidat parametr
monitor_control.virtual_rdtsc = FALSE
Záver
Určite sa pýtate: prečo je potrebné častejšie volať GetTimePrecise v SQL?
Nemám prístup k zdrojovým kódom SQL servera, ale logika hovorí toto. SQL je takmer operačný systém s kooperatívnym paralelizmom, kde každý vlákno by malo čas od času "ustúpiť". A kde je najlepšie to urobiť? Tam, kde je prirodzené čakanie — pri zámku alebo IO. Dobre, a čo ak vykonávame výpočtové cykly? Potom je evidentné a takmer jediné miesto, kde sa to deje — v interpreteri (to nie je úplne interpreter), po vykonaní nasledujúcej inštrukcie.
Vo všeobecnosti sa SQL server nepoužíva na vykonávanie čisto výpočtových úloh a to nie je problém. Ale cykly s manipuláciou s rôznymi dočasnými tabuľkami (ktoré sú ihneď do cache pamäte) transformujú kód na sekvenciu veľmi rýchlo vykonávaných príkazov.
Mimochodom, ak obalíte funkciu do NATIVELY COMPILED, prestane žiadať čas a jej rýchlosť sa zvýši desaťnásobne. A čo kooperatívne multitasking? Práve preto bol v SQL pre natively compiled code zavedený PREEMPTIVE MULTITASKING.
Zdroj: habr.com
