Sim, meu laptop antigo é várias vezes mais poderoso que o servidor de produção

Essas são as alegações que ouvi de nossos desenvolvedores. O mais interessante é que isso acabou sendo verdade, dando origem a uma longa investigação. Será sobre servidores SQL, que rodam em nosso VMware.



Na verdade, é fácil garantir que o servidor de produção esteja irremediavelmente atrás do laptop. Execute (não no tempdb e não em uma base com a durabilidade atrasada ativada) 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

Na minha área de trabalho, ele é executado por 5 segundos e no servidor de produção - 28 segundos. Como o SQL deve aguardar o final físico da entrada do log de transações, e aqui estamos fazendo transações muito curtas. Grosso modo, dirigimos um caminhão grande e poderoso para o tráfego da cidade e observamos como ele é ultrapassado por fornecedores de pizza em scooters - a taxa de transferência não é importante aqui, apenas a latência é importante. E nem um único armazenamento de rede, não importa quantos zeros em seu preço, consiga ganhar latência contra o SSD local.

(nos comentários, eu menti - eu havia atrasado a durabilidade em ambos os lugares. Sem durabilidade tardia, verifica-se:
Desktop - 39 segundos, 15K tr / s, 0,065ms / io ida e volta
PROD - 360 segundos, 1600 tr / s, 0,6ms
I deveria ter prestado atenção cedo demais)


No entanto, neste caso, estamos lidando com os zeros triviais da função zeta Riemann com um exemplo trivial. No exemplo que os desenvolvedores me trouxeram, houve outro. Fiquei convencido de que eles estavam certos e comecei a tirar do exemplo todas as suas especificidades relacionadas à lógica de negócios. Em algum momento, percebi que podia jogar completamente fora o código deles e escrever o meu - o que demonstra o mesmo problema -, ele é executado 3-4 vezes mais lento na produção:

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 tudo estiver bem com você, a verificação da simplicidade do número será realizada por 6-7-8 segundos. Por isso, estava em vários servidores. Mas, em alguns, a verificação levou de 25 a 40 segundos. Curiosamente, não havia servidores nos quais a execução levaria, digamos, 14 segundos - o código funcionava muito rápido ou muito lentamente, ou seja, o problema era, digamos, em preto e branco.

O que eu fiz? Útil nas métricas da VMware. Tudo estava bem lá - havia muitos recursos, Tempo de prontidão = 0, tudo foi suficiente, durante o teste em servidores rápidos e lentos CPU = 100 em uma vCPU. Fiz um teste para calcular o número de Pi - o teste mostrou os mesmos resultados em qualquer servidor. Cheirava a magia negra cada vez mais.

Tendo saído na fazenda DEV, comecei a jogar como servidores. Verificou-se que o vMotion de host para host pode "curar" o servidor, mas pode e vice-versa, transformar um servidor "rápido" em um "lento". Parece que sim - alguns anfitriões têm um problema ... mas ... não. Algumas máquinas virtuais desaceleraram no host, por exemplo, A, mas trabalharam rapidamente no host B. E a outra máquina virtual, pelo contrário, trabalhou rapidamente no A e desacelerou no B! Os carros "velozes" e "lentos" costumavam girar no anfitrião!

A partir desse momento, o ar cheirava distintamente a enxofre. Afinal, o problema não pode ser atribuído a nenhuma máquina virtual (patches do Windows, por exemplo) - porque se transformou em uma máquina “rápida” com o vMotion. Mas o problema também não pode ser atribuído ao host - porque ele pode ter máquinas "rápidas" e "lentas". Também não estava conectado à carga - consegui obter uma máquina “lenta” no host, onde não havia mais nada além dela.

Desesperado, lancei o Process Explorer da Sysinternals e olhei para a pilha SQL. Em máquinas lentas, a linha imediatamente chamou minha atenção:

ntoskrnl.exe! KeSynchronizeExecution + 0x5bf6
ntoskrnl.exe! KeWaitForMultipleObjects + 0x109d
ntoskrnl.exe! KeWaitForMultipleObjects + 0xb3f
ntosKrnx3 0!
ntoskrnl.exe! KeQuerySystemTimePrecise + 0x881 <- !!!
ntoskrnl.exe! ObDereferenceObjectDeferDelete + 0x28a
ntoskrnl.exe! KeSynchronizeExecution + 0x2de2
sqllang.dll! CDiagThreadSafe :: PxlvlReplace + 0x1a20
... ignorado
sqldk.dll! SystemThread :: MakeMiniSOSThread + 0xa54
KERNEL32.DLL! BaseThreadInitThunk + 0x14
ntdll.dll! RtlUserThreadStart + 0x21


Já era alguma coisa. O programa foi 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);
                }
            }
        }
    }

Esse programa mostrou uma desaceleração ainda mais brilhante - em máquinas "rápidas", mostra de 16 a 18 milhões de ciclos por segundo, enquanto nas lentas é de um milhão e meio, ou até 700 mil. Ou seja, a diferença é de 10 a 20 vezes (!!!). Essa já era uma pequena vitória: de qualquer forma, não havia ameaça de ficar preso entre o suporte da Microsoft e da VMware para que eles transferissem flechas entre si.

Além disso, o progresso parou - férias, assuntos importantes, histeria viral e um aumento acentuado da carga. Mencionei frequentemente um problema mágico para meus colegas, mas às vezes parecia que eles nem sempre confiavam em mim - a afirmação de que a VMware diminuía o código de 10 a 20 vezes era monstruosa demais.

Tentei descobrir o que estava diminuindo. Às vezes, pareceu-me que encontrei uma solução - ligar e desligar Hotplugs, alterar o tamanho da memória ou o número de processadores muitas vezes transformou a máquina em uma "rápida". Mas não para sempre. Mas o que acabou sendo verdade é que basta bater e bater no volante - ou seja, alterar qualquer parâmetro da máquina virtual.Finalmente

, meus colegas americanos de repente descobriram a causa raiz.



Hosts diferiram em frequência!
  • Isso geralmente não é assustador. Mas: ao passar de um host 'nativo' para um host com uma frequência 'diferente', o VMware deve ajustar o resultado do GetTimePrecise.
  • Como regra, isso não é assustador, a menos que haja um aplicativo que solicite o tempo exato milhões de vezes por segundo, como um servidor SQL.
  • Mas isso não é assustador, pois o SQL Server nem sempre faz isso (consulte a Conclusão)

Mas há casos em que esse rake atinge dolorosamente. E, no entanto, sim, tocando no volante (alterando algo nas configurações da VM) forcei o VMware a 'recontar' a configuração, e a frequência do host atual se tornou a frequência 'nativa' da máquina.

Decisão


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

When you disable virtualization of the TSC, reading the TSC from within the virtual machine returns the physical machine’s TSC value, and writing the TSC from within the virtual machine has no effect. Migrating the virtual machine to another host, resuming it from suspended state, or reverting to a snapshot causes the TSC to jump discontinuously. Some guest operating systems fail to boot, or exhibit other timekeeping problems, when TSC virtualization is disabled. In the past, this feature has sometimes been recommended to improve performance of applications that read the TSC frequently, but performance of the virtual TSC has been improved substantially in current products. The feature has also been recommended for use when performing measurements that require a precise source of real time in the virtual machine.

Em resumo, adicione o parâmetro

monitor_control.virtual_rdtsc = FALSE

Conclusão


Você provavelmente tem uma pergunta: o que para SQL chama GetTimePrecise com tanta frequência?

Eu não tenho a fonte do servidor SQL, mas a lógica diz isso. O SQL é quase um sistema operacional com simultaneidade cooperativa, em que cada thread deve ceder periodicamente. Onde é melhor fazer isso? Onde houver uma expectativa natural - bloqueio ou IO. Bem, e se girarmos os ciclos computacionais? Então, o óbvio e quase o único lugar está no intérprete (este não é exatamente um intérprete), após a execução do próximo operador.

Como regra, o SQL Server não é usado para pregar cálculos limpos e isso não é um problema. Mas os ciclos de trabalho com todos os tipos de tablets temporários (que são imediatamente armazenados em cache) transformam o código em uma sequência de instruções executadas muito rapidamente.

A propósito, se você agrupar a função em NATIVELY COMPILED, ela para de pedir tempo e sua velocidade aumenta em um fator de 10. Mas e a multitarefa cooperativa? Mas, para código compilado nativamente, eu tive que fazer multitarefa preventiva no SQL.

All Articles