FNSP Web Page

O N+1: uma consulta que se multiplica sem se notar

É o problema de desempenho mais comum em qualquer aplicação com base de dados, e o mais difícil de ver a olho nu — porque o código que o causa parece perfeitamente razoável.

const artigos = await db.artigos.findMany({ take: 20 });

for (const artigo of artigos) {
  artigo.autor = await db.autores.findUnique({ where: { id: artigo.autorId } });
}

São 21 consultas: uma para a lista, mais uma por cada artigo. Daí o nome — N+1.

Com a base na mesma máquina, cada uma custa um milissegundo e ninguém nota. Com a base noutro servidor, cada uma custa dez, e a página passa a demorar duzentos milissegundos a mais do que devia. É sempre em produção que se descobre.

1. Pedir tudo de uma vez

Qualquer ORM tem forma de trazer as relações na mesma consulta:

const artigos = await db.artigos.findMany({
  take: 20,
  include: { autor: true },
});

Uma consulta, ou duas — a diferença é entre um número fixo e um número que cresce com os resultados.

2. Quando não dá para juntar

Se os dados vierem de sítios diferentes, o padrão é recolher os ids e fazer uma consulta com IN:

const ids = [...new Set(artigos.map((a) => a.autorId))];

const autores = await db.autores.findMany({ where: { id: { in: ids } } });
const porId = new Map(autores.map((autor) => [autor.id, autor]));

for (const artigo of artigos) {
  artigo.autor = porId.get(artigo.autorId);
}

Duas consultas, independentemente de serem 20 artigos ou 20 000. O Set à volta dos ids evita pedir o mesmo autor cinco vezes.

3. Como se apanha

A olho, procura-se um await dentro de um ciclo. Em código real, o mais fiável é ligar o registo de SQL e olhar para o que sai:

// Prisma
new PrismaClient({ log: ['query'] });

Se ao carregar uma página aparecerem vinte linhas quase iguais na consola, está encontrado. É um daqueles casos em que a ferramenta mostra em dez segundos o que uma revisão de código não vê.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.