Seguindo com @DavidSchwartz e @Matt, isso parece um problema de gerenciamento de threads, bloqueios.
Sugiro:
-
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.
-
Use conjuntos de encadeamentos, se não estiverem usando-os.
-
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.