Uma API que passa tranquilamente pelo staging pode desmoronar no momento em que o tráfego real chega. A falha raramente é dramática. É uma morte por mil cortes. Uma thread bloqueada aqui. Uma consulta redundante ali. Sob carga leve, essas pequenas ineficiências ficam ocultas. Sob a pressão de produção, elas se acumulam em esgotamento do pool de threads, sufocamento do banco de dados e tempos de resposta que destroem a confiança do usuário. O ajuste de performance não se trata de encontrar uma solução mágica. Trata-se de construir um sistema onde cada camada respeite a escassez de threads, memória e conexões de banco de dados. Quando você trata esses recursos como finitos, você para de reagir a interrupções e começa a preveni-las.
Elimine o Sync-over-Async antes que ele elimine você
O padrão mais destrutivo em aplicações ASP.NET Core de alto tráfego é o sync-over-async. Você o vê quando alguém chama .Result ou .Wait() em um método assíncrono porque precisa do valor agora e não quer refatorar a pilha de chamadas. Essa decisão bloqueia a thread de chamada. A thread fica ociosa, esperando por um trabalho que já está acontecendo em outro lugar, mas o runtime não pode reutilizá-la para outra requisição.
Quando requisições suficientes fazem isso, o pool de threads sofre esgotamento. Seu gráfico de CPU parece saudável porque os processadores não estão ocupados, mas sua latência explode. As requisições entram em fila, esperando por threads que nunca serão liberadas. A correção é mecânica, mas exige disciplina: use await em toda a pilha de chamadas. Se um método chama uma API assíncrona, ele deve ser assíncrono também. Não há atalhos aqui. Cada bloqueio síncrono que você remove libera espaço para o tráfego real.
Pare de desperdiçar trabalho com clientes desconectados
Clientes desconectam. Navegadores fecham abas. Aplicativos móveis perdem o sinal. Se o seu servidor não souber que o cliente partiu, ele continuará executando consultas ao banco de dados, fazendo o parsing de JSON e consumindo threads em uma resposta que ninguém receberá. Passe um CancellationToken para cada operação assíncrona que o suporte. Isso significa consultas do Entity Framework, chamadas HTTP com HttpClient e qualquer trabalho de segundo plano de longa duração.
Quando a conexão cai, o token aciona o cancelamento e o trabalho para imediatamente. Isso preserva os ciclos de CPU do banco de dados e devolve as threads ao pool mais rapidamente. É uma pequena mudança nas assinaturas dos métodos que traz grandes resultados sob carga.
Faça cache do que não muda
Seu catálogo de produtos provavelmente não muda a cada requisição. Suas flags de configuração certamente não mudam. No entanto, muitas APIs consultam o banco de dados repetidamente para os mesmos dados estáticos. O Output caching no ASP.NET Core permite armazenar respostas renderizadas e servi-las diretamente da memória sem tocar novamente em seus controllers ou banco de dados.
Use a invalidação baseada em tags com cuidado. Quando você atualizar um produto, invalide apenas a tag associada àquela categoria ou item. Você não precisa limpar todo o cache. Isso mantém sua taxa de acerto de cache (cache hit ratio) alta e sua contagem de consultas ao banco de dados baixa.
Corrija seu acesso a dados primeiro
As chamadas de banco de dados consomem a maior parte do tempo de requisição na maioria das APIs. Antes de otimizar qualquer outra coisa, olhe para cá.
Se você está consultando dados apenas para exibi-los e nunca planeja atualizá-los, adicione AsNoTracking() às suas consultas do Entity Framework. O EF Core pula o rastreamento de alterações e a criação de snapshots
