System Design/03 - Qualidade/Performance Antipatterns2 min
Extraneous Fetching
- 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