MongoDB: creación y utilización de índices

MongoDB: creación y utilización de índices
Sin comentarios Facebook Twitter Flipboard E-mail

Una vez que hemos aprendido a insertar, modificar y eliminar datos en MongoDB, estamos casi preparados para empezar a realizar consultas. Y digo casi, porque todavía nos queda conocer una parte muy importante de MongoDB: los índices.

En las bases de datos relacionales, los índices son algo indispensable. Sería inconcebible consultar una tabla con millones de registros si no hemos configurado al menos un índice.

Con MongoDB pasa lo mismo. No podemos pensar en tener una colección con millones de documentos, sin tener índices sobre uno o varios campos. Las diferencia entre realizar una consulta sobre campo con índice, y realizarla sin él, puede ser abismal. Saber crear y configurar índices en nuestras colecciones, es algo vital.

Índices en MongoDB

Los índices en MongoDB se generar en forma de Árbol-B o B-Tree. Es decir, que los datos se guardan en forma de árbol, pero manteniendo los nodos balanceados. Esto incrementa la velocidad a la hora de buscar y también a la hora de devolver resultados ya ordenados. De hecho MongoDB es capaz de recorrer los índices en ambos sentidos, por lo que con un solo índice, podemos conseguir ordenación tanto ascendente como descendente.

Para mejorar la eficiencia de los índices, es recomendable que estos tengan una cardinalidad alta. ¿Y qué es la cardinalidad? Pensemos en un diccionario. Las palabras están ordenadas en orden alfabético, y son únicas. Por eso es relativamente fácil encontrar la palabra que buscamos. Ahora imaginemos que el diccionario está solo agrupado y ordenado por la primera letra de cada palabra. Tendremos miles de palabras que empiezan por A, otras miles que empiezan por M, muchas otras que empiezan por la P etc. Buscar en un diccionario así sería algo tedioso. Una vez encontrada la primera letra, tendríamos que leer cada palabra de ese rango para encontrar la palabra buscada.

Eso es exactamente la cardinalidad. Cuantos más valores únicos tenga el campo, más alta será la cardinalidad. Y más eficiente será el índice.

Tipos de índices en MongoDB

A la hora de configurar un índice, tenemos varias opciones dónde elegir. Son las siguientes.

Índices simples o de un solo campo

Estos índices se aplican a un solo campo de nuestra colección. Para declarar un índice de este tipo debemos usar un comando similar a este:

db.users.ensureIndex( { "user_id" : 1 } )

El número indica que queremos que el índice se ordene de forma ascendente. Si quisiéramos un orden descendente, el parámetro será un -1.

Índices compuestos

En este caso el índice se generará sobre varios campos.

db.users.ensureIndex( { "user_name" : 1, "age":-1 } )

El índice que se generará con la instrucción anterior, agrupará los datos primero por el campo user_name y luego por el campo age. Es decir, se generaría algo así:

    "Antonio", 35
    "Antonio", 18
    "María", 56
    "María", 30
    "María", 21
    "Pedro", 19
    "Unai", 34
    "Unai", 27

Lo bueno de los índices compuestos, es que podemos usarlos para consultar uno o varios de los campos, sin que sea necesario incluirlos todos. En el ejemplo anterior el índice se puede utilizar siempre que se hagan consultas sobre user_name o sobre user_name y age. Al fin y al cabo este índice compuesto ordenará el campo user_name de la misma manera que si creásemos un índice simple. Lo que no podemos hacer es buscar solo sobre el campo age. Para ese caso tendríamos que crear un índice específico.

Si el índice compuesto tuviera tres campos, podríamos utilizarlo al consultar sobre el primer campo, sobre el primer y segundo campo, o sobre los tres campos.

Índices únicos

Los índices simples y múltiples, pueden estar obligados a contener valores únicos. Esto lo conseguimos añadiendo el parámetro unique a la hora de crearlos.

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

En el caso de que tratemos de insertar valores repetidos en algún campo con un índice único, MongoDB nos devolverá un error.

Índices sparse

Los índices que hemos mencionado antes, incluyen todos los documentos. También los documentos que no contengan el campo indexado. MongoDB no obliga a que usemos un esquema, así que no hay obligatoriedad en que los campos existan en el documento. Vamos, que el campo user_name puede estar en el documento, o no.

Para crear índices que solo incluyan los documentos cuyo campo indexado existe, utilizaremos la opción sparse.

db.users.ensureIndex( { "user_name" : 1 }, {"sparse":true} )

En este caso lo que hacemos es crear un índice que no tiene porque contener todos los documentos de la colección.

Otros tipos de índices

Existen también otros tipos de índices como son los índices Hash, los índices para geoposicionamiento y los índices de texto. Son índices para operaciones más específicas, así que los dejaremos para otra ocasión.

Intersección de índices

Antes de la versión 2.6 de MongoDB, cada consulta podía utilizar como máximo un índice. Esto era un problema en algunos casos. Por ejemplo imaginemos que necesitamos una consulta que ordena por user_name de forma ascendente y age de forma descendente. Creando un índice compuesto, como hemos visto antes, tendríamos el problema solucionado.

db.users.ensureIndex( { "user_name" : 1, "age":-1 } )

¿Pero y si necesitamos también ejecutar consultas que devuelvan los resultados ordenados por user_id ascendente y age también ascendente? Nos veríamos obligados a crear otro índice, muy similar al anterior.

db.users.ensureIndex( { "user_name" : 1, "age":1 } )

Para paliar estos problemas, aparecen los índices cruzados. De esta manera MongoDB puede usar varios índices en una consulta, para así mejorar el rendimiento de la misma. Creando dos índices, tendríamos el problema solucionado.

    db.users.ensureIndex( { "user_name" : 1 } )
    db.users.ensureIndex( { "age" : -1 } )

Indexación de subdocumentos

No hemos hablado todavía de la posibilidad de indexar subdocumentos. Supongamos un documento con la siguiente estructura:

    { 
        "user_id": "DJK2312212",
        "info": 
        {
            "user_name": "Pedro",
            "age": 22
        }
    }

Si necesitamos crear un índice sobre el campo user_name, podríamos hacerlo sin problema.

db.users.ensureIndex( { "info.user_name" : 1 } )

También podríamos crear el índice sobre el nodo principal info, pero esto es algo que solo nos ayudaría en el caso de que se realizaran consultas sobre el subdocumento completo y con los campos en el mismo orden.

Indexación de arrays

Indexar arrays también es algo posible con MongoDB aunque es algo que deberemos hacer con cuidado y teniendo en cuenta algunas limitaciones.

La primera limitación es que sólo uno de los campos del índice puede ser un array.

db.users.ensureIndex( { "categories" : 1, "tags": 1 } )

Si intentamos insertar un documento con dos arrays, obtendremos un error:

db.users.insert({"categories":["game","book","movie"], "tags":["horror","scifi","history"]
cannot index parallel arrays [categories] [tags]

Esto es algo lógico, ya que por cada tupla del producto cartesiano habría que crear una entrada en el índice. En el caso de que los arrays fuesen grandes, sería una operación inmanejable.

La otra limitación, es que los elementos indexados del array, no tienen en cuenta el orden. Por tanto si realizamos consultas posicionales sobre el array, no se usará el índice.

Consultas totalmente cubiertas por los índices

Aunque tengamos varios índices creados, no todas las consultas van a ser igual de eficientes. Las consultas más rápidas serán las denominadas consultas totalmente cubiertas. Son aquellas consultas cuyos campos consultados y devueltos están incluidas en el índice.

Por ejemplo supongamos que tenemos el siguiente índice compuesto:

db.users.ensureIndex( { "user_name" : 1, "age":1 } )

Si hacemos una búsqueda que utilice en la consulta user_name y/o age, y además, con los valores devueltos user_name y/o age, estaríamos hablando de una consulta totalmente cubierta. MongoDB no tendrá que buscar en disco el documento completo, ya que los valores serán devueltos directamente desde el índice. Siempre que podamos, deberemos hacer consultas de este tipo.

Analizando planes de ejecución en MongoDB

Las bases de datos relacionales suelen poner a nuestra disposición alguna utilidad para consultar los planes de ejecución que utilizará el motor de consultas al realizar una consulta. Un plan de ejecución presenta una descripción, con los pasos que realizará el motor para buscar los datos. Es algo muy útil, ya que nos permite analizar si nuestros índices están funcionando correctamente, o si podemos añadir alguno más para incrementar la velocidad de las consultas.

MongoDB, como no podía ser menos, tiene una utilidad similar que podemos explotar con en el operador $explain.

db.colección.find().explain()

Esta consulta nos devolverá un documento JSON con muchísima información útil. Entre ella el número de documentos procesados para devolver el resultado, si la consulta ha utilizado solo los índices, si ha tenido que ordenar los resultados en memoria, el tiempo que ha tardado la consulta en ejecutarse etc.

Y ahora que sabemos cómo funcionan los índices de MongoDB, es hora de empezar a realizar consultas. Pero eso lo dejaremos para el próximo artículo. Allí nos vemos.

En Genbetadev | MongoDB: la vida cambia, tus datos también. Operaciones de actualización simples,

Comentarios cerrados
Inicio