Tutorial MongoDB. Índices

Una parte importante en cualquier base de datos, sea relacional o no, son los índices. Los índices se utilizan para incrementar la velocidad de acceso a los datos, pudiendo obtenerlos de manera más directa. En lugar de recorrer una tabla o colección para buscar un conjunto de resultados, buscamos directamente en el índice que nos devuelve el registro o conjunto de registros de forma directa.

MongoDB es capaz de definir índices a nivel de colección, pudiendo reducir drásticamente el tiempo que tarda una consulta en devolver los resultados, ya que MongoDB intenta mantener los índices en la memoria RAM, o si no es posible, en disco de forma secuencial. Este índice se almacena internamente como un B-Tree (árbol-B), por lo que su creación y mantenimiento puede influir en el rendimiento de las consultas de escritura como inserciones y actualizaciones.

Con cada consulta, el optimizador de consultas de MongoDB elegirá un solo índice ya que no es posible utilizar más. Este índice se determina de manera automática utilizando el que supuestamente tendrá el mejor rendimiento.

Ahora que sabemos qué son los índices, vamos a ver que tipos hay y como crearlos.

Creación de índices

Para crear un índice utilizaremos el comando ensureIndex.

> db.collection.ensureIndex(keys, options)  

En keys añadiremos los campos sobre los que se creará el índice y en opciones podremos añadir distintas opciones como unique, background etc.

Tipos de índices

Índice _id

Como ya hemos visto en otras entradas del blog, MongoDB crea un campo _id para cada documento. Con este campo se crea automáticamente un índice único que no podemos borrar. Todo documento insertado en una colección tendrá un campo _id único que no se podrá repetir.

Índices normales

Por índices normales se entienden aquellos que no utilizan ningún tipo de opción. Se crearán con un comando similar a

> db.products.ensureIndex( { "name": 1 }

Índices en subdocumentos

Podemos crear índices que engloben todo el contenido de un subdocumento. Imaginemos que tenemos documentos como el siguiente

{
 name:"Pedro Pérez",
 direccion: 
 {
  ciudad:"Madrid",
  pais: "España" 
 }
}



> db.addresses.ensureIndex({"direccion":1}) 

Una vez creado el índice sobre el subdocumento, se utilizará a la hora de realizar consultas, como por ejemplo la siguiente:

> db.addresses.find({direccion:{ciudad:"Madrid",pais:"España"}})

Índices embebidos

Podemos crear un índice que esté dentro del campo concreto de un subdocumento utilizando Dot Notation.

> db.addresses.ensureIndex({"direccion.ciudad":1}) 

Índices compuestos

Un índice compuesto es un aquel que incluye varios campos en su definición.

> db.productos.ensureIndex({"localizacion":1, "tipo":1, "cantidad":1})

Si nuestra aplicación suele hacer consultas que impliquen el uso de los campos localización y tipo, la velocidad de las consultas se verá incrementada.

Además los índices compuestos soportan prefijos. Un prefijo es el inicio de un subconjunto de campos. En el ejemplo anterior existen los prefijos {localización} y {localización, tipo}. Esto quiere decir que una consulta que utilice el campo localización o el campo localización y el campo tipo, también podrá hacer uso del índice incrementando su velocidad de respuesta.

Índices multikey

Si se añade un campo array a un índice, MongoDB indexará cada elemento del array por separado. Consideremos el siguiente ejemplo de documento.

{
 title:"MongoDB: base de datos NoSQL",
 tags: ["MongoDB","10gen","tutorial"]
}

Si creamos un índice sobre el campo tags, podremos buscar documentos con las querys {tags:“MongoDB”}, {tags:“10gen”} o {tags:“tutorial”}.

Si el array contiene documentos también podemos generar índices

{
 title:"MongoDB: base de datos NoSQL",
 tags: 
 [
  {id:1222,text:"MongoDB"},
  {id:1223,text:"10gen"},
  {id:1234,text:"tutorial"}
 ]
}

Por ejemplo podemos crear el siguiente índice

> db.blogPosts.ensureIndex({"tags.text":1})

Lo que nos permitirá realizar consultas utilizando dicho índice:

> db.blogPosts.find({"tags.text":"MongoDB"}) 

También podemos crear índices compuestos que contengan índices multikey, pero hay que tener en cuenta que solo uno de los campos en el índice compuesto podrá ser un array. Por ejemplo, pensemos en documentos con el siguiente formato

{
 title:"MongoDB: base de datos NoSQL",
 authors:["Pedro J","Roman K", "Luis"],
 tags: ["MongoDB","10gen", "tutorial"]
}

Está permitido crear un índice compuesto para {title:1,authors:1}, pero no crear un índice para {authors:1,tags:1} ya que MongoDB tendría que hacer el producto cartesiano de ambos arrays lo que podría dar lugar a un índice imposible de mantener.

Opciones

Unique

Un índice único es un índice que no acepta valores duplicados. Si intentamos insertar un índice que ya existe en la colección MongoDB nos devolverá un error. Para crearlo utilizaremos un comando como el siguiente:

> db.addresses.ensureIndex( { "user_id": 1 }, { unique:true } )

Sparse

Normalmente un índice incluye todos los documentos, incluso aquellos que no tienen el campo del índice. En ese caso el índice almacena valores null. Con la opción sparse hacemos que un índice no incluya dichos documentos.

> db.addresses.ensureIndex( { "user_id": 1 }, { sparse: true } )

Aunque al haber menos documentos la velocidad se puede ver incrementada, hay que tener en cuenta que las consultas pueden verse afectadas devolviendo datos incompletos.

Background

Cuando creamos un índice se bloquea la colección. Si esta colección tiene una gran cantidad de datos el sistema puede verse afectado por un largo periodo de tiempo. Para evitar esto podemos usar la opción background que permite seguir utilizando la instancia mientras se termina de generar el índice.

> db.addresses.ensureIndex( { "user_id": 1 }, { background: true } )

Borrar duplicados

Si intentamos crear un índice único sobre un campo que ya contiene valores duplicados, MongoDB nos devolverá un error. Para evitar esto podemos usar la opción dropDups que borrará todos los duplicados.

> db.addresses.ensureIndex( { "user_id": 1 }, {unique:true,dropDups: true})

El anterior comando borrará todos los user_id duplicados, dejando solo la primera ocurrencia. Es importante destacar, que también se incluirán en el índice los documentos que no tengan el campo “user_id”, pero con valor null. Si la primera ocurrencia es un documento que no tenga dicho campo, el resto de documentos serán eliminados.

Ordenación de los índices

Cuando utilizamos el comando ensureIndex y le pasamos como parámetro el nombre del campo y un número 1 estamos diciendo a MongoDB que el índice debe crearse de manera ascendente. Si por el contrario pasamos un -1, haremos que el índice se ordene de manera descendente.

Si el índice tiene un solo valor, la ordenación no es demasiado importante porque MongoDB es capaz de recorrer el árbol de índices desde el principio al final o desde el final al principio. Por tanto si una de nuestras consultas utiliza un operador order, se utilizará el índice recorriéndolo de una manera o de otra.

Sin embargo si tenemos un índice compuesto, es posible que necesitemos ordenar cada campo en una dirección. Por ejemplo si tenemos una tabla que controla los accesos de los usuarios a partir de la fecha y la hora, y queremos obtener el nombre de usuario y la fecha más actual, nuestro índice debería crearse con un {user_id:1, lastLoginDate:-1}. De esta manera se ordenaran primero los usuarios de forma ascendente y las fechas de acceso de forma descentente, haciendo las consultas de este tipo más rápidas y sencillas.

Consultas cubiertas totalmente por un índice

Una consulta está cubierta completamente por un índice cuando todos los campos de la consulta y todos los campos devueltos, están dentro del índice. Aunque hay algunas excepciones:

  • si alguno de los documentos en la colección, contiene un array en alguno de los campos incluídos en el índice, el índice se convierte en un multikey index y no cubrirá la consulta completamente.
  • si alguno de los índices hace referencia a algún subdocumento.

Cuando esto sucede las consultas se devuelven rápidamente ya que **MongoDB* *no necesita acceder al documento para devolver los resultados. Estas consultas son sin duda las más rápidas, así que conseguir que nuestros índices cubran completamente nuestras consultas nos proporcionará muy buen rendimiento.

Eliminar índices

Para eliminar un índice utilizaremos el comando dropIndex como se explica en el siguiente ejemplo.

> db.products.dropIndex( { "name": 1 }

Regenerar índices

Al igual que sucede en las bases de datos relacionales puede ser necesario recrear de nuevo un índice. Para ello utilizaremos el comando reIndex

> db.products.reIndex()

El comando borrará todos los índices de la colección y los volverá a generar. Si queremos hacerlo solo con un índice en lugar de realizarlo con todos basará con borrarlo y volver a crearlo.

Conclusión

Los índices son un punto importante a la hora de realizar consultas contra una base de datos MongoDB. Una mala gestión de los índices puede derivar en un rendimiento pobre, por lo que deberemos ser conscientes del tipo de consutlas que se realizan a la base de datos. Sabiendo qué campos se utilizan para filtrar y cuáles suelen ser los devueltos en las consultas, seremos capaces de crear los índices necesarios para que nuestra base de datos se comporte correctamente.



Recuerda que puedes ver el índice del tutorial y acceder a todos los artículos de la serie desde aquí.



¿Quiéres que te avisemos cuando se publiquen nuevas entradas en el blog?

Suscríbete por correo electrónico o por RSS