Réplica de datos en MongoDB

En este tutorial, explicaremos los fundamentos de la réplica de datos en MongoDB y configuraremos, a través de un sencillo script, una pequeña prueba de ReplicaSet en nuestra propia máquina.

Índice de contenidos


1. Introducción y Objetivo

La réplica de datos es una de las funcionalidades de MongoDB que nos permite mantener una copia de los datos en varios nodos, de forma que tengamos una mayor tolerancia a que uno de ellos pueda fallar, sin perder los datos que estamos persistiendo o recuperando.

En éste articulo explicaremos cómo configurar la réplica de datos (a través de ReplicaSet) en MongoDB.

A lo largo de la explicación iremos componiendo un Script con las instrucciones para montar, en una máquina, un servicio de MongoDB configurado en modo ReplicaSet con tres instancias.

Para ello se utilizará el propio API de MongoDB y la facilidad que implementa para la configuración por defecto de ReplicaSet.

Los pasos que se presentan en el presente artículo NO consituyen la forma recomendada de configurar ReplicaSet, sino que pretenden permitir probar la funcionalidad de réplica de MongoDB de una forma sencilla, al tiempo que sirve para ilustrar y probar los componentes y la arquitectura de nodos para configurar correctamente la réplica de datos. Por este motivo, esta guía no debe utilizarse como guía para la configuración de entornos de producción.


2. Requisitos

Para poder seguir las instrucciones de esta guía y probar el funcionamiento del ReplicaSet en MongoDB, vamos a utilizar un servidor de MongoDB, que levantará varios procesos mongod en una misma máquina y sobre el que probaremos a insertar datos y comprobar que se replican en el resto de Nodos.

Para todo ello, utilizaremos la consola de Mongo y el API JavaScript que ofrece esta consola.

Por tanto, el único requisito es tener una versión de MongoDB instalada. Se requiere que sea al menos la versión 1.6. Dado que es la primera versión donde el API incluye el objeto ReplSetTest, que es del que nos valdremos para configurar el grupo de réplica para pruebas. Este tutorial ha sido escrito utilizando la version 3.2 de MongoDB.

Para ello, vamos a utilizar la propia consola de Mongo, y las utilidades que permite Mongo a través del API para levantar y configurar un ReplicaSet.

En concreto, utilizaremos la clase ReplSetTest, disponible en el API JS de la shell de MongoDB desde la versión 1.6, del que también podemos consultar online su código fuente.


3. Arranque y acceso a la consola con Mongo Shell

Para iniciar la prueba de ReplicaSet necesitamos arrancar la consola de mongo sin conectar contra ningún servidor en concreto. Esto lo conseguimos con el parámetro –nodb

Es preferible que no tengamos una instancia de MongoDB previamente arrancada, las distintas instancias que formarán parte del grupo de réplica de prueba se irán levantando durante el proceso del script.


4. Creación del ReplicaSet de ejemplo

Para probar el mecanismo de réplica de MongoDB, necesitaremos crear varias instancias de mongod que actúen como servidores y relacionarlas de forma que todas mantengan una copia de los datos.

Habitualmente esto implica que configuraremos estos procesos de forma externa, pero para facilitar la configuración y arranque de estas instancias, MongoDB incluye en el API Javascript de su shell un objeto ReplSetTest que nos permite levantar estas instancias de una forma directa.

Para arrancarlo crearemos un nuevo objeto ReplSetTest. Como argumento al constructor del objeto podemos pasar un JSON con la configuración que queremos dar al grupo de réplica.

El conjunto de atributos que podemos configurar para este ReplicaSet está descrito en el Anexo A de este tutorial.

Al ejecutar el comando, por consola nos imprimirá el contenido del objeto ReplSetTest que acabamos de crear. Este efecto se produce porque la consola de mongoDB, por defecto siempre imprime el resultado de la evaluación de la última expresión. En este caso, la evaluación de la última expresión es la propia variable sampleReplicaSet que contiene la configuración del grupo de réplica que vamos a crear.

Si queremos evitar este efecto, podemos crear el ReplSetTest de la siguiente forma:


5. Arrancar los procesos mongod del grupo de réplica

Hasta ahora, sólo hemos configurado el grupo de réplica, pero no hemos iniciado ninguna instancia de los nodos que forma parte de dicho grupo de réplica.

Para arrancar los nodos del grupo de réplica ejecutaremos la función startSet() sobre el objeto que representa el grupo de réplica.

Como salida del comando veremos la configuración de cada nodo del grupo de réplica y los mensajes de logs que nos indican que están arrancando cada una de las instancias.

Durante la inicialización vemos los puertos en los que se va levantando cada uno de los nodos. En este caso, y por defecto, se levantan en los puertos 20012, 20013 y 20014.

A partir de aquí, es posible que veamos aparecer trazas en esta consola, ya que por defecto, la salida de los tres nodos que se han arrancado con ReplSetTest se volcará en esta shell.


6. Arrancar el proceso de réplica

En este punto, tenemos los tres procesos de servicio mongod que forman parte de nuestro grupo de réplica, pero no ha arrancado todavía la funcionalidad de réplica de datos.

Para activar la réplica tendremos que invocar la funcion initiate() sobre el ReplSetTest.

Al ejecutar la función, veremos como salida de consola nos muestra la configuración de miembros del réplica set y a continuación se activa la funcionalidad de réplica en el grupo

Una vez activada la funcionalidad de réplica, veremos aparecer trazas en esta consola, ya que por defecto, la salida de los tres nodos que se han arrancado con ReplSetTest se volcará en esta consola.


7. Prueba del grupo de réplica

Una vez que ya está configurado y activo el grupo de réplica, vamos a probar cómo se produce la réplica de datos entre todos los nodos que forman parte del grupo.


7.1. Arranque de una nueva consola

Para evitar que nuestras pruebas se vean entremezcladas con las trazas que los nodos del grupo de réplica están volcando en la consola con la que hemos configurado y arrancado el ReplSetTest, vamos a utilizar una nueva consola.

7.2. Conexión al nodo primario del grupo de réplica

Entre todos los nodos de un grupo de réplica, éstos pueden jugar dos roles distintos:

  • nodo primario: sólo existe uno y es el único que acepta operaciones tanto de escritura como de lectura.
  • nodo secundario: pueden existir más de uno. Son nodos que replican los datos del nodo primario, pero no aceptan operaciones de escritura ni de lectura (por defecto, aunque puede configurarse para permitir las operaciones de lectura).
  • nodos árbitro: son nodos que no almacenan réplica de datos, su única función es participar en el proceso de elección de un nuevo nodo primario entre los nodos secundarios cuando el nodo primario anterior se cae

Para probar el funcionamiento del grupo de réplica que hemos levantado, es necesario que nos conectemos al nodo primario. Como a priori no podemos saber cuál es el nodo primario (el nodo primario se elige en un proceso de votación entre todos los nodos), deberemos conectarnos a cada uno de ellos y preguntar si es el nodo primario.

Para obtener la conexión a uno de los demonios mongod crearemos un nuevo objeto Mongo pasando como argumento la cadena de conexión compuesta del hostname y el puerto (que corresponderá con los puertos que vimos en la salida al arrancar los procesos en el apartado 5):

Una vez obtenida la conexión, obtenemos la BD sobre la que realizaremos la prueba. En nuestro caso, para esta prueba vamos a utilizar la propia bd de test.

Por último, sobre la BD, preguntaremos si es el nodo primario, utilizando la función isMaster():

Del objeto devuelto, nos fijaremos en la propiedad ismaster y secondary. Si el nodo al que nos hemos conectado no es primario, volveremos a conectarnos al siguiente nodo del grupo de réplica, hasta que encontremos el nodo primario (o podemos identificarlo como el que está marcado con el atributo primary)

7.3. Insertamos un conjunto de datos sobre el nodo primario

Una vez que ya estamos conectados al nodo primario, vamos a ejecutar una inserción de un conjunto de datos en la colección de ejemplo (en nuestro caso, por ejemplo una serie de entradas de un blog).

Por último, comprobamos que se han almacenado los registros en la colección.

7.4. Comprobación de la réplica sobre los nodos secundarios

Una vez que hemos insertado los datos a través del nodo primario, vamos a conectarnos a alguno de los nodos secundarios, y comprobar si se han replicado los datos a ese nodo.

Empezamos por conectarnos a uno de los nodos secundarios, obtener la conexión y comprobamos que, efectivamente, el nodo es secundario:

Ahora que estamos ya conectados a la base de datos en el nodo secundario dentro del grupo de réplica, vamos a comprobar si los datos que hemos insertado en el nodo primario, se han replicado en este secundario.

Para ello hacemos una consulta a la colección (debería devolver los mismos datos que en la colección del nodo primario):

En este caso obtenemos un error porque por defecto, tal y como comentamos en el 7.2 sobre la conexión al nodo primario del grupo de réplica, los nodos secundarios en un grupo de réplica, no admiten operaciones ni de escritura ni de lectura. Todas las operaciones deben realizarse siempre sobre el nodo principal.

Sin embargo, podemos activar el permiso para realizar operaciones de lectura sobre un nodo secundario.

Para ello utilizaremos la función setSlaveOK() sobre la conexión al nodo secundario. La invocación de esta función significa que le estamos indicando a mongoDB que somos conscientes de que estamos trabajando sobre un nodo secundario. A partir de aquí la responsabilidad de lo que hagamos es nuestra, 😉

En este momento, ya podemos lanzar la operación de consulta y comprobar que la réplica de datos funciona:

En este caso, comprobamos que los datos que insertamos en el nodo principal se han replicado en el nodo secundario.


8. Promoción automática de un nodo secundario ante la caída del primario

Como hemos explicado, los nodos pueden tomar varios roles (primario, secundario y árbitro) en un grupo de réplica, siendo sólo el nodo primario el que admite las operaciones de escritura y de consulta.

En este apartado, vamos ahora a probar qué ocurre cuando el nodo primario se cae, y comprobar que automáticamente uno de los nodos secundarios toma el papel de primario y empieza a admitir las operaciones.

8.1. Parada del nodo primario

Empezamos por parar específicamente el nodo primario, para simular una caída (o una pérdida de conexión). Para ello podríamos :

  • ejecutar el comando kill a nivel de sistema operativo
  • o simplemente enviar el comando de parada del nodo a través del API JS de la consola.

Optaremos por la segunda opción, pero comprobando previamente que el nodo al que nos hemos conectado es el primario:

En este caso, vemos que, el nodo primario es el 20014. Vamos a parar este nodo, para ello emitimos el comando de apagado:

En este momento, en la consola de mongo en la que habíamos creado el ReplSetTest, y donde se están volcando las trazas, veremos que se ha detectado una pérdida de conexión (“Error in heartbeat”) con el nodo que hemos apagado e, inmediatamente, se produce un proceso de election para elegir al nuevo nodo primario:

En las trazas vemos que el demonio d20012 ha perdido conectividad con el pulso del nodo que corre en el puerto 20014, y cómo este inicia un proceso de elecciones en el que se erige en candidato a ser el primario.

Después de ser validado para ser el nuevo primario, promociona a nodo PRIMARY, o master.

8.2. Comprobación del nuevo nodo primario

Vamos a ahora a comprobar que el nodo efectivamente es el primario. Primero obtenemos una conexión al nuevo nodo primario:


9. Parada del ReplicaSet de pruebas

Por último, una vez finalizada la prueba de cómo funciona el mecanismo de réplica, vamos a parar el grupo de réplica.

Para ello, en la consola de mongo donde configuramos y arrancamos el grupo de réplica (la consola donde estamos viendo las trazas de los nodos), ejecutamos el siguiente comando para parar el grupo de réplica:


A. Configuración avanzada en la construcción del ReplSetTest

En la creación del ReplSetTest, se puede indicar como argumento un objeto JSON con la configuración detallada de cómo queremos crear el RéplicaSet.

Este objeto JSON puede tener las siguientes propiedades:

  • name: de tipo String, con el nombre que queremos asignar al grupo de réplica. Por defecto toma el valor testReplSet
  • host : de tipo String, con el nombre de la máquina donde se va a crear el grupo de réplica. Por defecto toma el hostname.
  • useHostName: de tipo boolean, indica si se debe utilizar el hostname de la máquina como host (en caso de true) o utiliza “localhost” (en caso de ser false) para los nodos. Por defecto a true.
  • nodes: Indica las instancias de mongod que formarán parte del grupo de réplica. El valor de este atributo de configuración puede ser de varios tipos:
    • de tipo entero, indica el número de instancias de mongod que se crearán en el grupo de réplica. Por defecto toma el valor 0.
    • de tipo objeto JSON, con la configuración de la instancia que quiere que sea parte del grupo de réplica. Este objeto JSON puede tener la siguiente estructura (atributos) de configuración
      • useHostName : de tipo boolean, cuando se configura a true utiliza el hostname de la máquina.
      • forceLock: de tipo boolean, si está puesto a true, borra el fichero de lock
      • dbpath: de tipo string, con la ruta de los ficheros de base de datos. Por defecto /data/db/{nodename}
      • cleanData: de tipo boolean, si está configurado a true, elimina los ficheros que hubiese en la ruta dbpath antes de arrancar la instancia
      • startData: equivalente cleanData.
      • noCleanData: de tipo boolean, si está puesto a true mantiene los ficheros que ya existiesen en la ruta dbpath. Este valor tiene prioridad si está especificado también la propiedad cleanData.
      • arbiter:: de tipo boolean, indica si la instancia se quiere que tome el papel de árbitro para las votaciones en caso de caída del nodo primario del grupo de réplica.
    • de tipo array, con los distintos objetos JSON de configuración (según están descritos en el punto anterior) de cada una de las instancias que se quieren incluir en el grupo de réplica.
  • nodeOptions: de tipo objeto JSON, con las opciones que se quieren aplicar a todos las instancias que formarán parte del grupo de réplica. Este objeto toma los valores de los argumentos que pasaríamos por linea de comandos al comando mongod arrancar las instancias de cada uno de los nodos del grupo de réplica.
  • opLogSize: de tipo numérico, tamaño del registro de operaciones que se mantienen para poder sincronizar los distintos nodos del grupo de réplica después de una caída de uno o varios de ellos. Por defecto toma el valor 40.
  • useSeedList: de tipo boolean, si se configura, la cadena de conexión se utilizará como nombre para el replicaSet. Este valor sobreescribe el valor asignado al atributo name, si se ha configurado. Por defecto toma el valor false
  • protocolVersion: de tipo numérico, indica la versión del protocolo a utilizar en la inicialización del grupo de réplica.

Resolución de problemas


startSet() no arranca los demonios mongod del grupo de réplica, por error en la ruta.

Configuración avanzada en la construcción de ReplSetTest

Si al ejecutar el comando de arranque de los nodos del del grupo de réplica no vemos que se levanten los procesos mongod y obtenemos un error de permiso denegado o de acceso a la ruta donde están los ficheros de la BD:

deberemos comprobar que tenemos permisos de acceso y escritura en la ruta de configurada como dbpath (por defecto /data/db):

  • Podemos asignar permisos en /data/db al usuario con el que ejecutamos la consola mongo
  • Podemos ejecutar mongo –nodb como superusuario
  • Podemos crear el ReplSetTest utilizando un dbpath diferente para los nodos del grupo de réplica. Ver propiedad dbpath del atributo nodes del objeto JSON que pasamos como configuración al constructor