Yeda AI Tips · #181

English

Haz grep del código de datos escrito por IA para cazar el N+1

Hay un bug de una línea que hace tu API cincuenta veces más lenta.

Pasa el review porque cada línea parece razonable. Traes una lista de cosas y luego iteras sobre ellas para enriquecer cada una, y en algún punto de ese bucle hay una llamada a la base de datos. Trae 50 órdenes y luego consulta el cliente por cada orden: eso son 1 + 50 = 51 viajes de ida y vuelta donde 1 o 2 bastarían. Este es el problema de la consulta N+1, y los asistentes de IA lo producen constantemente porque escribir "consulta dentro del bucle" es la forma más obvia de expresar "obtén los datos relacionados".

Por qué la IA escribe N+1 tan seguido

El patrón N+1 es el código localmente más simple. Pide "adjunta el autor de cada post" y la completación natural es un bucle con una búsqueda dentro:

posts = db.query("SELECT * FROM posts WHERE blog_id = ?", blog_id)  # 1 consulta
for post in posts:
    # +1 consulta por post — este es el N
    post.author = db.query("SELECT * FROM users WHERE id = ?", post.author_id)

Cada línea es correcta de forma aislada, así que nada se ve mal en el diff. El costo solo aparece a escala: con 500 posts son 501 consultas, cada una con su propio viaje de red, y el endpoint que era ágil en tu base de datos de prueba de 10 filas colapsa con datos reales.

Haz grep antes de mergear

Conviértelo en un reflejo de review. Antes de mergear código de obtención de datos escrito por IA, busca una llamada a base de datos o red que esté dentro de un bucle.

# heurística aproximada: una llamada query/find/fetch cerca de cuerpos de bucle
grep -rnE '(for |forEach|map\()' src | ...   # luego revisa los cuerpos de los bucles
grep -rnE '\.(query|find|fetch|get|select)\(' src

Ningún regex atrapa todos los casos, así que la verdadera comprobación es leer cada bucle y preguntar: ¿el cuerpo de este bucle habla con la base de datos u otro servicio? Si es así, ese es tu N+1.

Arréglalo con un solo viaje

Reemplaza la búsqueda por fila con una sola consulta que traiga los datos relacionados de una vez: un JOIN, un WHERE id IN (...), o el hook de carga anticipada (eager loading) de tu ORM:

# SQL: una consulta con un join
rows = db.query("""
  SELECT posts.*, users.name AS author_name
  FROM posts JOIN users ON users.id = posts.author_id
  WHERE posts.blog_id = ?""", blog_id)
# ORMs: carga anticipada de la relación
# SQLAlchemy
posts = session.query(Post).options(selectinload(Post.author)).all()
# Django
posts = Post.objects.select_related("author").all()
# Prisma
posts = await prisma.post.find_many(include={"author": True})

Un viaje en vez de cientos. El endpoint deja de caerse con datos reales, y lo atrapaste en review en lugar de en un incidente de producción.

Movidas avanzadas

Recursos

¿Desarrollas sistemas de IA o en la nube? Yeda AI audita y refuerza pipelines de LLM y de nube en producción. Hablemos · Lee el blog