Cómo evitar volar tu DB por los aires

Esto posiblemente deba ser una charla en vivo… Pasa más seguido de lo que debería. En realidad, es muy fácil escribir código y consultas que generen cuellos de botella el minuto que la aplicación comienza a tener usuarios reales.

Si el sistema está lento, es muy probable que al revisar el monitor de recursos la base de datos esté al 100% de uso. Es un síntoma clásico de problemas de diseño o implementación a nivel de consultas, acceso a datos o estructura de índices.

Veamos los principales factores que impactan en la performance de una base de datos, incluyendo tanto SQL como ORMs como Entity Framework (EF), buenas prácticas de optimización, y consejos que aplican tanto para programadores como para arquitectos e ingenieros. Estos consejos son válidos para cualquier motor de base de datos, ya que son conceptos básicos y comunes a todos.

Las bases de datos están optimizadas para manejar grandes volúmenes de datos, pero las consultas SQL no son gratuitas y por lo tanto no son infinitamente escalables. Cada consulta consume CPU, memoria, disco y/o red. Cuando el sistema crece, las consultas mal diseñadas, los accesos repetitivos e innecesarios o la falta de índices pueden saturar la instancia.

Los síntomas incluyen consultas que tardan segundos en responder, lecturas de millones de registros para traer pocos resultados (puede observarse como un uso de I/O elevado), y locks o bloqueos frecuentes (consultas que no responsen hasta que otras consultas terminan).

Lo fundamental es usar índices. Un índice en una base de datos es como el índice de un libro: Permite ir directamente a una página sin tener que leer todo. Sin índices adecuados, la base tiene que hacer table scans, revisar fila por fila hasta encontrar lo que busca. Buenos hábitos comienzan con crear índices en las columnas usadas para filtrar (WHERE), relacionar (JOIN) u ordenar (ORDER BY), incluyendo índices compuestos si filtramos por más de una columna. Pero no hay que abusar, demasiados índices también impactan las escrituras, por lo que no debemos crear índices por las dudas.

Un análisis con herramientas como EXPLAIN o el Query Plan en SQL Server ayudan a entender cómo se está ejecutando una consulta.

Entity Framework vs SQL crudo

Los ORM son herramientas fantásticas para abstraer el acceso a datos. Acelera el desarrollo, reduce errores y mejora la mantenibilidad. Pero no es magia.

Sin embargo, cuando no se usa bien, o sin entender cómo funciona, puede generar SQL subóptimo (lento, etc.), sobrecargar la memoria, y hacer algunas operaciones más complejas de lo necesario.

Comparado con SQL crudo, un ORM puede ser más lento en escenarios complejos. Pero muchas veces la lentitud no es culpa del ORM, sino del uso poco eficiente que hacemos del mismo.

Algunos consejos para optimizar EF y otros ORM:

Entender cuándo los resultados van a ser cargados en memoria: En cuanto se convierte la consulta a un tipo de array (al usar AsEnumerable() o ToArray(), por ejemplo), se ejecutará la consulta y se leerán todos los datos. Por lo tanto, si es inevitable usarlos, debe ser luego de haber filtrado y paginado. Una alternativa muy interesante en EF es usar en su lugar AsAsyncEnumerable() + await foreach que leerá una fila a la vez.

Tracking por defecto: EF guarda el estado de cada entidad que recupera. Esto está bueno si vas a hacer cambios y guardarlos, pero si solo estás leyendo datos, es un desperdicio de memoria. Usar AsNoTracking() cuando no se necesitará hacer updates.

Consultas demasiado amplias: EF tiende a traer muchas columnas si no indicás lo contrario. Debemos seleccionar solo lo necesario con Select().

N+1 queries: Si accedemos a propiedades relacionales (valores que provienen de otro modelo o tabla) sin incluirlas explícitamente con Include()), EF puede hacer una consulta adicional por cada entidad relacionada, en lugar de hacer un JOIN.

Filtros tardíos: Si filtrás después de traer los datos, estás cargando de más. Siempre filtrá lo más pronto posible.

En SQL directo, aplican los mismos principios. De hecho, si aprendemos cómo el ORM funciona internamente, podemos transladar esto de uno a otro, la clave, como siempre en ingeniería, es aprender a pensar el problema.

Filtrar lo antes posible: Evitá traer grandes volúmenes de datos para después filtrarlos en memoria.

Proyectá (seleccioná) lo necesario: No hagas SELECT *, por favor.

Paginá los resultados: Nunca cargues toda una tabla para mostrar una lista paginada. Usá LIMIT, OFFSET o Skip().Take() en EF.

Usá joins eficientemente: Evitá joins innecesarios y asegurate de que las columnas usadas en los joins estén indexadas.

Ordená solo cuando sea necesario: Evitalo si no lo necesitás, y si lo necesitás hacelo por columnas indexadas y habiendo filtrado todo lo que no debe estar en la lista (por ejemplo, excluyendo elementos eliminados).

Cacheá valores: Evitá todo lo posible pedir a la base de datos que manipule datos (truncar, dividir, mayúsculas, etc.). A veces, aunque se haga en código, tampoco tiene sentido manipular o calcular datos en cada solicitud. Usá columnas con el dato listo para filtrar, que se actualicen cuando se modifique la fila o periódicamente, o caché en memoria (variables, Redis).

Cacheá consultas: Algunos resultados que no varíen según el usuario, o no varíen frecuentemente, pueden quedar en memoria, Redis, o un caché global de la página completa.

Evitá consultas repetidas: No hagas una consulta por ítem en una lista. Agrupá, unificá, y traé todo junto.

Monitorizá el consumo: Usá logs de consultas lentas, métricas de CPU, disco y conexiones. Revisá logs de errores. No optimices a ciegas.

En conclusión, la base de datos no tiene por qué ser el cuello de botella de tu sistema. Pero para evitarlo, hay que entender cómo funciona, qué decisiones impactan en su rendimiento, y cómo evitar errores comunes tanto en Entity Framework como en SQL puro.

La buena noticia es que muchas veces no necesitás cambiar de stack ni hacer una reescritura total. Optimizar es iterar. Y muchas veces, con pequeños cambios podés obtener mejoras significativas.

Publicado el

en

, ,

¿Querés seguir la conversación?