Artigos

C# / .NET

Como reduzimos o tempo de resposta de uma API em 10x com EF Core

Um caso real de otimizacao de queries que transformou uma API lenta em um servico de alta performance. Estrategias praticas que voce pode aplicar hoje.

·12 min de leitura

O problema: API com tempo de resposta inaceitavel

Em um projeto recente, herdamos uma API que levava 8 segundos para retornar uma lista de pedidos. Os usuarios estavam frustrados, o time de produto estava pressionando, e a solucao parecia ser "mais hardware".

Antes de escalar verticalmente, decidimos investigar o que realmente estava acontecendo.

Diagnostico: onde estava o gargalo?

Utilizamos o SQL Server Profiler e o MiniProfiler para entender o comportamento:

// O codigo original - parece inocente, nao?
var pedidos = await _context.Pedidos
    .Include(p => p.Cliente)
    .Include(p => p.Itens)
        .ThenInclude(i => i.Produto)
    .Where(p => p.Status == StatusPedido.Ativo)
    .ToListAsync();

O problema? N+1 queries. Para 1000 pedidos, estavamos fazendo mais de 3000 queries ao banco.

A solucao: carregamento inteligente

1. Split queries para relacionamentos grandes

var pedidos = await _context.Pedidos
    .AsSplitQuery() // Divide em queries separadas
    .Include(p => p.Cliente)
    .Include(p => p.Itens)
        .ThenInclude(i => i.Produto)
    .Where(p => p.Status == StatusPedido.Ativo)
    .ToListAsync();

2. Projections para retornar apenas o necessario

var pedidosDto = await _context.Pedidos
    .Where(p => p.Status == StatusPedido.Ativo)
    .Select(p => new PedidoResumoDto
    {
        Id = p.Id,
        ClienteNome = p.Cliente.Nome,
        Total = p.Itens.Sum(i => i.Quantidade * i.PrecoUnitario),
        QuantidadeItens = p.Itens.Count
    })
    .ToListAsync();

3. Compiled queries para operacoes frequentes

private static readonly Func<AppDbContext, StatusPedido, IAsyncEnumerable<PedidoResumoDto>> 
    _getPedidosAtivos = EF.CompileAsyncQuery(
        (AppDbContext ctx, StatusPedido status) =>
            ctx.Pedidos
                .Where(p => p.Status == status)
                .Select(p => new PedidoResumoDto { /* ... */ })
    );

Resultados

MetricaAntesDepoisMelhoria
Tempo de resposta8.2s780ms10.5x
Queries executadas3,247399.9%
Memoria utilizada850MB120MB85%

Licoes aprendidas

  1. Sempre meça antes de otimizar - Sem profiling, estariamos chutando
  2. Includes nao sao gratuitos - Cada Include pode multiplicar o problema
  3. Projections > Entidades completas - Retorne apenas o que precisa
  4. Compiled queries para hot paths - O custo de compilacao vale a pena

A melhor otimizacao e aquela que voce nao precisa fazer. Mas quando precisa, va direto ao ponto.

O codigo completo esta disponivel no repositorio de exemplos.

#EF Core#Performance#SQL#APIs
T

Tiago Spana

Software Engineer & Architect

Engenheiro de software com foco em arquitetura de sistemas, cloud-native e DevOps. Construindo sistemas escalaveis em producao.

Gostou do conteudo?

Inscreva-se para receber novos artigos sobre engenharia de software.