Por que temos um pico 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 eles se deterioram rapidamente assim que atingimos cerca de 3.500 usuários simultâneos por servidor. Temos dois servidores e, ao atingi-los com 7.000 usuários, os tempos médios de resposta ficam abaixo de 500 ms para todos os endpoints. As caixas estão atrás de um balanceador de carga, portanto, obtemos 3.500 concurrents 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 nos dá um tempo médio de resposta por ponto final 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, com 10.000 usuários simultâneos, a média da CPU é um pouco abaixo de 50% e a RAM fica em torno de 3-4 GB em 16. Isso nos faz pensar que estamos atingindo algum tipo de limite em algum lugar. A captura de tela abaixo mostra alguns contadores de chaves no perfmon durante um teste de carga com um total de 10.000 usuários simultâneos. O contador realçado é pedidos / segundo. À direita da captura de tela, você pode ver o gráfico de solicitações por segundo se tornando realmente errático. Este é o principal indicador de tempos de resposta lentos. Assim que vemos este padrão, notamos tempos de resposta lentos no teste de carga.

Comoresolvemosesseproblemadedesempenho?Estamostentandoidentificarseissoéumproblemadecodificaçãooudeconfiguração.Háalgumaconfiguraçãonoweb.configounoIISquepossaexplicaressecomportamento?Opooldeaplicativosestáexecutandoo.NETv4.0eaversãodoIISé7.5.AúnicaalteraçãoquefizemosdasconfiguraçõespadrãoéatualizarovalordoComprimentodaFiladopooldeaplicativosde1.000para5.000.TambémadicionamosasseguintesconfiguraçõesnoarquivoAspnet.config:

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

Mais detalhes:

O objetivo da API é combinar dados de várias origens externas e retornar como JSON. Atualmente, está usando uma implementação de cache InMemory para armazenar chamadas externas individuais na camada de dados. A primeira solicitação para um recurso buscará todos os dados necessários e quaisquer solicitações subseqüentes para o mesmo recurso obterão resultados do cache. Temos um 'cache runner' que é implementado como um processo em segundo plano que atualiza as informações no cache em determinados intervalos definidos. Adicionamos bloqueio ao redor do código que busca dados dos recursos externos. Também implementamos os serviços para buscar os dados das fontes externas de maneira assíncrona, de modo que o ponto de extremidade deva ser 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 segmentos disponíveis para o processo?

    
por Christian Hagelid 13.11.2013 / 03:55

1 resposta

2

Seguindo com @DavidSchwartz e @Matt, isso parece um problema de gerenciamento de threads, bloqueios.

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 com o lado do ambiente do servidor.

  2. Use conjuntos de encadeamentos, se não estiverem usando-os.

  3. Sobre as chamadas externas que 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 deva ser tão lento quanto a chamada externa mais lenta (a menos que tenhamos dados em o cache, claro). "

As perguntas são:  - Você verificou se algum dado em 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 que sejam assíncronas, com que frequência as chamadas externas são executadas? Mesmo se eles não forem executados com tanta frequência, eles poderão ser bloqueados pela quantidade excessiva de solicitações para o cache das chamadas do usuário enquanto o cache estiver bloqueado. Esse cenário geralmente mostra porcentagem fixa de CPU usada porque muitos segmentos estão aguardando em intervalos fixos e o "bloqueio" também deve ser gerenciado.  - Você verificou se as 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 as chamadas externas por meio do mesmo pool de threads que gerencia as solicitações do usuário. Isso é para evitar o cenário anterior.

    
por 31.12.2013 / 13:11