image

Access unlimited bootcamps and 650+ courses forever

75
%OFF
Article image
Ruan Pablo
Ruan Pablo16/12/2025 12:51
Share

Rinha de Backend: Estratégias de Performance e Tuning com .NET

  • #C#
  • #.NET

Participar da Rinha de Backend não é apenas sobre escrever código que funciona, é sobre escrever código que aguenta pressão. Durante a última edição, testei diversas abordagens para espremer cada milissegundo de performance da minha API em .NET.

Neste artigo rápido, vou compartilhar as escolhas arquiteturais e de infraestrutura que fizeram meu p99 sair da casa dos segundos para um desempenho competitivo, e mostrar como a configuração correta do Program.cs foi crucia pra conquistar o Segundo Lugar.

Limites: 350Ram e 1.5vCPU e mínimo de 2 instância do backend e um balanceador de carga

1. O Erro Clássico: Abrir e Fechar Conexões (Redis)

Um dos primeiros gargalos que muitos enfrentam é o gerenciamento de conexões com o banco de dados ou cache. No início, pode parecer inofensivo instanciar uma conexão, usar e fechar. Porém, sob carga massiva, mas especificamente de até 91mil requisições, o handshake TCP se torna extremamente custoso.

A Solução: Singleton. Na minha implementação, registrei o IConnectionMultiplexer do Redis como Singleton. Isso garante que a aplicação reaproveite a mesma conexão física para todas as requisições, eliminando o overhead de abrir novos sockets repetidamente.

2. O Mistério do p99 em 1500ms (Infraestrutura)

Durante os testes de carga, notei algo estranho: meu p99 (o tempo de resposta dos 1% mais lentos) estava batendo 1500ms. O código parecia otimizado, o banco estava respondendo rápido. Onde estava o problema?

A culpa não era do C#, mas da memória do Nginx. O load balancer estava ficando sem memória suficiente para gerenciar o volume de conexões simultâneas e o buffering, causando latência na entrega das requisições para a API. Ajustar os recursos da infraestrutura foi tão importante quanto mexer no código.

3. Producer-Consumer e o "Número Mágico" de Workers

Para o processamento de pagamentos, optei por não processar tudo na thread da requisição HTTP. Utilizei System.Threading.Channels para criar um modelo de Producer-Consumer. A requisição chega, joga o trabalho no canal (memória) e libera a thread.

Do outro lado, precisei definir quantos consumidores (workers) processariam esses dados.

Cenário 1: Vou colocar 16 workers pra aumentar a vazão

A realidade: Performance degradada.

O excesso de workers aumentou a concorrência por recursos (CPU/Context Switching) sem ganho real. Após vários testes de estresse, cheguei ao número ideal de 8 workers por instância de backend. Mais que isso não mostrava ganho de desempenho e, às vezes, até piorava o throughput.

4. Native AOT: Matando o Cold Start e Otimizando Recursos

Uma das escolhas mais impactantes foi compilar a aplicação usando Native AOT (Ahead-of-Time). Diferente da compilação tradicional, que depende do JIT (Just-In-Time) ao iniciar, o AOT gera código de máquina nativo.

Isso explicou duas escolhas fundamentais no meu Program.cs:

  1. WebApplication.CreateSlimBuilder(args): Essa versão do builder carrega apenas o essencial do ASP.NET Core, removendo funcionalidades que dependem de Reflection (que é custoso ou incompatível com AOT) e tornando a inicialização quase instantânea.
  2. JSON Source Generators: O trecho options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); não é estético. No modo AOT, o System.Text.Json não pode usar Reflection para serializar objetos. Eu precisei usar geradores de código para "ensinar" o compilador como tratar meus JSONs em tempo de build.

O Resultado? Um Cold Start inexistente e uma aplicação consumindo muito menos memória RAM, o que permitiu que os containers rodassem mais leves e sobrasse recurso para o que importa: processar requisições.

Abaixo, a configuração completa unindo o AOT com o tuning de HTTP:

zanfranceschi/rinha-de-backend-2025: Rinha de Backend - Terceira Edição

RuanPabloCR/Rinha-backend

var builder = WebApplication.CreateSlimBuilder(args);


          builder.Services.ConfigureHttpJsonOptions(options =>
          {
              options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
          });


          string redisHost = Environment.GetEnvironmentVariable("REDIS_HOST") ?? "localhost:6379";
          builder.Services.AddSingleton<IConnectionMultiplexer>(
              _ => ConnectionMultiplexer.Connect(redisHost)
          );


          builder.Services.AddSingleton<IDatabase>(sp => sp.GetRequiredService<IConnectionMultiplexer>().GetDatabase());
          builder.Services.AddScoped<GetPaymentsByDateService>();


          builder.Services.AddHttpClient<PaymentProcessorService>()
              .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
              {
                  MaxConnectionsPerServer = 20,
                  PooledConnectionLifetime = TimeSpan.FromMinutes(10),
                  PooledConnectionIdleTimeout = TimeSpan.FromMinutes(4),
                  UseCookies = false,
                  EnableMultipleHttp2Connections = true
              })
              .ConfigureHttpClient(client =>
              {
                  client.Timeout = TimeSpan.FromSeconds(3);
                  client.DefaultRequestHeaders.ConnectionClose = false;


                  client.DefaultRequestHeaders.Add("Connection", "keep-alive");
                  client.DefaultRequestHeaders.Add("Keep-Alive", "timeout=30, max=100");
              });


          builder.Services.Configure<LoggerFilterOptions>(options =>
          {
              options.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning);
          });


          builder.Services.AddSingleton<DistributedHealthCheckService>();
          builder.Services.AddSingleton<PaymentWorkerService>();
          builder.Services.AddHostedService(provider => provider.GetRequiredService<PaymentWorkerService>());


          var app = builder.Build();


Share
Recommended for you
TIVIT - .Net com GitHub Copilot
Avanade - Back-end com .NET e IA
GFT Start #7 .NET
Comments (1)
Matheus Fraga
Matheus Fraga - 16/12/2025 14:28

Boa tarde, valeu pelas dicas, amigo. ótimo artigo.