Igen, a régi laptopom többszörösen erősebb, mint a termelési szervere.

Pontosan ezeket a panaszokat hallottam a fejlesztőinktől. A legérdekesebb az, hogy ez igaznak bizonyult, ami hosszú nyomozásra adott okot. Szó lesz a VMware-en futó SQL szerverekről.

Igen, a régi laptopom többszörösen erősebb, mint a termelési szervere.

Valójában könnyű megbizonyosodni arról, hogy az éles szerver reménytelenül a laptop mögött van. Futtassa le a kódot (nem a tempdb-n és nem olyan adatbázison, amelyen engedélyezve van a késleltetett tartósság):

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

Az én asztalomon ez 5 másodpercet vesz igénybe, az éles szerveren pedig 28 másodpercet. Mivel az SQL-nek meg kell várnia a tranzakciós napló bejegyzésének fizikai végét, és itt nagyon rövid tranzakciókat hajtunk végre. Nagyjából egy nagy, erős teherautóval hajtottunk be a városi forgalomba, és néztük, ahogy rohamosan megelőzik a rolleres pizzafutárok – itt nem az áteresztőképesség a fontos, csak a késleltetés. A helyi SSD-t pedig egyetlen hálózati tárhely sem tudja megverni késleltetésben, akárhány nulla van az árában.

(a kommentekben kiderült, hogy hazudtam - mindkét helyen késleltetett tartósság volt. Késleltetett tartósság nélkül kiderül:
Asztali számítógép – 39 másodperc, 15K tr/s, 0.065 ms /io oda-vissza
PROD – 360 másodperc, 1600 tr/s, 0.6 ms
Észre kellett volna vennem, hogy túl gyors volt)

Ebben az esetben azonban a Riemann-zéta-függvény triviális nulláival van dolgunk egy triviális példával. Abban a példában, amit a fejlesztők hoztak nekem, ez más volt. Meggyőződésem, hogy igazuk van, és elkezdtem kivenni a példából minden üzleti logikával kapcsolatos sajátosságukat. Valamikor rájöttem, hogy teljesen kidobhatom a kódjukat, és megírhatom a sajátomat - ami ugyanezt a problémát mutatja - termelésben 3-4-szer lassabban fut:

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

Ha minden rendben van, akkor egy szám elsődlegességének ellenőrzése 6-7-8 másodpercet vesz igénybe. Ez több szerveren is megtörtént. De egyeseknél az ellenőrzés 25-40 másodpercig tartott. Érdekes módon nem volt olyan szerver, ahol a végrehajtás mondjuk 14 másodpercet vesz igénybe - a kód vagy nagyon gyorsan, vagy nagyon lassan működött, vagyis mondjuk fekete-fehér volt a probléma.

Mit tettem? Használt VMware mérőszámok. Ott minden rendben volt - rengeteg erőforrás volt, Ready time = 0, mindenből elég volt, a teszt során gyors és lassú szervereken is CPU = 100 egy vCPU-n. Vettem egy tesztet a Pi szám kiszámításához - a teszt ugyanazt az eredményt mutatta bármelyik szerveren. A fekete mágia illata egyre erősebb lett.

Miután megérkeztem a DEV farmhoz, elkezdtem játszani a szerverekkel. Kiderült, hogy a vMotion hostról hosztra képes „meggyógyítani” egy szervert, de egy „gyors” szervert is „lassúvá” alakíthat. Úgy tűnik, ez az – néhány házigazdának van problémája... de... nem. Néhány virtuális gép lassú volt az A gazdagépen, de gyorsan működött a B gazdagépen. Egy másik virtuális gép pedig éppen ellenkezőleg, gyorsan működött A-n, és lelassult B-n! A „gyors” és „lassú” gépek is gyakran pörögtek a gazdagépen!

Ettől a pillanattól kezdve kifejezetten kénszag volt a levegőben. Végül is a probléma nem tulajdonítható a virtuális gépnek (például Windows javítások) - végül is „gyors” lett a vMotion segítségével. De a probléma nem tudható be a gazdagépnek - elvégre lehet „gyors” és „lassú” gépe is. Ezenkívül ez nem a terheléshez kapcsolódott - sikerült egy „lassú” gépet szereznem a gazdagépen, ahol rajta kívül nem volt semmi.

Kétségbeesésből elindítottam a Process Explorert a Sysinternalsból, és megnéztem az SQL veremét. Lassú gépeken azonnal megakadt a szemem:

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

Ez már volt valami. A program így íródott:

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

Ez a program még kifejezettebb lassulást mutatott - a „gyors” gépeken 16-18 millió, míg a lassú gépeken másfél milliót, vagy akár 700 ezret mutat. Vagyis 10-20-szoros (!!!) a különbség. Ez már kis győzelem volt: mindenesetre nem fenyegetett az a veszély, hogy elakad a Microsoft és a VMware támogatása, hogy egymás felé fordítsák a nyilakat.

Aztán megállt a haladás – nyaralások, fontos ügyek, vírusos hisztéria és meredeken megnövekedett munkateher. Gyakran említettem a varázslatos problémát kollégáimnak, de időnként úgy tűnt, hogy nem is mindig hisznek nekem – túl szörnyű volt az a kijelentés, hogy a VMware 10-20-szor lassítja le a kódot.

Megpróbáltam kiásni magamból, ami lassít. Időnként úgy tűnt, hogy megtaláltam a megoldást – a Hot plug ki- és bekapcsolása, a memória mennyiségének vagy a processzorok számának változtatása gyakran „gyorsra” tette a gépet. De nem örökre. De ami igaznak bizonyult, az az, hogy elég kimenni a kormányra kopogtatni – vagyis átöltözni bármilyen virtuális gép paraméter

Végül amerikai kollégáim hirtelen megtalálták a kiváltó okot.

Igen, a régi laptopom többszörösen erősebb, mint a termelési szervere.

Gyakoriságban különböztek a házigazdák!

  • Általános szabály, hogy ez nem nagy baj. De: amikor egy „natív” gazdagépről „más” frekvenciájú gazdagépre vált, a VMware-nek módosítania kell a GetTimePrecise eredményt.
  • Ez általában nem probléma, hacsak nincs olyan alkalmazás, amely másodpercenként milliószor kéri le a pontos időt, például az SQL szerver.
  • De ez nem ijesztő, mivel az SQL szerver nem mindig teszi ezt (lásd a következtetést)

De vannak esetek, amikor ez a gereblye erősen üt. És mégis, igen, a kerék megérintésével (a virtuális gép beállításaiban valamit megváltoztatva) kényszerítettem a VMware-t a konfiguráció „újraszámítására”, és az aktuális gazdagép frekvenciája lett a gép „natív” frekvenciája.

döntés

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

Ha letiltja a TSC virtualizációját, a TSC virtuális gépen belüli olvasása visszaadja a fizikai gép TSC értékét, és a TSC virtuális gépen belüli írása nincs hatással. A virtuális gép áttelepítése egy másik gazdagépre, a felfüggesztett állapotból való visszaállítás vagy a pillanatképhez való visszatérés a TSC megszakításait okozza. Egyes vendég operációs rendszerek nem indulnak el, vagy egyéb időzítési problémákat mutatnak, ha a TSC virtualizáció le van tiltva. A múltban ezt a funkciót néha a TSC-t gyakran olvasó alkalmazások teljesítményének javítására javasolták, de a virtuális TSC teljesítménye jelentősen javult a jelenlegi termékekben. A funkciót olyan mérések végzésekor is javasolták használni, amelyek pontos valós idejű forrást igényelnek a virtuális gépen.

Röviden: hozzá kell adni a paramétert

monitor_control.virtual_rdtsc = HAMIS

Következtetés

Valószínűleg felmerül a kérdés: miért hívja meg az SQL olyan gyakran a GetTimePrecise-t?

Nincs meg az SQL szerver forráskódja, de a logika ezt mondja. Az SQL szinte egy kooperatív párhuzamossággal működő operációs rendszer, ahol minden szálnak időről időre „meg kell adnia magát”. Hol van erre a legjobb hely? Ahol természetes várakozás van - zár vagy IO. Oké, de mi van, ha számítási hurkokat forgatunk? Ekkor a kézenfekvő és szinte egyetlen hely az értelmezőben van (ez valójában nem interpreter), a következő utasítás végrehajtása után.

Általában az SQL szervert nem használják pusztán számítástechnikai szögelésre, és ez nem probléma. Azok a ciklusok azonban, amelyek mindenféle ideiglenes táblával működnek (amelyek azonnal gyorsítótárazottak), a kódot nagyon gyorsan végrehajtott utasítások sorozatává változtatják.

Amúgy ha NATIVELY COMILED-be csomagolod a függvényt, akkor leáll az időkérés, és a sebessége 10-szeresére nő.Mi a helyzet a kooperatív multitasking-el? A natívan lefordított kódhoz azonban PREEMPTIVE MULTITASKING-t kellett végrehajtanunk SQL-ben.

Forrás: will.com

Hozzászólás