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.
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
| Metrica | Antes | Depois | Melhoria |
|---|---|---|---|
| Tempo de resposta | 8.2s | 780ms | 10.5x |
| Queries executadas | 3,247 | 3 | 99.9% |
| Memoria utilizada | 850MB | 120MB | 85% |
Licoes aprendidas
- Sempre meça antes de otimizar - Sem profiling, estariamos chutando
- Includes nao sao gratuitos - Cada Include pode multiplicar o problema
- Projections > Entidades completas - Retorne apenas o que precisa
- 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.
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.