En este artículo, exploraremos diferentes métodos para optimizar una base de datos MongoDB. Analizaremos los síntomas clave en las métricas que indican problemas de rendimiento y cómo solucionarlos para aplicar el método adecuado.
Utilizaremos como ejemplo una aplicación web básica similar a Medium, donde los usuarios pueden leer, escribir artículos y comentar. Para este ejemplo, manejamos tres colecciones en MongoDB:
- Usuarios: incluye el email y el nombre del usuario.
- Posts: contiene el título, el contenido, el ID del usuario que lo escribió y la fecha de creación.
- Comentarios: almacena el contenido del comentario, el ID del usuario que lo realizó y el ID del post donde se publicó.
Con este contexto, vamos a detallar paso a paso los diferentes métodos disponibles para escalar una base de datos MongoDB de manera efectiva.
Paginación de Queries de Lectura en MongoDB
A medida que más usuarios se registran y se crean nuevos posts en la aplicación, la colección de posts en MongoDB crece hasta alcanzar 100.000 documentos. Este crecimiento provoca que la vista principal, donde se descubren los posts más recientes, se vuelva más lenta. Tras analizar el problema, observamos que estamos recuperando todos los posts almacenados en la base de datos.
Para mejorar el rendimiento de las queries y evitar acceder a todos los datos de golpe, implementamos paginación. Esto nos permite cargar solo una parte de los datos necesarios en cada consulta, utilizando las opciones skip y limit de MongoDB. Con esta estrategia, la velocidad de lectura de datos mejora significativamente al no recuperar todos los documentos de una sola vez.
Este enfoque se denomina paginación por offset. En cada nueva página, indicamos a MongoDB que ignore los primeros X elementos, lo que requiere escanear esos primeros documentos hasta llegar a los datos solicitados. Aunque esta técnica es útil, en colecciones grandes, como una de 10 millones de documentos, puede tardar más en recuperar las últimas páginas debido al escaneo previo de todos los elementos anteriores.
Utilización de Índices para Incrementar el Rendimiento en MongoDB
A medida que nuestra aplicación crece, hemos añadido filtros para visualizar los blog posts por categoría, rango de fechas y ordenación por fecha de publicación. A pesar de tener implementada la paginación, la base de datos sigue tardando en devolver los resultados.
Diagnóstico del Problema de Rendimiento en MongoDB
Para diagnosticar el problema, revisamos las métricas de nuestro clúster de MongoDB. Si usamos MongoDB Atlas, podemos ver las métricas de hardware de nuestros nodos directamente desde la web.
Al ejecutar una consulta para obtener los posts de una categoría específica ordenados por fecha, notamos que la CPU se dispara. Utilizando el comando explain, observamos que el ratio entre las claves escaneadas y los documentos retornados es superior a 1000. Esto indica la necesidad de aplicar un índice en la colección sobre los campos utilizados para los filtros y la ordenación.
¿Por Qué Necesitamos Índices en MongoDB?
El motivo de este comportamiento es que, sin un índice, MongoDB tiene que escanear toda la colección para encontrar los documentos que coinciden con los filtros, lo que aumenta el tiempo de respuesta. Si solicitamos 50 posts por página, la consulta puede tardar bastante si los documentos coincidentes están al final de la colección.
Tipos de Índices en MongoDB
Los índices en MongoDB son estructuras de datos que mejoran la velocidad de lectura al organizar las claves de acuerdo con el orden que especifiquemos. Esto permite que MongoDB escanee una menor cantidad de documentos al aplicar los filtros. Existen varios tipos de índices, incluidos los de un solo campo, múltiples campos, texto y geoespaciales.
Beneficios de Aplicar un Índice
Con un índice correctamente aplicado, el ratio de claves escaneadas a documentos retornados debería reducirse considerablemente, mejorando el rendimiento de las queries.
Sharding en MongoDB
En algún punto, la máquina de nuestra base de datos empezará a utilizar bastantes recursos de hardware, incluso teniendo paginación por todas partes y con todas las queries indexadas. En este punto, ya estamos limitados por el hardware disponible, por lo tanto, debemos decidir entre incrementar el tamaño de la máquina (escalado vertical) o incluir una nueva máquina del mismo tamaño para balancear el espacio y los recursos entre dos o más máquinas (escalado horizontal).
Para nuestra aplicación de ejemplo, decidimos optar por el escalado horizontal, añadiendo otra máquina de MongoDB para guardar nuestros datos. Una vez añadida, para balancear los datos y definir dónde se almacenan y se obtienen, necesitaremos realizar algunos pasos de configuración.
¿Qué es la Shard Key?
La shard key es una clave que MongoDB utiliza para particionar los datos en pequeños chunks. Estos chunks se reparten equitativamente entre las diferentes máquinas que tengamos contratadas en nuestro clúster.
Configuración del Sharding
Para poder habilitar el particionado de nuestras colecciones, primero tendremos que configurar la nueva máquina para conectarla con la máquina actual. Si tenemos un servicio autogestionado como MongoDB Atlas, el proceso es tan sencillo como contratar la nueva máquina. En caso de tener nuestros propios servidores, debemos configurar nuestro clúster para incluir la nueva máquina como un segundo shard.
Habilitación del Sharding
Para habilitar el particionado de la base de datos, debemos escoger una shard key y utilizar el comando shardCollection, donde indicamos nuestra base de datos, la colección a particionar y la shard key. Es importante que la shard key tenga alta cardinalidad, para que se generen chunks pequeños y estos se puedan repartir eficientemente entre las diferentes máquinas que añadamos en el futuro.
Rebalanceo y Carga de Recursos
Al habilitar el sharding, la base de datos empezará a rebalancear los datos de las colecciones shardeadas. Durante este proceso, se incrementará la carga de CPU y disco, y el tiempo que tome dependerá de la cantidad de datos que tengamos almacenados.
Monitorización
Un punto clave para escalar correctamente tu base de datos es la capacidad de monitorizar las métricas de hardware y queries de tu clúster. Si utilizamos MongoDB Atlas, tendremos acceso a una amplia variedad de métricas a través de la web. Si tu clúster está hosteado en tu propia infraestructura cloud, deberás usar las herramientas nativas del sistema operativo de la máquina y los logs nativos de la base de datos.
Obtención de Métricas de Queries en MongoDB
Las métricas de queries pueden obtenerse mediante comandos nativos, como activar el profiler para registrar logs de queries lentas y su cantidad de accesos. MongoDB también dispone de varias colecciones donde se almacenan logs de información útiles para el análisis.
Visibilidad Completa del Clúster
Al tener una visibilidad completa del estado de nuestro clúster, podemos identificar cuáles queries son responsables del bajo rendimiento o si enfrentamos limitaciones de hardware que sugieren la necesidad de añadir una máquina adicional.
Escrituras en Batch
Otra forma de mejorar el rendimiento de nuestro clúster es realizando escrituras en batch.
Por ejemplo, si decidimos desnormalizar los blog posts añadiendo información del usuario en cada uno, esto puede crear un problema de consistencia de datos. Si un usuario cambia su nombre, tendríamos que actualizar todos los posts asociados. En este caso, podemos usar una query updateMany para actualizar el nombre del usuario en todos sus posts de una sola vez.
Uso de BulkWrite para Actualizaciones Masivas
En un escenario donde debamos actualizar documentos individuales, podríamos usar una query de actualización para cada documento de MongoDB. Sin embargo, bulkWrite nos permite ejecutar varias operaciones individuales de escritura en una sola petición.
Por defecto, MongoDB ejecuta estas peticiones de forma ordenada, lo que es útil cuando queremos ejecutar muchas operaciones sin incrementar el uso de recursos.
Si necesitamos una actualización más rápida, podemos usar la opción ordered: false, que permite ejecutar operaciones en paralelo, incrementando la carga del hardware pero mejorando la velocidad de escritura.
InsertMany para Operaciones en Paralelo
Otro método que realiza varias operaciones en paralelo es insertMany. Al igual que bulkWrite, ofrece la opción de realizar las inserciones de manera ordenada o en paralelo, según nuestras necesidades.
Cuidado con los Índices en MongoDB
Es importante tener precaución al realizar operaciones de inserción o actualización en una colección con múltiples índices. Cada vez que se añade un nuevo índice, MongoDB debe actualizarlo y rebalancearlo con cada operación de escritura, lo que incrementa el coste computacional de dichas operaciones.
Uso de TTL en MongoDB
Imaginemos que en algún punto de nuestra aplicación introducimos la monitorización de los usuarios en la web. Dado que no queremos almacenar todos esos datos indefinidamente, añadimos un límite de tiempo desde que se almacenan hasta que se eliminan. Para ello, creamos funciones lambda que eliminan estos datos en momentos de menor actividad.
Problema de Crecimiento de Datos
Con el incremento de las métricas de monitorización y el aumento en la cantidad de usuarios que visitan nuestra web para leer posts, escribir comentarios y crear nuevos posts, cada día se generan más gigas de información. Esto incrementa la cantidad de datos que nuestra función debe eliminar.
Implementación de un Índice TTL
Una opción más eficiente que ofrece MongoDB para evitar ejecutar un proceso de borrado pesado es utilizar un índice TTL. Este tipo de índice utiliza un campo de fecha para que MongoDB sepa qué documentos han superado el criterio de antigüedad y así pueda eliminarlos. Con el índice TTL, MongoDB lanza procesos internos que eliminan los documentos de manera gradual a lo largo del día, evitando borrar de golpe cientos de gigas de datos acumulados en un día.
Modelo de datos
El modelo de datos que tengamos también es otro factor de escalabilidad. Un modelo de datos que no esté optimizado para las queries que estemos haciendo puede acabar ralentizando la base de datos, incluso aplicando diversos métodos de escalado.
Ejemplo de Ineficiencia en el Modelo
Un ejemplo sencillo podría ser que quisiéramos mostrar cuántos usuarios únicos han visitado un blog post.
Para esto, cada vez que un usuario visite un post, crearemos un nuevo documento en MongoDB para una nueva colección. Estos documentos contendrán el ID del post, el ID del usuario y el momento en el que visitó el post. Para blog posts con pocas visualizaciones no habría problemas, pero en posts con millones de visitas tendríamos un problema con el rendimiento de la query.
Soluciones con Agregación
En este caso, para obtener el número exacto, tendríamos que, o bien traernos todas las visitas a un post y calcularlo en nuestra API. También podríamos hacer una query de agregación, que tendría tres etapas:
- Filtrar los documentos por el ID del blog post.
- Agrupar los documentos por ID de usuario.
- Contar los documentos obtenidos del stage anterior.
Repensar el Modelo de Datos
Cuando tenemos estos casos, la mejor solución sería repensar el modelo de datos o introducir diferentes elementos al sistema. Como por ejemplo una caché, para no delegar este cálculo tan costoso de rendimiento en MongoDB.
Conclusión
Aplicando sharding, índices, paginación y con una buena monitorización del clúster de MongoDB podemos conseguir escalar nuestra base de datos para que soporte el incremento constante de usuarios que vayamos teniendo. Este proceso de escalado no termina por aplicar estas estrategias sino que es un proceso constante de revisión de logs y métricas a medida que se introducen nuevas prestaciones y datos a la aplicación.
Descúbre el caso de éxito real aquí.


