Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Por qué sí y por qué no — El problema N+1#

⬅️ Clase 056 · 📚 Parte 4

ORM Por qué sí Por qué no Qué se paga
Prisma No carga sola: el N+1 no puede aparecer por descuido Olvidar include da una relación ausente Un fallo silencioso en vez de uno lento
Entity Framework Core Igual, y con interceptores para contar comandos Igual: sin Include, lista vacía Igual
SQLAlchemy selectinload usa dos consultas: nunca multiplica filas Perezosa por omisión: el N+1 aparece sin escribirlo Vigilar cada acceso a una relación
Hibernate Contador de sentencias nativo; grafos declarativos Perezosa por omisión, y fuera de la sesión lanza excepción Dos fallos distintos según dónde toques la relación

Ninguna de las cuatro te encierra en su estrategia de carga anticipada: AsSplitQuery() en EF Core, joinedload en SQLAlchemy y @Fetch(FetchMode.SUBSELECT) en Hibernate cambian de unión a segunda consulta o al revés con una llamada.

🧭 Los dos valores por omisión, y sus fallos#

No cargar —Prisma, EF Core— convierte el olvido en datos ausentes. La lista llega vacía, el cliente muestra «sin etiquetas», y nadie ve un error. Es un fallo de corrección silencioso.

Cargar al tocar —SQLAlchemy, Hibernate— convierte el olvido en mil consultas. Los datos son correctos y el servicio se arrastra. Es un fallo de rendimiento, visible en el registro de SQL si alguien lo mira.

Y hay un tercer caso, solo en Hibernate: tocar la relación fuera de la sesión lanza una excepción. Es ruidoso, aparece en desarrollo y —contra lo que parece— es el mejor de los tres, porque no se puede ignorar.

💡 Lo que de verdad resuelve esto#

No es elegir ORM: es medir.

Los cuatro traen un contador de consultas, y con él se puede escribir una prueba que falle si una ruta pasa de un número. Eso convierte el N+1 en un fallo de integración continua en lugar de un informe de lentitud seis meses después.

Es la misma idea que Gregg defiende para cualquier problema de rendimiento: instrumentar antes de optimizar, porque sin medición se acaba adivinando —y la intuición sobre dónde está el tiempo casi siempre se equivoca [gregg-systems-performance].

Y explica por qué el contrato de esta clase cuenta consultas en lugar de medir tiempo: el tiempo depende de la máquina; el número de consultas, no.

Con un matiz que costó una ejecución de integración continua descubrir: el número absoluto sí depende del ORM —una consulta con unión, dos con segunda consulta—, así que lo que se fija en la prueba no puede ser ese número. Lo que se fija es que no crezca con las filas, que es lo único que separa un N+1 de una carga anticipada.

Fuentes#