Si, o meu vello portátil é varias veces máis potente que o teu servidor de produción

Estas son exactamente as queixas que escoitei dos nosos desenvolvedores. O máis interesante é que isto resultou ser certo, dando lugar a unha longa investigación. Falaremos dos servidores SQL que se executan en VMware.

Si, o meu vello portátil é varias veces máis potente que o teu servidor de produción

En realidade, é fácil asegurarse de que o servidor de produción está irremediablemente detrás do portátil. Execute (non en tempdb nin nunha base de datos coa Durabilidade retardada activada) o código:

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

No meu escritorio leva 5 segundos, e no servidor de produción leva 28 segundos. Porque SQL debe esperar ao final físico da entrada do rexistro de transaccións, e aquí estamos facendo transaccións moi curtas. En liñas xerais, conducimos un camión grande e poderoso ata o tráfico da cidade e observamos como era superado por repartidores de pizza en scooters: o rendemento non é importante aquí, só é importante a latencia. E ningún almacenamento en rede, non importa cantos ceros haxa no seu prezo, pode superar o SSD local en termos de latencia.

(nos comentarios resultou que mentín - tiña unha durabilidade atrasada en ambos os lugares. Sen durabilidade atrasada resulta:
Escritorio: 39 segundos, 15K tr/seg, 0.065 ms/io ida e volta
PROD - 360 segundos, 1600 tr/seg, 0.6 ms
Debería ter notado que era demasiado rápido)

Porén, neste caso estamos a tratar con ceros triviais da función zeta de Riemann cun exemplo trivial. No exemplo que me trouxeron os desenvolvedores, era diferente. Estaba convencido de que tiñan razón, e comecei a eliminar do exemplo todas as súas particularidades relacionadas coa lóxica empresarial. Nalgún momento deime conta de que podía tirar completamente o seu código e escribir o meu propio, o que demostra o mesmo problema, na produción corre 3-4 veces máis lento:

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 todo está ben, comprobar a primalidade dun número levará 6-7-8 segundos. Isto ocorreu en varios servidores. Pero nalgúns, a comprobación levou 25-40 segundos. Curiosamente, non había servidores onde a execución levase, digamos, 14 segundos: o código funcionaba moi rápido ou moi lentamente, é dicir, o problema era, digamos, en branco e negro.

Que fixen? Métricas de VMware utilizadas. Todo estaba ben alí: había unha abundancia de recursos, tempo de preparación = 0, había suficiente de todo, durante a proba en servidores rápidos e lentos CPU = 100 nunha vCPU. Fixen unha proba para calcular o número Pi: a proba mostrou os mesmos resultados en calquera servidor. O cheiro da maxia negra facíase cada vez máis forte.

Unha vez que cheguei á granxa DEV, comecei a xogar cos servidores. Resultou que vMotion de host a host pode "curar" un servidor, pero tamén pode converter un servidor "rápido" nun servidor "lento". Parece que isto é: algúns anfitrións teñen un problema... pero... non. Algunha máquina virtual foi lenta no host, digamos A, pero funcionou rapidamente no host B. E outra máquina virtual, pola contra, funcionou rapidamente en A e diminuíu a velocidade en B! Tanto as máquinas "rápidas" como as "lentas" adoitaban xirar no host!

A partir dese momento, había un cheiro distinto a xofre no aire. Despois de todo, o problema non se puido atribuír á máquina virtual (parches de Windows, por exemplo) - despois de todo, converteuse en "rápido" con vMotion. Pero o problema tampouco se puido atribuír ao host; despois de todo, podería ter máquinas "rápidas" e "lentas". Ademais, isto non estaba relacionado coa carga: conseguín conseguir unha máquina "lenta" no host, onde non había nada ademais del.

Por desesperación, lancei Process Explorer desde Sysinternals e mirei a pila SQL. Nas máquinas lentas a liña chamoume inmediatamente a atención:

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

Isto xa era algo. O programa estaba escrito:

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

Este programa demostrou unha desaceleración aínda máis pronunciada: en máquinas "rápidas" mostra 16-18 millóns de ciclos por segundo, mentres que en máquinas lentas mostra un millón e medio, ou mesmo 700 mil. É dicir, a diferenza é de 10-20 veces (!!!). Esta xa era unha pequena vitoria: en calquera caso, non había ningunha ameaza de quedar atrapado entre o soporte de Microsoft e VMware para que se viran frechas entre si.

Entón o progreso detívose: vacacións, asuntos importantes, histeria viral e un forte aumento da carga de traballo. Moitas veces mencionei o problema máxico aos meus compañeiros, pero ás veces parecía que nin sequera me crían: a afirmación de que VMware ralentiza o código entre 10 e 20 veces era demasiado monstruosa.

Intentei desenterrarme o que me ralentizaba. Ás veces pareceume que atopara unha solución: activar e desactivar os hot plugs, cambiar a cantidade de memoria ou o número de procesadores moitas veces converteu a máquina nunha "rápida". Pero non para sempre. Pero o que resultou ser certo é que abonda con saír e bater na roda, é dicir, cambiar calquera parámetro de máquina virtual

Finalmente, os meus colegas estadounidenses atoparon de súpeto a causa raíz.

Si, o meu vello portátil é varias veces máis potente que o teu servidor de produción

Os anfitrións diferían en frecuencia!

  • Como regra xeral, isto non é un gran problema. Pero: ao pasar dun host "nativo" a un host cunha frecuencia "diferente", VMware debe axustar o resultado de GetTimePrecise.
  • Como regra xeral, isto non é un problema, a non ser que haxa unha aplicación que solicite a hora exacta millóns de veces por segundo, como o servidor SQL.
  • Pero isto non dá medo, xa que o servidor SQL non sempre fai isto (ver Conclusión)

Pero hai casos nos que este anciño golpea con forza. E aínda así, si, ao tocar a roda (ao cambiar algo na configuración da máquina virtual) obriguei a VMware a "recalcular" a configuración e a frecuencia do servidor actual converteuse na frecuencia "nativa" da máquina.

decisión

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

Cando desactivas a virtualización do TSC, a lectura do TSC desde a máquina virtual devolve o valor TSC da máquina física e escribir o TSC desde a máquina virtual non ten ningún efecto. Migrar a máquina virtual a outro host, retomala desde o estado suspendido ou volver a unha instantánea fai que o TSC salte de forma descontinua. Algúns sistemas operativos convidados non se inician ou presentan outros problemas de cronometraxe cando a virtualización TSC está desactivada. No pasado, esta función recomendábase ás veces para mellorar o rendemento das aplicacións que len o TSC con frecuencia, pero o rendemento do TSC virtual mellorouse substancialmente nos produtos actuais. Tamén se recomendou a función cando se realizan medicións que requiren unha fonte precisa de tempo real na máquina virtual.

En resumo, cómpre engadir o parámetro

monitor_control.virtual_rdtsc = FALSO

Conclusión

Probablemente teñas unha pregunta: por que SQL chama a GetTimePrecise con tanta frecuencia?

Non teño o código fonte do servidor SQL, pero a lóxica di isto. SQL é case un sistema operativo con concorrencia cooperativa, onde cada fío debe "ceder" de cando en vez. Onde é o mellor lugar para facelo? Onde hai unha espera natural: bloqueo ou IO. Está ben, pero e se estamos a xirar bucles computacionais? Entón o lugar obvio e case único está no intérprete (este non é realmente un intérprete), despois de executar a seguinte instrución.

Xeralmente, o servidor SQL non se usa para cravar informático puro e isto non é un problema. Pero os bucles que funcionan con todo tipo de táboas temporais (que se almacenan inmediatamente na caché) converten o código nunha secuencia de instrucións executadas moi rapidamente.

Por certo, se envolves a función en COMPILADO NATIVO, entón deixa de pedir tempo e a súa velocidade aumenta 10 veces. E a multitarefa cooperativa? Pero para o código compilado de forma nativa tivemos que facer PREEMPTIVE MULTITASKING en SQL.

Fonte: www.habr.com

Engadir un comentario