trilha

System Design/03 - Qualidade/Performance Antipatterns2 min

Extraneous Fetching

Perguntas-guia
  • Que problema isso resolve?
  • Quando usar / quando NÃO usar?
  • Qual o principal trade-off?
  • Como isso falha em produção?

Conceito

Buscar mais dados do que se usa. É o antipadrão espelhado de Chatty IO: em vez de muitas idas pequenas, poucas idas desnecessariamente grandes.

Formas comuns

Forma Custo
SELECT * Traz colunas não usadas; impede covering index
Filtrar na aplicação O banco lê tudo, a aplicação descarta
Sem paginação Carrega a tabela inteira na memória
Agregar na aplicação SUM de milhões de linhas no código
API sem projeção Cliente recebe 40 campos, usa 3
Eager loading excessivo Carrega o grafo de objetos inteiro

Por que SELECT * é pior do que parece

Além de trafegar colunas inúteis, ele impede que o banco use um covering index — um índice que contém todas as colunas da consulta e dispensa tocar a tabela. Uma consulta que poderia ser resolvida só no índice passa a fazer acesso aleatório ao heap por linha.

Em tabelas com TEXT ou BLOB, o efeito é dramático: uma coluna que ninguém pediu domina a transferência.

Trade-offs

A correção é projeção explícita, filtro e agregação no banco, e paginação — mas ela também tem limite.

Projetar campos demais cria acoplamento: cada tela precisa de uma consulta própria, e mudanças de UI viram mudanças de backend. É exatamente a tensão que GraphQL tenta resolver, e que Backends for Frontend resolve por outro caminho.

Filtrar no banco é quase sempre certo — exceto quando o filtro é caro e não indexável, e o resultado seria melhor calculado uma vez e cacheado.

Paginação por OFFSET corrige o consumo de memória e introduz outro problema: OFFSET 100000 faz o banco descartar 100 mil linhas antes de devolver 20, e o resultado é instável sob escrita concorrente. Paginação por cursor é a forma correta.

Exemplo prático

-- traz 500 MB para calcular um número
SELECT * FROM vendas WHERE ano = 2026;
-- aplicação: soma o campo total

-- traz 8 bytes
SELECT SUM(total) FROM vendas WHERE ano = 2026;

O segundo caso não é apenas mais rápido na rede: o banco resolve a agregação com um índice, em paralelo, sem materializar as linhas. A aplicação não aloca nada.

A versão ingênua tem um agravante escondido: ela funciona bem nos primeiros meses e degrada continuamente conforme a tabela cresce — até o dia em que o processo morre por falta de memória, sem que nenhuma linha de código tenha mudado.

Relacionado


Parte de Performance Antipatterns · roadmap.sh/system-design

Buscar

Busca por título, seção e texto das notas