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

Ho sentito esattamente queste lamentele dai nostri sviluppatori. La cosa interessante è che si è rivelata vera, dando inizio a un lungo processo di indagine. Parleremo dei server SQL, che girano su VMware.

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

In effetti, è facile fare in modo che il server di produzione rimanga irrimediabilmente indietro rispetto a un laptop. Esegui (non su tempdb e non su un database con Delayed Durability attivato) il codice:

set nocount on
create table _t (v varchar(100))
declare @n int=300000
while @n>0 begin 
  insert into _t select 'Che lento!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

Sul mio desktop impiega 5 secondi, mentre sul server di produzione ci vogliono 28 secondi. Perché SQL deve attendere la completa registrazione fisica nel transaction log, mentre noi stiamo eseguendo transazioni molto brevi. In parole semplici, abbiamo portato un grande camion potente nel traffico cittadino e osserviamo come i fattorini della pizza sulle moto lo sorpassano agilmente: qui non conta la larghezza di banda, ma solo la latenza. E nessuno storage di rete, per quanto possa costare, potrà mai vincere in latenza rispetto a un SSD locale.

(nei commenti è emerso che ho mentito — in entrambi i posti c'era del delayed durability. Senza delayed durability otteniamo:
Desktop — 39 secondi, 15K tr/sec, 0.065ms /io roundtrip
PROD — 360 secondi, 1600 tr/sec, 0.6ms
Avrei dovuto prestare attenzione, era troppo veloce)

Tuttavia, in questo caso ci troviamo di fronte a zeri banali della funzione di Riemann con un esempio banale. Nell'esempio che mi hanno presentato gli sviluppatori, ce n'era un altro. Ho verificato che avevano ragione e ho cominciato a ripulire l'esempio da tutta la loro specificità legata alla logica aziendale. A un certo punto ho capito che potevo buttar via completamente il loro codice e scrivere il mio — che dimostrava lo stesso problema — si esegue in produzione da 3 a 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 è a posto, il controllo della primalità di un numero verrà eseguito in 6-7-8 secondi. Così è stato in alcuni casi. server. Ma in alcuni altri il controllo richiedeva 25-40 secondi. È interessante notare che non c'erano server in cui l'esecuzione richiedesse, diciamo, 14 secondi: il codice funzionava molto rapidamente oppure molto lentamente, quindi il problema era, per così dire, in bianco e nero.

Cosa ho fatto? Sono andato a controllare le metriche di VMware. Tutto sembrava a posto: c'era un abbondanza di risorse, il tempo di attesa = 0, c'era tutto il necessario, sia nei server veloci che lenti, CPU=100 su un vCPU. Ho eseguito un test sul calcolo di Pi — il test mostrava risultati identici su tutti i server. Vento di magia nera.

Uscito nel data center di sviluppo, ho iniziato a giocare con i server. Ho scoperto che vMotion da host a host può 'guarire' un server, ma può anche trasformare un server 'veloce' in 'lento'. Sembrava proprio così — alcuni host avevano problemi... ma... no. Una macchina virtuale rallentava su un host, chiamiamolo A, ma funzionava velocemente su un host B. E un'altra macchina virtuale al contrario funzionava rapidamente su A e rallentava su B! Su un host giravano spesso sia macchine 'veloci' che 'lente'!

Da quel momento, nell'aria si sentiva chiaramente odore di zolfo. Infatti, il problema non poteva essere attribuito a nessuna macchina virtuale (aggiornamenti di Windows, per esempio) — poiché diventava 'veloce' durante vMotion. Ma non poteva nemmeno essere attribuito all'host — visto che su di esso potevano esserci sia macchine 'veloci' che 'lente'. Inoltre, non era legato al carico — sono riuscito a ottenere una macchina 'lenta' su un host dove non c'era null'altro.

Dalla frustrazione, ho avviato Process Explorer di Sysinternals e ho guardato lo stack SQL. Sulle macchine lente, mi è subito saltata agli occhi la riga:

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. Era stata scritta un programma:

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

Questo programma ha mostrato un rallentamento ancora più evidente: su macchine "veloci" mostra 16-18 milioni di cicli al secondo, mentre su quelle lente un milione e mezzo o addirittura 700 mila. La differenza è quindi di 10-20 volte (!!!). Questo è stato già un piccolo successo: almeno non c'era la minaccia di rimanere bloccati tra il supporto di Microsoft e VMware, così da indirizzare le responsabilità l'uno sull'altro.

Poi il progresso si è fermato: ferie, questioni importanti, isteria virale e un aumento improvviso del carico. Ho spesso accennato al problema magico ai colleghi, ma a volte sembrava che non mi credessero, poiché affermare che VMware rallenta il codice 10-20 volte sembrava troppo mostruoso.

Ho cercato di scoprire cosa stesse causando il rallentamento. A volte mi sembrava di aver trovato la soluzione: accendere e spegnere i Hot plugs, modificare la quantità di memoria o il numero di processori spesso rendeva la macchina "veloce". Ma non per sempre. Quello che si è rivelato vero è che basta uscire e dare un colpetto alla ruota, cioè cambiare da chiunque il parametro della macchina virtuale.

Finalmente, i miei colleghi americani hanno improvvisamente trovato la causa principale.

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

I host differivano nella frequenza!

  • Di solito, non è un grosso problema. Ma: quando si migra da un host "nativo" a uno con una frequenza "diversa", VMware deve correggere il risultato di GetTimePrecise.
  • In genere non è un grosso problema, a meno che non ci sia un'applicazione che richiede un orario preciso milioni di volte al secondo, come SQL Server.
  • Ma neanche questo è un grosso problema, poiché SQL Server non lo fa di certo sempre (vedi Conclusione).

Ma ci sono casi in cui questi problemi possono essere molto dolorosi. E sì, battendo sulla ruota (cambiando qualcosa nelle impostazioni della VM) costringivo VMware a ‘ricalcolare’ la configurazione, e la frequenza dell'host attuale diventava la frequenza "nativa" della macchina.

Soluzione

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

Quando disabilitiamo 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. Migrando la macchina virtuale su un altro host, riprendendola da uno stato sospeso o tornando a uno snapshot causa un salto discontinuo del TSC. Alcuni sistemi operativi guest non riescono ad avviarsi o mostrano altri problemi di sincronizzazione temporale quando la virtualizzazione del TSC è disabilitata. In passato, questa funzionalità è stata talvolta raccomandata 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 funzionalità è stata anche raccomandata per l'uso durante la misurazione che richiede una fonte precisa di tempo reale nella macchina virtuale.

In breve, è necessario aggiungere il parametro

monitor_control.virtual_rdtsc = FALSE

Conclusione

Sicuramente vi sarete chiesti: perché chiamare GetTimePrecise così frequentemente in SQL?

Non ho il codice sorgente del server SQL, ma la logica suggerisce che. SQL è quasi un sistema operativo con concorrenza cooperativa, dove ogni thread deve, di tanto in tanto, "cedere". E dove è meglio farlo? Lì dove ci sono attese naturali — lock o IO. Bene, e se stiamo eseguendo cicli computazionali? Allora il luogo ovvio e quasi unico è nell'interprete (che non è proprio un interprete), dopo l'esecuzione di un operatore.

In genere, il server SQL non viene utilizzato per le semplici operazioni di calcolo e questo non è un problema. Ma i cicli che lavorano con varie tabelle temporanee (che vengono immediatamente memorizzate nella cache) trasformano il codice in una sequenza di operatori molto rapidamente eseguibili.

A proposito, se si avvolge la funzione in NATIVELY COMPILED, essa smette di richiedere il tempo e la sua velocità aumenta di 10 volte. E la concorrenza cooperativa? Beh, per il codice nativamente compilato è stato necessario implementare in SQL la PREEMPTIVE MULTITASKING.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster