search

GraphQL: DataLoader, N+1 Problem e Federation

GraphQL: DataLoader, N+1 Problem e Federation

GraphQL virou um padrão de fato para muitas equipes que precisam entregar APIs flexíveis, especialmente em plataformas digitais com múltiplos clientes (web, mobile, parceiros). Mas, ao mesmo tempo em que o modelo de consulta do graphql melhora a experiência de consumo, ele também torna mais fácil cair em armadilhas de performance e arquitetura — com destaque para o N+1 Problem e para desafios de escala organizacional quando a API cresce.

Este artigo explica, de forma prática, três pilares para lidar com esse cenário:

  • O que é o N+1 Problem em graphql e como identificá-lo
  • Como o DataLoader ajuda com batching e caching por requisição
  • O que é GraphQL Federation, quando faz sentido e quais cuidados de segurança observar

N+1 Problem em GraphQL: o que é e por que acontece

O N+1 Problem ocorre quando uma única consulta do cliente dispara:

  1. Uma query para buscar uma lista (o “1”)
  2. Mais N queries (ou chamadas) para buscar dados relacionados para cada item da lista (o “N”)

Em graphql, isso aparece com frequência por causa de resolvers encadeados. Um exemplo típico:

  • O cliente pede users { id name posts { id title } }
  • O resolver de users busca a lista de usuários (1 consulta)
  • Para cada usuário, o resolver de posts faz uma consulta separada (N consultas)

Sintomas comuns

  • A API “funciona” com poucos dados, mas degrada rapidamente em produção
  • Picos de latência quando consultas pedem campos relacionais
  • Aumento do consumo do banco (CPU/IO) e saturação de conexões
  • Logs com muitas queries repetidas (por exemplo, SELECT ... WHERE user_id = ? em loop)

Como confirmar o N+1 na prática

Um passo a passo comum para diagnosticar:

  1. Ative logs de queries no banco (ou no ORM) com correlação por request.
  2. Rastreie a requisição graphql com um requestId.
  3. Execute uma query que retorna muitos itens com um relacionamento.
  4. Conte quantas consultas ao banco acontecem:
    • Se a contagem cresce linearmente com o tamanho da lista, é N+1.

Em ambientes com observabilidade, é útil medir:

  • número de queries por requisição
  • tempo total em banco
  • p95/p99 de latência por operação graphql

DataLoader: batching e cache por requisição

O DataLoader é uma biblioteca popular (originalmente do ecossistema do Facebook) usada para mitigar N+1 por meio de duas estratégias:

  • Batching: agrupar várias cargas do mesmo tipo em uma única consulta
  • Caching: reutilizar resultados dentro da mesma requisição para evitar recomputação e chamadas duplicadas
DataLoader

A ideia central: em vez de cada resolver fazer uma consulta imediata, ele “agenda” uma carga. O DataLoader coleta várias dessas cargas durante o ciclo do event loop e executa uma operação em lote.

Como o batching resolve o N+1

Em vez de:

  • SELECT * FROM posts WHERE user_id = 1
  • SELECT * FROM posts WHERE user_id = 2
  • SELECT * FROM posts WHERE user_id = N

Você faz:

  • SELECT * FROM posts WHERE user_id IN (1,2,...,N)

Depois, o DataLoader reorganiza os resultados para devolver ao resolver o array correspondente a cada chave.

Passo a passo de implementação (visão geral)

  1. Crie loaders por tipo de entidade/consulta (ex.: postsByUserIdLoader).
  2. Instancie os loaders por requisição, não como singleton global.
  3. Injete loaders no contexto do graphql (ex.: context.loaders).
  4. Nos resolvers, use load()/loadMany() em vez de buscar diretamente no banco.

Esse detalhe “por requisição” é crítico para evitar vazamento de dados entre usuários e para manter o cache limitado ao request atual.

Cuidados importantes com cache e segurança

Em cyber segurança, DataLoader merece atenção por dois motivos:

  1. Risco de vazamento por cache global: se o loader for compartilhado entre requisições (singleton), o cache pode servir dados de um usuário para outro, especialmente quando as chaves não incorporam o escopo de autorização (ex.: userId em multi-tenant).
  2. Autorização em nível de campo: graphql incentiva consultas complexas; se a autorização ficar “espalhada” e for aplicada depois do batch, você pode carregar dados demais e filtrar tarde, gerando exposição acidental em logs, traces ou erros.

Boas práticas:

  • Crie loaders no context por request.
  • Inclua o tenantId/escopo quando necessário na chave do cache (ou separe loaders por tenant).
  • Valide autorização antes de retornar dados (e, quando possível, antes de buscar).
  • Evite mensagens de erro detalhadas com dados sensíveis em produção.

DataLoader não é “cura para tudo”

DataLoader ajuda principalmente em cargas por chave (ex.: por IDs). Para casos como:

  • filtros complexos,
  • ordenações por agregações,
  • paginação avançada, o ideal é projetar resolvers que façam consultas otimizadas (por exemplo, com joins, views, materializações, ou query builders bem definidos) e considerar limites de complexidade.

Quando GraphQL Federation entra em cena

Se o DataLoader resolve um problema de performance em tempo de execução, GraphQL Federation ataca um problema de arquitetura e escala organizacional: quando a API graphql cresce a ponto de ser mantida por múltiplos times e domínios.

Em vez de um “monolito graphql”, a federação permite que você tenha:

  • Subgraphs (serviços graphql menores) responsáveis por um domínio (ex.: users, catalog, billing)
  • Um gateway que compõe um schema único para o cliente

O cliente continua vendo um único endpoint graphql, mas internamente o gateway roteia partes da query para os subgraphs necessários e junta o resultado.

Benefícios típicos

  • Autonomia por domínio: cada time evolui seu subgraph sem coordenar mudanças em um repositório único.
  • Escala de desenvolvimento: evita gargalos de PR e releases centralizados.
  • Separação de responsabilidade: domínios com ciclos diferentes (ex.: catálogo vs. pagamentos) podem ter deploys independentes.

Custos e complexidade que precisam ser considerados

Federation é útil, mas adiciona camadas:

  • Mais hops de rede (gateway → subgraph), com impacto em latência.
  • Observabilidade mais difícil (tracing distribuído passa a ser obrigatório).
  • Governança de schema e compatibilidade (breaking changes ficam mais perigosas).
  • Possibilidade de reintroduzir N+1 “entre serviços” se a composição exigir muitas chamadas pequenas.

DataLoader e Federation: como eles se combinam

É comum precisar dos dois:

  • Dentro de um subgraph, DataLoader reduz N+1 no acesso a banco/serviços internos.
  • No nível do gateway, você precisa cuidar para não criar padrões de consulta que disparem múltiplas resoluções remotas redundantes.

Um cenário frequente em federação:

  • Uma entidade é “estendida” em outro subgraph (ex.: User definido em users, mas estendido em billing com invoices).
  • O gateway pode precisar consultar users para resolver referências e depois billing para completar campos.

Se essas resoluções remotas ocorrerem item a item, o N+1 aparece como N+1 de rede. Dependendo da stack, é importante garantir que haja batching para resoluções de entidades (por exemplo, resolvers que aceitam listas de chaves) e que o gateway/subgraphs estejam configurados para agrupar operações quando possível.

Controles de segurança recomendados em APIs GraphQL (especialmente federadas)

GraphQL amplia a superfície de ataque por permitir consultas profundas e altamente customizáveis. Em federação, isso se soma à complexidade distribuída.

APIs GraphQL

Controles práticos:

  1. Limite de complexidade e profundidade

    • Defina custo por campo e limite por operação.
    • Bloqueie recursão e profundidades exageradas.
  2. Persisted Queries (quando aplicável)

    • Reduz exploração por ad hoc queries em produção.
    • Ajuda a controlar o que pode ser executado.
  3. Rate limiting e quotas por consumidor

    • Por token, por IP, por app, por tenant.
    • Diferencie limites para operações caras.
  4. Autorização consistente

    • Padronize autenticação/claims no gateway e repasse confiável aos subgraphs.
    • Garanta autorização em nível de objeto/campo quando necessário.
  5. Observabilidade e auditoria

    • Logging estruturado por operação (nome da operação, complexidade, duração).
    • Tracing distribuído (gateway e subgraphs).
    • Alertas para spikes de complexidade/latência.
  6. Hardening de introspecção

    • Em produção, avalie restringir introspecção para clientes não confiáveis, ou condicionar por ambiente/role.

Como decidir: DataLoader, Federation ou ambos?

Um guia objetivo:

  • Você tem latência alta e muitas queries repetidas por request, principalmente em relacionamentos?
    → Priorize DataLoader e revisão de resolvers.

  • Você tem múltiplos times, domínios bem definidos e um schema grande difícil de evoluir em um único serviço?
    → Considere GraphQL Federation (com gateway, governança e observabilidade).

  • Você tem federação e sofre com excesso de chamadas entre serviços e lentidão em campos “estendidos”?
    → Combine Federation + batching (estratégias semelhantes ao DataLoader, mas também para resoluções remotas).

Conclusão

Em graphql, performance e arquitetura caminham juntas. O N+1 Problem é uma consequência comum do modelo de resolvers e precisa ser tratado cedo, com ferramentas como DataLoader (batching e cache por requisição) e com desenho cuidadoso de consultas e autorização.

Quando a API cresce em escopo e equipes, GraphQL Federation permite escalar o desenvolvimento com subgraphs por domínio — desde que você aceite a complexidade adicional em observabilidade, governança e segurança. Na prática, sistemas maduros costumam usar os dois: DataLoader para eficiência local e Federation para escala organizacional, com controles claros para evitar N+1 “de rede” e reduzir a superfície de ataque.

Tags: Cursos
Compartilhar este artigo: