Ja, mijn oude laptop is vele malen krachtiger dan uw productieserver.

Dit zijn precies de klachten die ik van onze ontwikkelaars heb gehoord. Het meest interessante is dat dit waar bleek te zijn, wat aanleiding gaf tot een lang onderzoek. We zullen het hebben over SQL-servers die op VMware draaien.

Ja, mijn oude laptop is vele malen krachtiger dan uw productieserver.

Eigenlijk is het makkelijk om ervoor te zorgen dat de productieserver hopeloos achter de laptop staat. Voer de code uit (niet op tempdb en niet op een database met Delayed Durability ingeschakeld):

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

Op mijn desktop duurt het 5 seconden en op de productieserver 28 seconden. Omdat SQL moet wachten op het fysieke einde van de transactieloginvoer, en we hier zeer korte transacties uitvoeren. Grof gezegd reden we met een grote, krachtige vrachtwagen het stadsverkeer in en keken hoe deze onstuimig werd ingehaald door pizzabezorgers op scooters - de doorvoersnelheid is hier niet belangrijk, alleen de latentie is belangrijk. En geen enkele netwerkopslag, hoeveel nullen er ook in de prijs zitten, kan de lokale SSD verslaan in termen van latentie.

(in de reacties bleek dat ik loog - ik had de duurzaamheid op beide plaatsen vertraagd. Zonder vertraagde duurzaamheid blijkt:
Desktop - 39 seconden, 15K tr/sec, 0.065 ms/io retour
PROD - 360 seconden, 1600 tr/sec, 0.6 ms
Ik had moeten merken dat het te snel was)

In dit geval hebben we echter te maken met triviale nullen van de Riemann-zetafunctie met een triviaal voorbeeld. In het voorbeeld dat de ontwikkelaars mij gaven, was het anders. Ik was ervan overtuigd dat ze gelijk hadden en begon al hun details met betrekking tot bedrijfslogica uit het voorbeeld te verwijderen. Op een gegeven moment realiseerde ik me dat ik hun code volledig kon weggooien en mijn eigen code kon schrijven - wat hetzelfde probleem aantoont - in productie draait het 3-4 keer langzamer:

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

Als alles in orde is, duurt het controleren van de primaliteit van een getal 6-7-8 seconden. Dit gebeurde op een aantal servers. Maar bij sommigen duurde de controle 25-40 seconden. Interessant genoeg waren er geen servers waar de uitvoering bijvoorbeeld 14 seconden zou duren - de code werkte heel snel of heel langzaam, dat wil zeggen dat het probleem, laten we zeggen, zwart-wit was.

Wat ik heb gedaan? Gebruikte VMware-statistieken. Alles was daar in orde - er was een overvloed aan bronnen, gereedtijd = 0, er was genoeg van alles, tijdens de test op zowel snelle als langzame servers CPU = 100 op één vCPU. Ik heb een test gedaan om het getal Pi te berekenen - de test liet op elke server dezelfde resultaten zien. De geur van zwarte magie werd sterker en sterker.

Toen ik eenmaal bij de DEV-boerderij was, begon ik met de servers te spelen. Het bleek dat vMotion van host naar host een server kan ‘genezen’, maar het kan ook een ‘snelle’ server in een ‘trage’ server veranderen. Het lijkt erop dat dit het is: sommige hosts hebben een probleem... maar... nee. Een virtuele machine was traag op host, bijvoorbeeld A, maar werkte snel op host B. En een andere virtuele machine werkte daarentegen snel op A en vertraagde op B! Zowel “snelle” als “langzame” machines draaiden vaak op de host!

Vanaf dat moment hing er een duidelijke geur van zwavel in de lucht. Het probleem kon immers niet worden toegeschreven aan de virtuele machine (bijvoorbeeld Windows-patches) - het werd tenslotte "snel" met vMotion. Maar het probleem kon ook niet aan de host worden toegeschreven - deze kon immers zowel "snelle" als "langzame" machines hebben. Dit had ook niets te maken met de belasting - het lukte me om een ​​​​"trage" machine op de host te krijgen, waar er verder helemaal niets was.

Uit wanhoop startte ik Process Explorer van Sysinternals en keek naar de SQL-stack. Op langzame machines viel de regel meteen op:

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

Dit was al iets. Het programma is geschreven:

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

Dit programma demonstreerde een nog meer uitgesproken vertraging: op "snelle" machines toont het 16-18 miljoen cycli per seconde, terwijl het op langzame machines anderhalf miljoen of zelfs 700 duizend laat zien. Dat wil zeggen, het verschil is 10-20 keer (!!!). Dit was al een kleine overwinning: er bestond in ieder geval geen dreiging dat we klem zouden komen te zitten tussen de ondersteuning van Microsoft en VMware, zodat ze de pijlen op elkaar zouden richten.

Toen stopte de vooruitgang: vakanties, belangrijke zaken, virale hysterie en een sterke toename van de werkdruk. Ik heb het magische probleem vaak tegen mijn collega's genoemd, maar soms leek het erop dat ze me niet eens altijd geloofden - de bewering dat VMware de code 10-20 keer vertraagt, was te monsterlijk.

Ik probeerde zelf uit te zoeken wat mij vertraagde. Soms leek het mij dat ik een oplossing had gevonden: het in- en uitschakelen van Hot Plugs, het veranderen van de hoeveelheid geheugen of het aantal processors maakte de machine vaak tot een "snelle" machine. Maar niet voor altijd. Maar wat waar bleek te zijn, is dat het voldoende is om op het stuur te kloppen - dat wil zeggen: verandering een virtuele machineparameter

Eindelijk vonden mijn Amerikaanse collega's plotseling de oorzaak.

Ja, mijn oude laptop is vele malen krachtiger dan uw productieserver.

De hosts verschilden in frequentie!

  • In de regel is dit geen probleem. Maar: bij de overstap van een 'native' host naar een host met een 'andere' frequentie moet VMware het GetTimePrecise resultaat aanpassen.
  • In de regel is dit geen probleem, tenzij er een applicatie is die miljoenen keren per seconde de exacte tijd opvraagt, zoals SQL Server.
  • Maar dit is niet eng, aangezien SQL-server dit niet altijd doet (zie conclusie)

Maar er zijn gevallen waarin deze hark hard toeslaat. En toch, ja, door op het stuur te tikken (door iets in de VM-instellingen te veranderen) dwong ik VMware om de configuratie te 'herberekenen', en de frequentie van de huidige host werd de 'native' frequentie van de machine.

beslissing

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

Wanneer u de virtualisatie van de TSC uitschakelt, retourneert het lezen van de TSC vanuit de virtuele machine de TSC-waarde van de fysieke machine en heeft het schrijven van de TSC vanuit de virtuele machine geen effect. Het migreren van de virtuele machine naar een andere host, het hervatten vanuit de onderbroken status of het terugkeren naar een momentopname zorgt ervoor dat de TSC discontinu springt. Sommige gastbesturingssystemen starten niet op of vertonen andere tijdwaarnemingsproblemen wanneer TSC-virtualisatie is uitgeschakeld. In het verleden werd deze functie soms aanbevolen om de prestaties te verbeteren van toepassingen die de TSC regelmatig lezen, maar de prestaties van de virtuele TSC zijn aanzienlijk verbeterd in de huidige producten. De functie is ook aanbevolen voor gebruik bij het uitvoeren van metingen waarvoor een nauwkeurige bron van realtime in de virtuele machine nodig is.

Kortom, u moet de parameter toevoegen

monitor_control.virtual_rdtsc = ONWAAR

Conclusie

U heeft waarschijnlijk een vraag: waarom roept SQL GetTimePrecise zo vaak aan?

Ik heb de broncode van de SQL-server niet, maar de logica zegt dit. SQL is bijna een besturingssysteem met coöperatieve gelijktijdigheid, waarbij elke thread van tijd tot tijd moet 'toegeven'. Wat is de beste plek om dit te doen? Waar er een natuurlijke wachttijd is - lock of IO. Oké, maar wat als we rekenlussen draaien? Dan is de voor de hand liggende en vrijwel enige plaats de tolk (dit is niet echt een tolk), na het uitvoeren van de volgende instructie.

Over het algemeen wordt de SQL-server niet gebruikt voor puur computerwerk en dit is geen probleem. Maar lussen die werken met allerlei tijdelijke tabellen (die onmiddellijk in de cache worden opgeslagen) veranderen de code in een reeks zeer snel uitgevoerde instructies.

Trouwens, als je de functie in NATIVELY COMPILED verpakt, stopt hij met het vragen om tijd en neemt de snelheid tien keer toe. Maar voor native gecompileerde code moesten we PREEMPTIVE MULTITASKING in SQL uitvoeren.

Bron: www.habr.com

Voeg een reactie