Por que temos um aumento repentino nos tempos de resposta?

12

Temos uma API implementada usando o ServiceStack, hospedado no IIS. Ao realizar o teste de carga da API, descobrimos que os tempos de resposta são bons, mas que se deterioram rapidamente assim que atingimos cerca de 3.500 usuários simultâneos por servidor. Temos dois servidores e, quando atingimos 7.000 usuários, o tempo médio de resposta fica abaixo de 500 ms para todos os pontos de extremidade. Como as caixas estão atrás de um balanceador de carga, temos 3.500 concorrentes por servidor. No entanto, assim que aumentamos o número total de usuários simultâneos, vemos um aumento significativo nos tempos de resposta. Aumentar os usuários simultâneos para 5.000 por servidor fornece um tempo médio de resposta por terminal de cerca de 7 segundos.

A memória e a CPU nos servidores são bastante baixas, enquanto os tempos de resposta são bons e quando eles se deterioram. No pico de 10.000 usuários simultâneos, a CPU fica em média abaixo de 50% e a RAM fica em torno de 3-4 GB em 16. Isso nos deixa pensando que estamos atingindo algum tipo de limite em algum lugar. A captura de tela abaixo mostra alguns contadores importantes no perfmon durante um teste de carga com um total de 10.000 usuários simultâneos. O contador destacado é solicitações / segundo. À direita da captura de tela, você pode ver as solicitações por segundo gráfico se tornando realmente irregulares. Este é o principal indicador para tempos de resposta lentos. Assim que vemos esse padrão, observamos tempos de resposta lentos no teste de carga.

captura de tela perfmon com solicitações por segundo destacadas

Como resolvemos esse problema de desempenho? Estamos tentando identificar se esse é um problema de codificação ou de configuração. Existem configurações no web.config ou no IIS que possam explicar esse comportamento? O pool de aplicativos está executando o .NET v4.0 e a versão do IIS é 7.5. A única alteração que fizemos das configurações padrão é atualizar o valor de Comprimento da fila do pool de aplicativos de 1.000 para 5.000. Também adicionamos as seguintes configurações ao arquivo Aspnet.config:

<system.web>
    <applicationPool 
        maxConcurrentRequestsPerCPU="5000"
        maxConcurrentThreadsPerCPU="0" 
        requestQueueLimit="5000" />
</system.web>

Mais detalhes:

O objetivo da API é combinar dados de várias fontes externas e retornar como JSON. Atualmente, ele está usando uma implementação de cache do InMemory para armazenar em cache chamadas externas individuais na camada de dados. A primeira solicitação para um recurso buscará todos os dados necessários e quaisquer solicitações subsequentes para o mesmo recurso obterão resultados do cache. Temos um 'corredor de cache' que é implementado como um processo em segundo plano que atualiza as informações no cache em determinados intervalos definidos. Adicionamos bloqueio ao código que busca dados dos recursos externos. Também implementamos os serviços para buscar os dados das fontes externas de forma assíncrona, de modo que o ponto de extremidade seja tão lento quanto a chamada externa mais lenta (a menos que tenhamos dados no cache, é claro). Isso é feito usando a classe System.Threading.Tasks.Task.Poderíamos estar atingindo uma limitação em termos de número de threads disponíveis para o processo?

Christian Hagelid
fonte
5
Quantos núcleos sua CPU possui? Talvez você esteja extrapolando um núcleo. Quando o número mágico é de 50%, 25% ou 12,5%, isso sugere que você atingiu o máximo de um núcleo e, por algum motivo, não pode usar os outros núcleos que estão ociosos. Verifique se há um núcleo máximo.
precisa
1
Você tem um segmento por solicitação? Então, para 5000 solicitações, você tem 5000 threads? Se você o fizer, provavelmente esse é o seu problema. Em vez disso, você deve criar um conjunto de encadeamentos e usá-lo para processar as solicitações, enfileirando-as à medida que elas chegam ao conjunto de encadeamentos. Quando um encadeamento termina com uma solicitação, ele pode processar uma solicitação fora da fila. Esse tipo de discussão é melhor para o stackoverflow. Muitos threads significam muitas opções de contexto.
Matt
1
Apenas uma verificação de integridade aqui, você tentou desativar todos os seus processos em segundo plano e ver qual é o comportamento apenas para o JSON retornar dados estáticos do cache? Em outras palavras, fazer o seu JSON solicitar dados estáticos e remover as "chamadas assíncronas externas" que atualizam completamente o cache. Além disso, dependendo da quantidade de dados JSON sendo veiculados em todas as solicitações, você já pensou na taxa de transferência da rede e se as solicitações estão começando a fazer backup porque os servidores simplesmente não conseguem enviar os dados com rapidez suficiente?
Robert
1
+1 à sugestão de Davids acima. Você realmente deve refazer o teste e analisar cuidadosamente cada utilização principal. Eu sugiro que você faça isso o mais rápido possível para eliminá-lo, se nada mais. Em segundo lugar, desconfio um pouco do seu cache. A contenção de bloqueio pode mostrar exatamente esse tipo de comportamento - em algum momento crítico, os bloqueios causam atrasos que, por sua vez, fazem com que os bloqueios sejam mantidos por mais tempo do que o normal, causando um ponto de inflexão no qual as coisas vão rapidamente para baixo. Você pode compartilhar seu código de cache e bloqueio?
Steve Cook
1
Qual é a configuração do disco para os servidores (supondo que, uma vez que tenham carga balanceada, a configuração do disco seja a mesma)? Você pode postar todas as especificações das unidades / servidores em sua postagem inicial? Você lançou um perfmon no (s) disco (s) nas unidades físicas nas quais o IIS E os arquivos de log do IIS existem? É bem possível que você esteja tendo problemas com o disco, com a entrada de 3.500 solicitações = 3.500+ registros do IIS. Se eles estiverem no mesmo disco / partição, você poderá ter um grande problema lá.
21813 Techie Joe

Respostas:

2

Seguindo @DavidSchwartz e @Matt, isso parece um encadeamento, bloqueia o problema de gerenciamento.

Eu sugiro:

  1. Congele as chamadas externas e o cache gerado para elas e execute o teste de carga com informações externas estáticas apenas para descartar qualquer problema não relacionado ao lado do ambiente do servidor.

  2. Use conjuntos de encadeamentos se não os estiver usando.

  3. Sobre as chamadas externas, você disse: "Também implementamos os serviços para buscar os dados das fontes externas de maneira assíncrona, de modo que o ponto de extremidade seja tão lento quanto a chamada externa mais lenta (a menos que tenhamos dados no cache, é claro). "

As perguntas são: - Você verificou se algum dado do cache está bloqueado durante a chamada externa ou apenas ao gravar o resultado da chamada externa no cache? (muito óbvio, mas devo dizer). - Você bloqueia todo o cache ou partes pequenas dele? (muito óbvio, mas devo dizer). - Mesmo sendo assíncronas, com que frequência as chamadas externas são executadas? Mesmo que não sejam executados com tanta frequência, eles podem ser bloqueados por uma quantidade excessiva de solicitações ao cache das chamadas do usuário enquanto o cache está bloqueado. Esse cenário geralmente mostra a porcentagem fixa de CPU usada porque muitos threads estão aguardando em intervalos fixos e o "bloqueio" também deve ser gerenciado. - Você verificou se tarefas externas significam que o tempo de resposta também aumenta quando o cenário lento chega?

Se o problema persistir, sugiro evitar a classe Task e fazer chamadas externas através do mesmo pool de threads que gerencia as solicitações do usuário. Isso é para evitar o cenário anterior.

SaintJob 2.0
fonte