Sí, el meu vell ordinador portàtil és diverses vegades més potent que el vostre servidor de producció

Aquestes són exactament les queixes que he sentit dels nostres desenvolupadors. El més interessant és que això va resultar ser cert, donant lloc a una llarga investigació. Parlarem dels servidors SQL que s'executen a VMware.

Sí, el meu vell ordinador portàtil és diverses vegades més potent que el vostre servidor de producció

De fet, és fàcil assegurar-se que el servidor de producció està irremediablement darrere de l'ordinador portàtil. Executeu (no a tempdb ni a una base de dades amb la durabilitat retardada activada) el codi:

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

Al meu escriptori triga 5 segons i al servidor de producció triga 28 segons. Com que SQL ha d'esperar al final físic de l'entrada del registre de transaccions, i aquí estem fent transaccions molt curtes. A grans trets, vam conduir un camió gran i potent al trànsit de la ciutat i vam veure com era superat de manera espectacular per repartidors de pizza amb patinets: el rendiment no és important aquí, només la latència és important. I cap emmagatzematge de xarxa, per molts zeros que hi hagi en el seu preu, no pot superar el SSD local en termes de latència.

(en els comentaris va resultar que vaig mentir: vaig haver retardat la durabilitat en tots dos llocs. Sense retardar la durabilitat resulta:
Escriptori: 39 segons, 15K tr/s, 0.065 ms/io anada i tornada
PROD - 360 segons, 1600 tr/s, 0.6 ms
Hauria d'haver notat que era massa ràpid)

Tanmateix, en aquest cas estem tractant amb zeros trivials de la funció zeta de Riemann amb un exemple trivial. En l'exemple que em van portar els desenvolupadors, era diferent. Estava convençut que tenien raó i vaig començar a eliminar de l'exemple totes les seves especificitats relacionades amb la lògica empresarial. En algun moment em vaig adonar que podia llençar completament el seu codi i escriure el meu propi, cosa que demostra el mateix problema, en producció funciona 3-4 vegades més lent:

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

Si tot està bé, comprovar la primalitat d'un número trigarà 6-7-8 segons. Això va passar en diversos servidors. Però en alguns, la comprovació va trigar entre 25 i 40 segons. Curiosament, no hi havia servidors on l'execució trigués, per exemple, 14 segons: el codi funcionava molt ràpidament o molt lentament, és a dir, el problema era, diguem-ne, en blanc i negre.

El que he fet? S'han utilitzat mètriques de VMware. Tot estava bé allà: hi havia una gran quantitat de recursos, temps de preparació = 0, n'hi havia prou de tot, durant la prova en servidors ràpids i lents CPU = 100 en una vCPU. Vaig fer una prova per calcular el nombre Pi: la prova va mostrar els mateixos resultats a qualsevol servidor. L'olor de la màgia negra es feia més i més forta.

Un cop vaig arribar a la granja DEV, vaig començar a jugar amb els servidors. Va resultar que vMotion d'amfitrió a amfitrió pot "curar" un servidor, però també pot convertir un servidor "ràpid" en un de "lent". Sembla que això és tot: alguns amfitrions tenen un problema... però... no. Alguna màquina virtual era lenta a l'amfitrió, per exemple A, però funcionava ràpidament a l'amfitrió B. I una altra màquina virtual, per contra, funcionava ràpidament a A i es va alentir a B! Tant les màquines "ràpides" com les "lentes" sovint giraven a l'amfitrió!

A partir d'aquell moment, hi va haver una clara olor de sofre a l'aire. Al cap i a la fi, el problema no es va poder atribuir a la màquina virtual (pegats de Windows, per exemple); després de tot, amb vMotion es va convertir en "ràpid". Però el problema tampoc es pot atribuir a l'amfitrió; després de tot, podria tenir màquines "ràpides" i "lentes". A més, això no estava relacionat amb la càrrega: vaig aconseguir aconseguir una màquina "lenta" a l'amfitrió, on no hi havia res a part d'ella.

Desesperat, vaig llançar Process Explorer des de Sysinternals i vaig mirar la pila SQL. A les màquines lentes, la línia em va cridar immediatament l'atenció:

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

Això ja era alguna cosa. El programa va ser escrit:

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

Aquest programa va demostrar una desacceleració encara més pronunciada: en màquines "ràpides" mostra 16-18 milions de cicles per segon, mentre que en màquines lentes mostra un milió i mig, o fins i tot 700 mil. És a dir, la diferència és de 10-20 vegades (!!!). Aquesta ja va ser una petita victòria: en qualsevol cas, no hi havia cap amenaça de quedar-se encallat entre el suport de Microsoft i VMware perquè girarien fletxes entre si.

Aleshores es va aturar el progrés: vacances, qüestions importants, histèria viral i un fort augment de la càrrega de treball. Sovint vaig esmentar el problema de la màgia als meus companys, però de vegades semblava que ni tan sols sempre em van creure: l'afirmació que VMware alenteix el codi entre 10 i 20 vegades era massa monstruosa.

Vaig intentar desenterrar el que m'estava frenant. De vegades em va semblar que havia trobat una solució: activar i desactivar els connectors calents, canviar la quantitat de memòria o el nombre de processadors sovint convertia la màquina en una de "ràpida". Però no per sempre. Però el que va resultar ser cert és que n'hi ha prou amb sortir i picar la roda, és a dir, canviar qualsevol paràmetre de màquina virtual

Finalment, els meus col·legues nord-americans van trobar de sobte la causa principal.

Sí, el meu vell ordinador portàtil és diverses vegades més potent que el vostre servidor de producció

Els amfitrions difereixen en freqüència!

  • Per regla general, això no és gran cosa. Però: quan es passa d'un amfitrió "natiu" a un amfitrió amb una freqüència "diferent", VMware ha d'ajustar el resultat de GetTimePrecise.
  • Per regla general, això no és un problema, tret que hi hagi una aplicació que sol·liciti l'hora exacta milions de vegades per segon, com ara el servidor SQL.
  • Però això no fa por, ja que el servidor SQL no sempre ho fa (vegeu Conclusió)

Però hi ha casos en què aquest rasclet colpeja fort. I, tanmateix, sí, fent un toc a la roda (canviant alguna cosa a la configuració de la màquina virtual) vaig forçar VMware a "recalcular" la configuració i la freqüència de l'amfitrió actual es va convertir en la freqüència "nativa" de la màquina.

decisió

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

Quan desactiveu la virtualització del TSC, llegir el TSC des de la màquina virtual retorna el valor TSC de la màquina física i escriure el TSC des de la màquina virtual no té cap efecte. Migrar la màquina virtual a un altre host, reprendre-la des de l'estat suspès o tornar a una instantània fa que el TSC salti de manera discontínua. Alguns sistemes operatius convidats no poden arrencar o presenten altres problemes de cronometratge quan la virtualització TSC està desactivada. En el passat, de vegades s'ha recomanat aquesta funció per millorar el rendiment de les aplicacions que llegeixen el TSC amb freqüència, però el rendiment del TSC virtual s'ha millorat substancialment en els productes actuals. La funció també s'ha recomanat per utilitzar-la quan es realitzen mesures que requereixen una font precisa de temps real a la màquina virtual.

En resum, cal afegir el paràmetre

monitor_control.virtual_rdtsc = FALSE

Conclusió

Probablement tingueu una pregunta: per què SQL truca a GetTimePrecise tan sovint?

No tinc el codi font del servidor SQL, però la lògica diu això. SQL és gairebé un sistema operatiu amb concurrència cooperativa, on cada fil ha de "cedir" de tant en tant. On és el millor lloc per fer-ho? On hi ha una espera natural: bloqueig o IO. D'acord, però, què passa si estem girant bucles computacionals? Aleshores, el lloc obvi i gairebé únic és a l'intèrpret (això no és realment un intèrpret), després d'executar la següent instrucció.

En general, el servidor SQL no s'utilitza per a l'enclavament informàtic pur i això no és un problema. Però els bucles que funcionen amb tot tipus de taules temporals (que s'emmagatzemen immediatament a la memòria cau) converteixen el codi en una seqüència d'instruccions executades molt ràpidament.

Per cert, si emboliqueu la funció a NATIVELY COMPILED, aleshores deixa de demanar temps i la seva velocitat augmenta 10 vegades. Què passa amb la multitasca cooperativa? Però per al codi compilat de forma nativa havíem de fer MULTTASCA PREEMPTIVE en SQL.

Font: www.habr.com

Afegeix comentari