Meça a jornada, não apenas uma linha de código

Registre duração, quantidade de consultas, linhas retornadas e esperas relevantes em um cenário realista. Verifique a consulta gerada pelo EF Core e o plano de execução no banco. Logs de comandos podem expor dados sensíveis; configure parâmetros e logging com cuidado, especialmente em produção.

Compare a mesma operação com volume e distribuição próximos aos reais. Uma consulta rápida com dez linhas pode degradar com milhões; benchmark isolado não substitui métricas de produção e análise de plano.

Evite trazer dados desnecessários

Para leitura, projete apenas colunas utilizadas em vez de materializar agregados inteiros. Se não há alteração a salvar, AsNoTracking reduz trabalho de change tracking. Não aplique indiscriminadamente: tracking é necessário quando a unidade de trabalho precisa persistir as mudanças e pode oferecer identidade consistente dentro do contexto.

var resultado = await db.Pedidos
    .AsNoTracking()
    .Where(p => p.ClienteId == clienteId)
    .OrderByDescending(p => p.CriadoEm)
    .Select(p => new PedidoResumo(p.Id, p.CriadoEm, p.Total))
    .Take(50)
    .ToListAsync(cancellationToken);

Detecte e corrija N+1

N+1 ocorre quando uma consulta busca uma coleção principal e o código dispara uma consulta adicional para cada item. Carregamento lazy pode tornar isso pouco visível. Inspecione quantidade e padrão dos comandos; prefira projeção ou carregamento explícito adequado ao caso.

Carregar várias coleções com joins pode multiplicar linhas e transferir dados duplicados. Compare projeção, split queries e consultas específicas, considerando consistência e round trips. “Eager loading em tudo” também pode ser uma regressão.

Paginação e índices

Paginação por offset é simples e funciona bem para páginas pequenas, mas offsets grandes podem exigir percorrer muitas linhas e os resultados podem mudar entre páginas. Em feeds grandes, keyset pagination usa a última chave vista como cursor, com ordenação estável e índice compatível.

Índices aceleram leituras específicas ao custo de espaço e trabalho adicional em inserções/atualizações. Escolha com base em filtros, ordenação, seletividade e plano de execução. Mais índices nem sempre significam banco mais rápido.

Transações, conexões e concorrência

Consultas lentas podem manter conexões e locks por mais tempo. Use transações com escopo apropriado e evite trabalho de rede enquanto mantém transação aberta. Reveja pool de conexões, concorrência e padrões de acesso antes de adicionar cache; Redis introduz decisões de invalidação e consistência que devem ser justificadas por métricas.

Um roteiro de investigação

  1. Identifique endpoint, percentis de latência e período do problema.
  2. Correlacione traces e logs com as consultas executadas.
  3. Analise SQL e plano real; confirme linhas estimadas versus observadas.
  4. Forme uma hipótese pequena: índice, projeção, paginação ou eliminação de N+1.
  5. Meça novamente sob carga e acompanhe possíveis regressões.

Para comparar fornecedores e comportamento de banco, veja SQL Server e PostgreSQL para desenvolvedores .NET. Para instrumentação, veja observabilidade. O guia prático de performance em .NET conecta esse diagnóstico a benchmarks, runtime e validação sob carga.

Leituras relacionadas