🎨 Sysprovider Code
Sysprovider LogoWiki
🇪🇸Hosting español para ecommerce

Técnicas de indexación avanzada en MongoDB para aplicaciones escalables

Actualizado el 1 de noviembre de 2025

Cuando una aplicación crece, el cuello de botella más común no suele estar en el código, sino en la capa de persistencia. MongoDB, al ser una base de datos NoSQL orientada a documentos, ofrece una flexibilidad enorme, pero esa misma flexibilidad puede convertirse en una trampa de rendimiento si no se dominan las técnicas de indexación avanzada.

En este artículo exploraremos estrategias de indexación que van más allá del índice básico de un solo campo. Abordaremos cómo diseñar índices compuestos para consultas complejas, cómo sacar partido de los índices geoespaciales para búsquedas de localización, y cómo gestionar la escalabilidad horizontal e vertical mediante índices optimizados. Todo ello con ejemplos prácticos y advertencias basadas en experiencia real en entornos de producción.


Comprendiendo el impacto de la indexación en la escalabilidad

Antes de lanzarnos a crear índices, debemos entender qué papel juegan en la escalabilidad. Un índice en MongoDB es una estructura de datos (normalmente un B-tree) que almacena una pequeña porción de los datos de la colección de forma ordenada. Sin índices, MongoDB realiza un collection scan (escaneo completo de la colección) para cada consulta, lo que es inviable cuando la colección supera unos pocos gigabytes.

La escalabilidad en MongoDB se consigue de dos formas:

  • Vertical: Aumentando los recursos de un solo nodo (RAM, CPU, SSD). Los índices bien diseñados reducen la necesidad de escalar verticalmente porque minimizan el uso de memoria y disco.
  • Horizontal (Sharding): Distribuyendo datos entre varios servidores. La clave de sharding debe estar correctamente indexada para que las consultas sean eficientes.

[INFO] Un índice no solo acelera las lecturas, también ralentiza las escrituras (inserciones, actualizaciones, borrados). En sistemas con alta tasa de escritura, es crucial encontrar el equilibrio entre velocidad de lectura y coste de escritura.


Índices compuestos: el pilar de las consultas complejas

Un índice compuesto es un índice sobre múltiples campos de un documento. Es la técnica más potente para optimizar consultas que filtran por varios criterios simultáneamente.

Estructura y orden de los campos

La regla de oro es: ESR (Equality, Sort, Range). Este acrónimo define el orden óptimo de los campos en un índice compuesto:

  1. Equality (E): Campos que se filtran con igualdad exacta (field: value).
  2. Sort (S): Campo por el que se ordena el resultado (si aplica).
  3. Range (R): Campos que usan operadores de rango ($gt, $lt, $gte, $lte, $in, $regex no ancla).

Ejemplo práctico:
Supongamos una colección pedidos con documentos:

{
  "cliente_id": 123,
  "fecha": ISODate("2024-03-15"),
  "total": 450.00,
  "estado": "enviado"
}

Queremos una consulta que busque pedidos de un cliente específico, ordenados por fecha descendente, y con total mayor a 100.

Consulta:

db.pedidos.find({
  cliente_id: 123,
  total: { $gt: 100 }
}).sort({ fecha: -1 })

El índice compuesto óptimo sería:

db.pedidos.createIndex(
  { cliente_id: 1, fecha: -1, total: 1 }
)

Explicación:

  • cliente_id: Equality (E) – primero.
  • fecha: Sort (S) – segundo, porque MongoDB puede recorrer el índice en orden sin necesidad de ordenación en memoria.
  • total: Range (R) – último.

[TIP] Si el sort no se corresponde con el orden del índice, MongoDB hará una ordenación en memoria (bloque de 32 MB por defecto). Eso puede matar el rendimiento. Usa explain() para verificar que aparece "SORT_KEY" en el stage.

Índices compuestos con arrays (índices multikey)

Cuando un campo de un índice compuesto es un array, MongoDB crea un índice multikey. Cada elemento del array genera una entrada en el índice. Esto es muy útil para etiquetas o categorías, pero tiene una limitación importante: solo se permite un campo array por índice compuesto.

db.productos.createIndex({ categorias: 1, precio: 1 })
// Funciona: categorias es array, precio no.

Si intentas dos arrays, MongoDB lanzará un error.


Índices geoespaciales: localización y proximidad

Las aplicaciones modernas (delivery, redes sociales, logística) necesitan búsquedas por coordenadas geográficas. MongoDB ofrece dos tipos de índices geoespaciales: 2d (para geometrías planas) y 2dsphere (para la superficie esférica de la Tierra).

Índice 2dsphere (recomendado para la mayoría de casos)

Almacena puntos, líneas y polígonos en formato GeoJSON. Soporta consultas como $near, $geoWithin y $geoIntersects.

Ejemplo: Colección de restaurantes

{
  "nombre": "Pizzería Roma",
  "direccion": {
    "type": "Point",
    "coordinates": [-73.97, 40.77] // [longitud, latitud]
  },
  "cocina": "italiana",
  "puntuacion": 4.5
}

Creación del índice:

db.restaurantes.createIndex({ direccion: "2dsphere" })

Consulta para encontrar restaurantes a menos de 1 km de Times Square:

db.restaurantes.find({
  direccion: {
    $near: {
      $geometry: { type: "Point", coordinates: [-73.9857, 40.7580] },
      $maxDistance: 1000 // en metros
    }
  }
})

[WARNING] El orden de las coordenadas en GeoJSON es longitud, latitud (no al revés). Es un error clásico que produce resultados absurdos. Verifica siempre con $geoNear en el aggregation pipeline.

Índice 2d (para coordenadas legacy)

Si tus datos usan el formato legacy [x, y] (como lat/lon en orden inverso o coordenadas de píxel), puedes usar el índice 2d. Es menos preciso para la Tierra (asume plano) pero más rápido para distancias cortas en una zona pequeña.

db.ubicaciones.createIndex({ coordenadas: "2d" })

Combinando geoespacial con otros filtros

Para aplicaciones escalables, a menudo necesitas filtrar por ubicación y por otros criterios (por ejemplo, restaurantes italianos cerca de mí). La solución es un índice compuesto donde el campo geoespacial va primero.

db.restaurantes.createIndex(
  { direccion: "2dsphere", cocina: 1, puntuacion: -1 }
)

Esto permite que MongoDB use el índice para la parte geoespacial y luego refine con los otros campos sin escanear todos los documentos.


Técnicas avanzadas para escalar índices

Índices parciales (Partial Indexes)

En colecciones enormes, indexar todos los documentos puede ser derrochador. Un índice parcial solo indexa documentos que cumplen una condición. Reduce el tamaño del índice y mejora el rendimiento de escritura.

Ejemplo: Solo indexar pedidos activos (no cancelados ni completados).

db.pedidos.createIndex(
  { cliente_id: 1, fecha: -1 },
  { partialFilterExpression: { estado: { $in: ["pendiente", "procesando"] } } }
)

Las consultas que no incluyan la condición del filtro parcial no usarán este índice. Es ideal para workloads donde la mayoría de los datos son históricos y solo consultas los recientes.

Índices cubiertos (Covered Queries)

Una consulta está cubierta cuando todos los campos que necesita (filtro + proyección) están contenidos en el índice. MongoDB no necesita tocar los documentos, solo leer el índice. Esto es extremadamente rápido.

Ejemplo:

db.usuarios.createIndex({ email: 1, nombre: 1, rol: 1 })

// Consulta cubierta:
db.usuarios.find(
  { email: "user@example.com" },
  { _id: 0, nombre: 1, rol: 1 }
)

[TIP] Para lograr consultas cubiertas, excluye siempre _id en la proyección (a menos que esté en el índice) y asegúrate de que el índice incluye todos los campos proyectados.

Índices con collation (ordenación sensible a idioma)

En aplicaciones multiidioma, la ordenación por defecto (binaria) no es adecuada. MongoDB permite definir collation en el índice para manejar mayúsculas/minúsculas, acentos y reglas de idioma.

db.productos.createIndex(
  { nombre: 1 },
  { collation: { locale: "es", strength: 1 } }
)
// strength: 1 ignora mayúsculas y acentos

Índices TTL (Time-To-Live)

Para datos temporales como sesiones o logs, los índices TTL borran documentos automáticamente tras un tiempo. Se crean sobre un campo de tipo fecha.

db.sesiones.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 3600 } // 1 hora
)

MongoDB ejecuta un hilo cada 60 segundos que elimina documentos caducados. No es preciso al segundo, pero suficiente para la mayoría de casos.


Monitoreo y mantenimiento de índices para escalabilidad

Uso de explain() y hint()

Antes de desplegar un índice en producción, analiza el plan de ejecución:

db.pedidos.find({ cliente_id: 123 }).sort({ fecha: -1 }).explain("executionStats")

Busca en la salida:

  • totalDocsExamined vs totalKeysExamined: idealmente deben ser iguales.
  • stage: debe ser IXSCAN (escaneo de índice), no COLLSCAN.
  • nReturned: número de documentos devueltos.

Si MongoDB no elige el índice que esperas, puedes forzarlo con hint():

db.pedidos.find({ cliente_id: 123 }).hint({ cliente_id: 1, fecha: -1 })

Reindexación y compactación

Con el tiempo, los índices se fragmentan. En MongoDB, el comando reIndex() reconstruye todos los índices de una colección. Es una operación pesada que bloquea la colección, así que programa ventanas de mantenimiento.

db.pedidos.reIndex()

[WARNING] En MongoDB 4.4+, reIndex() está deprecado para la mayoría de motores de almacenamiento (WiredTiger). En su lugar, usa compact() o simplemente deja que MongoDB maneje la fragmentación internamente. Solo usa reIndex() si notas degradación severa.

Índices y sharding

Cuando fragmentas una colección (sharding), la clave de shard debe estar indexada. Además, los índices en colecciones fragmentadas se crean en todos los shards automáticamente. Sin embargo, los índices secundarios (que no incluyen la clave de shard) pueden causar scatter-gather (consultas que deben ir a todos los shards). Para evitarlo, diseña índices compuestos que comiencen con la clave de shard.

Ejemplo:
Clave de shard: { region: 1 }
Índice eficiente para consultas por región y fecha:

db.ventas.createIndex({ region: 1, fecha: -1 })

Errores comunes y cómo evitarlos

  1. Sobreindexación: Cada índice consume RAM y ralentiza escrituras. No crees índices para consultas que no existen. Usa el profiler de MongoDB (db.setProfilingLevel(1)) para detectar consultas lentas y crear índices solo para ellas.

  2. Ignorar el orden de los campos en índices compuestos: Un índice { a:1, b:1 } no sirve para consultas que filtran solo por b. El orden importa.

  3. No usar partialFilterExpression en tablas enormes: Si el 90% de tus datos son históricos y nunca los consultas, un índice parcial puede reducir el tamaño del índice un 90%.

  4. Confiar en índices geoespaciales sin verificar el formato: Recuerda: GeoJSON usa [longitud, latitud]. El formato legacy [latitud, longitud] es diferente. Mezclarlos da resultados erróneos.

  5. No planificar índices para sharding: Si tu aplicación va a crecer horizontalmente, la clave de shard debe estar en el índice principal de las consultas más frecuentes.


Conclusión

La indexación avanzada en MongoDB no es un lujo, es una necesidad para cualquier aplicación que aspire a ser escalable. Los índices compuestos bien diseñados según el patrón ESR, los índices geoespaciales para datos de localización, y las técnicas como índices parciales y cubiertos, te permitirán exprimir al máximo el rendimiento de MongoDB sin necesidad de escalar hardware prematuramente.

Recuerda siempre:

  • Mide antes de optimizar: Usa explain() y el profiler.
  • Equilibra lecturas y escrituras: Un índice extra puede ser un arma de doble filo.
  • Diseña pensando en el sharding: La clave de shard debe ser parte de tus índices principales.

Con estas técnicas, estarás preparado para manejar desde miles hasta millones de documentos sin que el rendimiento se resienta. La clave está en entender cómo MongoDB recorre los índices y adaptar la estructura a tus patrones de consulta reales.

¿Necesitas ayuda?Son dos de nuestros técnicos, Agustín y Mikel, y están disponibles para resolver cualquier problema.

Hablar con ellos ahora
Agustín y Mikel