en
· 4 min de leitura

O ORM está bem. Suas queries não.

Todo banco lento que audito tem a mesma forma: um ORM culpado por queries que ninguém leu. Leia o SQL. Conserte o padrão.

fullstackdata

Em algum ponto da vida de todo projeto, alguém propõe remover o ORM porque o banco está lento. Já fui chamado para revisar essa proposta muitas vezes. Concordei com ela uma vez.

O ORM quase nunca é o problema. O problema é que o ORM tornou fácil escrever queries que ninguém nunca leu, e os padrões que essas queries não lidas produzem são os mesmos que seriam lentos em SQL escrito à mão. Conserte os padrões e o ORM fica bem. Remova o ORM sem consertar os padrões e o time vai escrever as mesmas queries lentas à mão, com mais bugs.

Os padrões que estão sempre lá

O primeiro é N+1. Uma lista de cinquenta pedidos, e para cada um uma query para o cliente. O ORM escondeu o loop atrás de um acesso a propriedade, e o log mostra cinquenta e uma queries por página. A correção é declarar a relação na query para que vire um join ou dois selects em lote. Todo ORM tem um jeito. Ninguém usou porque ninguém olhou o log.

O segundo é buscar demais. Select de tudo de uma tabela com uma coluna JSON e uma coluna de texto, para renderizar uma lista que mostra o nome. A rede carrega megabytes por requisição e o banco lê páginas que não precisava. Selecione as colunas que você renderiza, e o ORM vai gerar isso de bom grado.

O terceiro é filtrar em código de aplicação. Buscar mil linhas, filtrar para vinte em JavaScript. O banco tem um índice para isso. Ele nunca teve a chance de usar.

O quarto é o índice ausente, que nenhum ORM pode adicionar por você. Um where em uma coluna sem índice é um sequential scan, rápido com dez mil linhas e uma catástrofe com dez milhões. O ORM não causou isso. A ausência de revisão do plano de query causou.

Leia o SQL

Meu primeiro passo em qualquer reclamação de banco é ligar o log de queries por uma hora de tráfego de produção e ordenar por tempo total. Não por média, por total: uma query de dois milissegundos que roda um milhão de vezes é a que está te machucando, e ela é invisível em um slow query log com limite de um segundo.

Depois pego as cinco primeiras e rodo com o plano explicado. Em quase todo caso, as cinco primeiras respondem pela maior parte da carga, e pelo menos três delas são um dos quatro padrões acima. É um dia de trabalho para consertar, e geralmente encerra a conversa sobre remover o ORM.

Deixo esse log ligado permanentemente de forma amostrada. Uma query que estava bem no trimestre passado e está lenta agora mudou porque os dados mudaram, e você quer saber a semana em que aconteceu, não o mês em que um cliente notou.

Para que o ORM serve de verdade

Um ORM merece o lugar dele tornando a query comum segura e tipada. Inserir um registro e receber um objeto tipado de volta. Atualizar por id. Carregar um agregado com seus filhos. Isso são noventa por cento das queries de um produto, e escrever à mão é onde moram o SQL injection e a coluna digitada errado.

Os outros dez por cento são queries de relatório, agregações complexas, window functions, qualquer coisa com common table expression. Essas eu escrevo em SQL, à mão, com resultado tipado, e mantenho em um módulo ao lado do código do ORM. Os dois coexistem sem conflito. O que não funciona é forçar o ORM a expressar uma query para a qual não foi feito, porque o resultado é ilegível e lento, e faz o ORM parecer culpado.

A única vez em que concordei

O caso em que remover o ORM era certo envolvia um pipeline de dados que processava milhões de linhas em lotes, e a alocação de objeto por linha do ORM era o gargalo. Isso é um problema de throughput, não de query, e é raro. Se sua aplicação serve requisições web, não é o seu caso.

Leia o SQL que o seu ORM gera. Adicione os índices que o plano pede. Agrupe as relações. Depois decida se a ferramenta algum dia foi o problema. Não foi.