Ja, min gamle bærbare datamaskin er flere ganger kraftigere enn produksjonsserveren din.

Dette er akkurat de klagene jeg har hørt fra utviklerne våre. Det mest interessante er at dette viste seg å være sant, noe som førte til en lang etterforskning. Vi vil snakke om SQL-servere som kjører på VMware.

Ja, min gamle bærbare datamaskin er flere ganger kraftigere enn produksjonsserveren din.

Faktisk er det enkelt å sikre at produksjonsserveren er håpløst bak den bærbare datamaskinen. Kjør (ikke på tempdb og ikke på en database med Delayed Durability aktivert) koden:

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

På skrivebordet mitt tar det 5 sekunder, og på produksjonsserveren tar det 28 sekunder. Fordi SQL må vente på den fysiske slutten av transaksjonsloggoppføringen, og vi gjør veldig korte transaksjoner her. Grovt sett kjørte vi en stor, kraftig lastebil inn i bytrafikken, og så på at den ble overkjørt av pizzabud på sparkesykkel – gjennomstrømming er ikke viktig her, kun latens er viktig. Og ingen nettverkslagring, uansett hvor mange nuller det er i prisen, kan slå den lokale SSD-en når det gjelder ventetid.

(i kommentarfeltet viste det seg at jeg løy - jeg hadde forsinket holdbarhet begge steder. Uten forsinket holdbarhet viser det seg:
Desktop - 39 sekunder, 15K tr/sek, 0.065ms /io tur/retur
PROD - 360 sekunder, 1600 tr/sek, 0.6ms
Jeg burde ha lagt merke til at det var for fort)

I dette tilfellet har vi imidlertid å gjøre med trivielle nuller av Riemann zeta-funksjonen med et trivielt eksempel. I eksemplet som utviklerne tok med meg, var det annerledes. Jeg var overbevist om at de hadde rett, og begynte å fjerne alle detaljer knyttet til forretningslogikk fra eksemplet. På et tidspunkt skjønte jeg at jeg kunne kaste koden deres fullstendig og skrive min egen - som demonstrerer det samme problemet - i produksjon går den 3-4 ganger saktere:

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

Hvis alt er bra, vil det ta 6-7-8 sekunder å sjekke primaliteten til et tall. Dette skjedde på en rekke servere. Men på noen tok kontrollen 25-40 sekunder. Interessant nok var det ingen servere hvor kjøringen ville ta, for eksempel, 14 sekunder - koden fungerte enten veldig raskt eller veldig sakte, det vil si at problemet var, la oss si, svart-hvitt.

Hva jeg har gjort? Brukte VMware-målinger. Alt var bra der - det var en overflod av ressurser, klar tid = 0, det var nok av alt, under testen på både raske og trege servere CPU = 100 på en vCPU. Jeg tok en test for å beregne tallet Pi - testen viste de samme resultatene på hvilken som helst server. Lukten av svart magi ble sterkere og sterkere.

Da jeg kom til DEV-farmen, begynte jeg å leke med serverne. Det viste seg at vMotion fra vert til vert kan "kurere" en server, men det kan også gjøre en "rask" server til en "langsom". Det virker som dette er det - noen verter har et problem... men... nei. En eller annen virtuell maskin var treg på vert, si A, men fungerte raskt på vert B. Og en annen virtuell maskin, tvert imot, jobbet raskt på A og bremset ned på B! Både "raske" og "sakte" maskiner snurret ofte på verten!

Fra det øyeblikket var det en tydelig lukt av svovel i luften. Tross alt kunne ikke problemet tilskrives den virtuelle maskinen (for eksempel Windows-oppdateringer) - det ble tross alt "raskt" med vMotion. Men problemet kunne heller ikke tilskrives verten - den kan tross alt ha både "raske" og "langsomme" maskiner. Dette var heller ikke relatert til belastningen - jeg klarte å få en "treg" maskin på verten, der det ikke var noe annet enn den.

Av desperasjon startet jeg Process Explorer fra Sysinternals og så på SQL-stakken. På trege maskiner fanget linjen mitt øye umiddelbart:

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

Dette var allerede noe. Programmet ble skrevet:

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

Dette programmet demonstrerte en enda mer uttalt nedgang - på "raske" maskiner viser det 16-18 millioner sykluser per sekund, mens det på trege maskiner viser en og en halv million, eller til og med 700 tusen. Det vil si at forskjellen er 10-20 ganger (!!!). Dette var allerede en liten seier: i alle fall var det ingen trussel om å sette seg fast mellom Microsoft og VMware-støtte slik at de skulle snu piler mot hverandre.

Så stoppet fremgangen - ferier, viktige saker, viralt hysteri og en kraftig økning i arbeidsmengden. Jeg nevnte ofte det magiske problemet for kollegene mine, men til tider så det ut til at de ikke engang alltid trodde meg - påstanden om at VMware bremser koden 10-20 ganger var for monstrøs.

Jeg prøvde selv å grave frem det som bremset meg. Noen ganger virket det for meg som om jeg hadde funnet en løsning - å slå Hot Plugs på og av, endre mengden minne eller antall prosessorer gjorde ofte maskinen til en "rask" en. Men ikke for alltid. Men det som viste seg å stemme er at det er nok å gå ut og banke på rattet – altså bytte noen virtuell maskin parameter

Til slutt fant mine amerikanske kolleger plutselig årsaken.

Ja, min gamle bærbare datamaskin er flere ganger kraftigere enn produksjonsserveren din.

Vertene var forskjellige i frekvens!

  • Som regel er ikke dette en stor sak. Men: når du flytter fra en "innfødt" vert til en vert med en "annen" frekvens, må VMware justere GetTimePrecise-resultatet.
  • Som regel er dette ikke et problem, med mindre det er en applikasjon som ber om den nøyaktige tiden millioner av ganger per sekund, som SQL-server.
  • Men dette er ikke skummelt, siden SQL-server ikke alltid gjør dette (se konklusjon)

Men det er tilfeller når denne raken slår hardt. Og likevel, ja, ved å trykke på hjulet (ved å endre noe i VM-innstillingene) tvang jeg VMware til å 'rekalkulere' konfigurasjonen, og frekvensen til den nåværende verten ble den 'native' frekvensen til maskinen.

beslutning

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

Når du deaktiverer virtualisering av TSC, vil lesing av TSC fra den virtuelle maskinen returnere den fysiske maskinens TSC-verdi, og å skrive TSC fra den virtuelle maskinen har ingen effekt. Å migrere den virtuelle maskinen til en annen vert, gjenoppta den fra suspendert tilstand eller gå tilbake til et øyeblikksbilde får TSC til å hoppe diskontinuerlig. Noen gjesteoperativsystemer klarer ikke å starte opp, eller viser andre tidtakingsproblemer, når TSC-virtualisering er deaktivert. Tidligere har denne funksjonen noen ganger blitt anbefalt for å forbedre ytelsen til applikasjoner som leser TSC ofte, men ytelsen til den virtuelle TSC har blitt betydelig forbedret i nåværende produkter. Funksjonen er også anbefalt for bruk når du utfører målinger som krever en presis kilde til sanntid i den virtuelle maskinen.

Kort sagt, du må legge til parameteren

monitor_control.virtual_rdtsc = FALSE

Konklusjon

Du har sannsynligvis et spørsmål: hvorfor kaller SQL GetTimePrecise så ofte?

Jeg har ikke SQL-serverens kildekode, men logikken sier dette. SQL er nærmest et operativsystem med samarbeidende samtidighet, der hver tråd må "gi etter" fra tid til annen. Hvor er det beste stedet å gjøre dette? Der det er naturlig ventetid - lås eller IO. Ok, men hva om vi spinner beregningsløkker? Da er det åpenbare og nesten eneste stedet i tolken (dette er egentlig ikke en tolk), etter å ha utført neste setning.

Vanligvis brukes ikke SQL-server til ren dataspikring, og dette er ikke et problem. Men løkker som fungerer med alle slags midlertidige tabeller (som umiddelbart bufres) gjør koden om til en sekvens av meget raskt utførte setninger.

Forresten, hvis du pakker funksjonen inn i NATIVELY COMPILED, så slutter den å spørre om tid, og hastigheten øker med 10 ganger Hva med samarbeidende multitasking? Men for naturlig kompilert kode måtte vi gjøre PREEMPTIVE MULTITASKING i SQL.

Kilde: www.habr.com

Legg til en kommentar