Sì, il mio vecchio laptop è molte volte più potente del tuo server di produzione.

Queste sono le affermazioni che ho sentito dai nostri sviluppatori. La cosa più interessante è che questo si è rivelato vero, dando vita a una lunga indagine. Parleremo di server SQL in esecuzione su VMware.

Sì, il mio vecchio laptop è molte volte più potente del tuo server di produzione.

In realtà, ottenere il server di produzione senza speranza dietro il laptop è facile. Esegui (non su tempdb e non su un database con Delayed Durability abilitato) il codice:

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

Ci vogliono 5 secondi sul mio desktop e 28 secondi sul server di produzione. Perché SQL deve attendere la fine fisica della scrittura nel registro delle transazioni e qui stiamo eseguendo transazioni molto brevi. In parole povere, abbiamo guidato un camion grande e potente nel traffico cittadino e stiamo osservando come le persone che consegnano la pizza sugli scooter lo stanno notoriamente sorpassando: il rendimento non è importante qui, solo la latenza è importante. E non un singolo storage di rete, non importa quanti zeri ci siano nel suo prezzo, sarà in grado di superare un SSD locale in termini di latenza.

(nei commenti si è scoperto che ho mentito: avevo ritardato la durabilità in entrambi i punti. Senza ritardata durabilità risulta:
Desktop: 39 secondi, 15K tr/sec, 0.065ms/io andata e ritorno
PROD - 360 secondi, 1600 tr/sec, 0.6 ms
Avrei dovuto notare che è troppo veloce)

Tuttavia, in questo caso abbiamo a che fare con zeri banali della funzione zeta di Riemann con un esempio banale. Nell'esempio che mi hanno portato gli sviluppatori, era diverso. Ero convinto che avessero ragione e ho iniziato a ripulire dall'esempio tutte le loro specifiche relative alla logica aziendale. Ad un certo punto, mi sono reso conto che potevo buttare via completamente il loro codice e scrivere il mio - che dimostra lo stesso problema - in produzione funziona 3-4 volte più lentamente:

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

Se tutto va bene per te, il controllo della semplicità di un numero richiederà 6-7-8 secondi. Questo è successo su un certo numero di server. Ma su alcuni, il controllo ha richiesto 25-40 secondi. È interessante notare che non c'erano server in cui l'esecuzione avrebbe richiesto, diciamo, 14 secondi: il codice funzionava molto velocemente o molto lentamente, ovvero il problema era, diciamo, in bianco e nero.

Quello che ho fatto? Sono entrato nelle metriche VMware. Lì andava tutto bene: c'erano molte risorse, Tempo di preparazione = 0, ce n'era abbastanza di tutto, durante il test sia su server veloci che lenti CPU = 100 su una vCPU. Ho fatto un test per calcolare il numero di Pi: il test ha mostrato gli stessi risultati su tutti i server. L'odore della magia nera diventava sempre più forte.

Dopo essere uscito nella DEV farm, ho iniziato a giocare con i server. Si è scoperto che vMotion da host a host può "curare" un server, ma può anche trasformare un server "veloce" in uno "lento". Sembra che sia così: alcuni host hanno un problema ... ma ... no. Alcune macchine virtuali hanno rallentato sull'host, ad esempio A, ma hanno funzionato rapidamente sull'host B. E l'altra macchina virtuale, al contrario, ha funzionato velocemente su A e ha rallentato su B! Sia le auto "veloci" che quelle "lente" giravano spesso sull'host!

Da quel momento nell'aria si diffuse un netto odore di zolfo. Dopotutto, il problema non può essere attribuito a nessuna macchina virtuale (patch di Windows, ad esempio) - dopotutto, si è trasformato in uno "veloce" con vMotion. Ma anche il problema non può essere attribuito all'host: dopotutto, potrebbe avere sia macchine "veloci" che "lente". Inoltre, non era correlato al carico: sono riuscito a ottenere una macchina "lenta" sull'host, dove non c'era nient'altro oltre a essa.

Spinto dalla disperazione, ho avviato Process Explorer di Sysinternals e ho esaminato lo stack SQL. Sulle macchine lente, la linea ha immediatamente attirato la mia attenzione:

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

Era già qualcosa. Il programma è stato scritto:

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

Questo programma ha mostrato un rallentamento ancora più pronunciato: su macchine "veloci" mostra 16-18 milioni di cicli al secondo, mentre su macchine lente - un milione e mezzo, o addirittura 700mila. Cioè, la differenza è 10-20 volte (!!!). Questa era già una piccola vittoria: in ogni caso, non c'era alcuna minaccia di rimanere bloccati tra il supporto Microsoft e VMware in modo che si scambiassero le frecce.

Poi i progressi si sono fermati: vacanze, cose importanti, isteria virale e un forte aumento del carico di lavoro. Ho spesso menzionato il problema magico ai colleghi, ma a volte sembrava che non mi credessero nemmeno sempre: l'affermazione secondo cui VMware ha rallentato il codice di 10-20 volte era troppo mostruosa.

Ho cercato di scoprire da solo cosa lo rallenta. A volte mi sembrava di aver trovato una soluzione: accendere e spegnere gli hot plug, modificare la quantità di memoria o il numero di processori spesso trasformava la macchina in una "veloce". Ma non per sempre. Ma quello che si è rivelato vero è che basta uscire e bussare alla ruota, cioè cambiare qualsiasi parametro della macchina virtuale

Alla fine, i miei colleghi americani hanno improvvisamente trovato una causa principale.

Sì, il mio vecchio laptop è molte volte più potente del tuo server di produzione.

Gli host differivano per frequenza!

  • Di norma, questo non fa paura. Ma: quando si passa da un host "nativo" a un host con una frequenza "diversa", VMware deve regolare il risultato GetTimePrecise.
  • Di norma, questo non è un problema, a meno che non ci sia un'applicazione che richieda l'ora esatta milioni di volte al secondo, come SQL Server.
  • Ma neanche questo è spaventoso, poiché SQL server non sempre lo fa (vedi Conclusione)

Ma ci sono casi in cui questo rastrello fa male. E sì, bussando alla ruota (cambiando qualcosa nelle impostazioni della VM), ho costretto VMware a "ricalcolare" la configurazione e la frequenza dell'host corrente è diventata la frequenza "nativa" della macchina.

Soluzione

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

Quando si disabilita la virtualizzazione del TSC, la lettura del TSC dall'interno della macchina virtuale restituisce il valore TSC della macchina fisica e la scrittura del TSC dall'interno della macchina virtuale non ha alcun effetto. La migrazione della macchina virtuale su un altro host, la sua ripresa dallo stato sospeso o il ripristino di uno snapshot provoca un salto discontinuo del TSC. Alcuni sistemi operativi guest non si avviano o presentano altri problemi di cronometraggio quando la virtualizzazione TSC è disabilitata. In passato, questa funzione è stata talvolta consigliata per migliorare le prestazioni delle applicazioni che leggono frequentemente il TSC, ma le prestazioni del TSC virtuale sono state notevolmente migliorate nei prodotti attuali. La funzione è stata consigliata anche per l'uso durante l'esecuzione di misurazioni che richiedono una fonte precisa di tempo reale nella macchina virtuale.

In breve, è necessario aggiungere il parametro

monitor_control.virtual_rdtsc = FALSO

conclusione

Probabilmente hai una domanda: perché SQL dovrebbe chiamare GetTimePrecise così spesso?

Non ho le fonti del server SQL, ma la logica dice questo. SQL è quasi un sistema operativo con concorrenza cooperativa, in cui ogni thread deve "cedere" di volta in volta. Qual è il posto migliore per farlo? Dove c'è un'aspettativa naturale: blocco o IO. Ok, ma cosa succede se stiamo girando cicli computazionali? Quindi l'ovvio e quasi l'unico posto è nell'interprete (questo non è proprio un interprete), dopo l'esecuzione dell'operatore successivo.

Di norma, il server SQL non viene utilizzato per il puro calcolo e questo non è un problema. Ma i cicli con il lavoro con tutti i tipi di tabelle temporanee (che vengono immediatamente memorizzate nella cache) trasformano il codice in una sequenza di istruzioni eseguite molto rapidamente.

A proposito, se la funzione è racchiusa in NATIVELY COMPILED, smette di richiedere il tempo e la sua velocità aumenta di 10 volte, ma per quanto riguarda il multitasking cooperativo? Ma per il codice compilato in modo nativo, ho dovuto eseguire PREEMPTIVE MULTITASKING in SQL.

Fonte: habr.com

Aggiungi un commento