Executando verificação de segurança...
5

O "Assassino Silencioso" de Performance: O Problema N+1 (e como resolvê-lo no REST e GraphQL)

Se você já viu uma aplicação funcionar perfeitamente em localhost com 10 registros, mas se arrastar em produção com 10.000, há uma grande chance de você ter sido vítima do Problema N+1.

Recentemente, mergulhei em diversas discussões sobre arquitetura de APIs e otimização de banco de dados. O que encontrei foi um consenso: o N+1 não é apenas um "glitch" técnico, é um erro arquitetural fundamental que afeta tanto REST quanto GraphQL, mas de formas diferentes.

O que é exatamente?

O cenário clássico: Você quer listar posts de um blog e o autor de cada post.

código do brasil api
  1. A Query Inicial (1): Você busca os posts (SELECT * FROM posts). Retorna 100 registros.
  2. O Loop (N): Para cada um dos 100 posts, seu código (ou ORM) faz uma nova query para buscar o autor (SELECT * FROM authors WHERE id = ?).

Resultado: 101 queries para uma tarefa que deveria levar 1 ou 2. Em um ambiente de nuvem, onde latência e I/O custam dinheiro, isso é queimar orçamento à toa.

N+1 no REST vs. GraphQL

Embora o problema seja o mesmo, a manifestação e a solução mudam drasticamente dependendo da sua arquitetura.

No REST (e ORMs clássicos)

No mundo REST, o problema geralmente nasce do uso ingênuo de ORMs (como Hibernate, TypeORM ou Prisma) e Lazy Loading.

  • Cenário: Você tem um endpoint GET /posts. Seu serializer JSON itera sobre os objetos e, ao tocar na propriedade post.author, o ORM dispara uma query silenciosa.
  • A Solução Clássica (Eager Loading): A maioria dos ORMs resolve isso com um simples .include() ou JOIN FETCH.
    • Exemplo (SQL mental): Em vez de N queries, você faz um JOIN e traz tudo de uma vez.
    • Trade-off: Você pode acabar trazendo dados demais (Over-fetching) se não cuidar dos campos selecionados.

No GraphQL: O Buraco é Mais Embaixo

O GraphQL foi criado pelo Facebook em 2012 justamente para resolver o over-fetching e o under-fetching do REST em redes móveis ruins. Ironicamente, ele tornou o problema N+1 mais difícil de detectar no backend.

Como cada campo no GraphQL é resolvido por uma função independente (resolver), o servidor não sabe naturalmente que você vai pedir os autores de uma lista de posts até que ele comece a executar a query.

  • Se você pede posts { author { name } }, o resolver de posts roda uma vez, mas o resolver de author roda N vezes.

Como Resolver de Verdade?

Não basta "fazer funcionar", precisa escalar. Aqui estão as estratégias modernas baseadas na arquitetura que você usa:

1. O Padrão DataLoader (A Bala de Prata do GraphQL)

Se você usa GraphQL (Node, Java, Python), o DataLoader é obrigatório.
Ele age como um "pedágio" que retém as requisições dos resolvers. Em vez de ir ao banco imediatamente, ele espera um "tick" do event loop, agrupa todos os IDs de autores solicitados e faz uma única query:
SELECT * FROM authors WHERE id IN (1, 2, 5, ...)
Depois, ele distribui os resultados de volta para os resolvers corretos. Isso transforma N+1 queries em 1+1.

2. "Look-ahead" e Join Strategy (Prisma e Modern ORMs)

Ferramentas modernas como o Prisma evoluíram. Antigamente, o Prisma sofria críticas por fazer muitas queries separadas. Agora, com a relationLoadStrategy: 'join', ele consegue forçar um SQL JOIN único no nível do banco de dados, sendo muito mais eficiente em ambientes de alta latência.

3. Soluções Híbridas (Breadth-First Loading)

Uma abordagem interessante vinda do WunderGraph (DataLoader 3.0) sugere resolver a árvore de dados em "largura" (Breadth-First) em vez de profundidade. Isso permite carregar todos os "autores" de todos os níveis da árvore de uma só vez, reduzindo a complexidade de concorrência e o uso de threads.

Resumo para Seniores (e aspirantes)

A diferença entre um Júnior e um Sênior muitas vezes não é saber sintaxe, é entender o custo de uma linha de código.

  • Júnior: Faz o código funcionar. O endpoint retorna o JSON correto.
  • Pleno: Usa include ou select_related no ORM para otimizar queries conhecidas.
  • Sênior: Implementa guardrails arquiteturais. Adiciona testes automatizados que falham se o número de queries exceder um limite (Query Count Budgeting) e monitora APMs (como Datadog ou New Relic) para pegar N+1 que escapou para produção.

Você já teve que refatorar uma API inteira por causa disso? Qual estratégia sua equipe usa hoje?


Fontes de referência:

  1. Architect's dilemma: When to choose GraphQL over REST + why? - Hasura
    https://hasura.io/blog/architects-dilemma-when-to-choose-graphql-over-rest-and-why

  2. Solving the N+1 Problem with DataLoader - GraphQL.js
    https://www.graphql-js.org/docs/n1-dataloader/

  3. Dataloader 3.0: A new algorithm to solve the N+1 Problem - WunderGraph
    https://wundergraph.com/blog/dataloader_3_0_breadth_first_data_loading

https://github.com/filipedeschamps/tabnews.com.br/pull/2011

Carregando publicação patrocinada...