Tutorial MongoDB. Simulando fallos en los conjuntos de réplicas

En los últimos artículos sobre conjuntos de réplicas de este tutorial de MongoDB, hemos podido ver cómo crear un conjunto de réplicas, cómo configurarlo y cómo se trabaja con él. Lo único que nos queda por ver es cómo se comporta MongoDB en situaciones en las que algún miembro del conjunto deja de funcionar. Así que en esta ocasión intentaremos poner en apuros a nuestras réplicas para ver cómo se comportan.

Nota: recuerda que para probar los ejemplos de este artículo, necesitas crear un conjunto de replicas. Si no sabes como hacerlo aquí explico como hacerlo. Si no sabes qué son las réplicas, en este otro artículo explico qué son y para qué sirven.

Verificando el estado de los servidores

En nuestro estado inicial, tenemos tres servidores MongoDB corriendo en los puertos 27666, 27667 y 27668. Uno de ellos será el servidor principal, y los otros serán servidores secundarios. Para saber en qué estado está cada uno, podemos conectarnos a través de la consola de MongoDB a cualquiera de los servidores y ejecutar el comando rs.status. Veamos un ejemplo:

rep1:PRIMARY> rs.status()
{
    "set" : "rep1",
    "date" : ISODate("2014-01-13T22:08:19Z"),
    "myState" : 1,
    "members" : [
            {
                    "_id" : 0,
                    "name" : "CYLON-MACHINE:27666",
                    "health" : 1,
                    "state" : 1,
                    "stateStr" : "PRIMARY",
                    "uptime" : 1250,
                    "optime" : Timestamp(1389220435, 1000),
                    "optimeDate" : ISODate("2014-01-08T22:33:55Z"),
                    "self" : true
            },
            {
                    "_id" : 1,
                    "name" : "CYLON-MACHINE:27667",
                    "health" : 1,
                    "state" : 2,
                    "stateStr" : "SECONDARY",
                    "uptime" : 1205,
                    "optime" : Timestamp(1389220435, 1000),
                    "optimeDate" : ISODate("2014-01-08T22:33:55Z"),
                    "lastHeartbeat" : ISODate("2014-01-13T22:08:17Z"),
                    "lastHeartbeatRecv" : ISODate("2014-01-13T22:08:18Z"),
                    "pingMs" : 0,
                    "syncingTo" : "CYLON-MACHINE:27666"
            },
            {
                    "_id" : 2,
                    "name" : "CYLON-MACHINE:27668",
                    "health" : 1,
                    "state" : 2,
                    "stateStr" : "SECONDARY",
                    "uptime" : 1142,
                    "optime" : Timestamp(1389220435, 1000),
                    "optimeDate" : ISODate("2014-01-08T22:33:55Z"),
                    "lastHeartbeat" : ISODate("2014-01-13T22:08:18Z"),
                    "lastHeartbeatRecv" : ISODate("2014-01-13T22:08:18Z"),
                    "pingMs" : 0,
                    "syncingTo" : "CYLON-MACHINE:27666"
            }
    ],
    "ok" : 1
}

Cómo podemos ver en el resultado, el servidor que escucha por el puerto 27666 es el servidor principal. También podemos ver otros datos interesantes, como el estado en el que se encuentran los servidores, o cuándo han sido actualizados por última vez. Cómo nosotros no hemos ejecutado ninguna operación de actualización nueva, los campos optimeDate y optime son iguales para los tres servidores.

¿Qué pasa si se cae un servidor?

Para simular la caída de un servidor, simplemente vamos al administrador de tareas y eliminamos uno de los procesos de MongoDB que están en ejecución. En este caso vamos a eliminar el proceso del servidor principal, ya que así nos aseguraremos de que se lance un proceso de elección de nuevo principal.

Tras eliminar el proceso, veremos en la consola de los otros dos servidores, que estos tratan de localizar al servidor que no está funcionando:

Mon Jan 13 23:17:54.849 [rsBackgroundSync] replSet sync source problem: 10278 db client error communicating with server: CYLON-MACHINE:27666
Mon Jan 13 23:17:54.850 [rsBackgroundSync] replSet syncing to: CYLON-MACHINE:27666  Mon Jan 13 23:17:54.891 [rsHealthPoll] DBClientCursor::init call() failed
Mon Jan 13 23:17:54.892 [rsHealthPoll] replset info CYLON-MACHINE:27666 heartbeat failed, retrying 
Mon Jan 13 23:17:55.849 [rsBackgroundSync] repl: couldn't connect to server CYLON-MACHINE:27666
Mon Jan 13 23:17:55.850 [rsBackgroundSync] replSet not trying to sync from CYLON-MACHINE:27666, it is vetoed for 10 more seconds
Mon Jan 13 23:17:55.851 [rsBackgroundSync] replSet not trying to sync from CYLON-MACHINE:27666, it is vetoed for 10 more seconds
Mon Jan 13 23:17:55.887 [rsHealthPoll] replSet info CYLON-MACHINE:27666 is down (or slow to respond):
Mon Jan 13 23:17:55.888 [rsHealthPoll] replSet member CYLON-MACHINE:27666 is now in state DOWN

Como podéis ver, en este proceso el servidor que ha fallado es marcado como caído. Si volvemos a ejecutar el comando rs.status, veremos que los datos de ese servidor han cambiado.

{
     "_id" : 0,
     "name" : "CYLON-MACHINE:27666",
     "health" : 0,
     "state" : 8,
     "stateStr" : "(not reachable/healthy)",
     "uptime" : 0,
     "optime" : Timestamp(1389220435, 1000),
     "optimeDate" : ISODate("2014-01-08T22:33:55Z"),
     "lastHeartbeat" : ISODate("2014-01-13T22:27:11Z"),
     "lastHeartbeatRecv" : ISODate("2014-01-13T22:17:54Z"),
     "pingMs" : 0
}

En ese momento, al detectar que el servidor principal no está accesible, se lanza una elección para decidir cuál será el nuevo servidor principal.

Mon Jan 13 23:17:55.985 [rsMgr] replSet info electSelf 1
Mon Jan 13 23:17:55.987 [conn120] replSet voting no for CYLON-MACHINE:27668 already voted for another
Mon Jan 13 23:17:55.989 [rsMgr] replSet couldn't elect self, only received 1 votes
Mon Jan 13 23:17:58.891 [rsHealthPoll] replset info CYLON-MACHINE:27666 heartbeat failed, retrying
Mon Jan 13 23:18:00.641 [rsMgr] replSet info electSelf 1
Mon Jan 13 23:18:00.855 [rsMgr] replSet PRIMARY

Cuando se producen unas elecciones para nuevo servidor principal, los servidores se comunican unos con otros con la intención de decidir quién es el servidor que debería ser el principal. El proceso de elección tiene múltiples variantes, que ya expliqué en el artículo teórico sobre conjuntos de réplicas.. En nuestro caso el servidor que escucha por el puerto 27667 se ha establecido como principal tras finalizar la elección.

¿Qué pasa si se cae otro servidor más?

Si siguiendo el mismo procedimiento, matamos el proceso de otro de los servidores, el mensaje que mostrará la consola será el siguiente:

Mon Jan 13 23:48:01.731 [rsMgr] replSet can't see a majority, will not try to elect self

En este caso el servidor restante nunca se va a convertir en el servidor principal, ya que no es capaz de conectar con la mayoría de servidores. Recordemos con mayoría de servidores nos estámos refiriendo a los votos en juego, que en este caso eran 3 votos. Para convertirse en principal un servidor debería conseguir al menos 2 votos, pero ahora esto es algo imposible, ya que al caerse los otros servidores, solo hay un voto en juego.

Si volvemos a arrancar uno de los servidores, se volverá a lanzar un proceso de elección y se elegirá de nuevo un servidor principal.

Forzando las elecciones para cambiar el servidor principal

Si volvemos a arrancar todos los servidores, es posible que la configuración de los servidores haya cambiado. Dependiendo de como se hayan desarrollado las elecciones el servidor principal ahora puede ser otro. Esto será así hasta que se produzca una nueva elección. Pero no necesitamos esperar a qué se produzca un fallo, ya que podemos forzar el proceso conectándonos al servidor principal y ejecutando el comando rs.stepDown

rep1:PRIMARY> rs.stepDown()
Tue Jan 14 00:00:06.013 DBClientCursor::init call() failed
Tue Jan 14 00:00:06.021 Error: error doing query: failed at src/mongo/shell/quer
y.js:78
Tue Jan 14 00:00:06.023 trying reconnect to localhost:27668
Tue Jan 14 00:00:06.025 reconnect localhost:27668 ok
rep1:SECONDARY> rs.status()

Como vemos en el ejemplo, se lanzan unas nuevas elecciones, en las que no entra el servidor actual, por lo que se elige uno de los otros dos servidores como principal.

Cambiando la prioridad de los servidores.

Si todos los servidores están en el mismo estado, están actualizados y tienen la misma prioridad, la elección puede elegir a cualquiera de los servidores como principal. Si queremos darle mayor importancia a uno de ellos, y asegurarnos de que es elegido como principal si está disponible, deberemos jugar con la prioridad. Veamos un ejemplo de como podemos cambiarlo.

rep1:PRIMARY> conf = rs.conf()
{
    "_id" : "rep1",
    "version" : 3,
    "members" : [
            {
                    "_id" : 0,
                    "host" : "CYLON-MACHINE:27666"
            },
            {
                    "_id" : 1,
                    "host" : "CYLON-MACHINE:27667"
            },
            {
                    "_id" : 2,
                    "host" : "CYLON-MACHINE:27668"
            }
    ]
}   
rep1:PRIMARY> conf.members[1].priority=2
2
rep1:PRIMARY> rs.reconfig(conf)
Tue Jan 14 00:08:41.806 DBClientCursor::init call() failed
Tue Jan 14 00:08:41.808 trying reconnect to localhost:27666
Tue Jan 14 00:08:41.811 reconnect localhost:27666 ok reconnected to server after rs command (which is normal)

rep1:SECONDARY>

Como ya habíamos dicho anteriormente un servidor MongoDB tiene por defecto una prioridad de 1. En nuestro ejemplo, hemos cambiado la prioridad del servidor que escucha por el puerto 27667 a 2, por lo que se convierte en el servidor con la prioridad más alta. Desde este momento, siempre que se produzca un fallo en los otros servidores, en las elecciones resultantes, este servidor será elegido como principal al tener la prioridad más alta.

Conclusiones

MongoDB tiene un sistema robusto y fiable para elegir un servidor principal en caso de que se produzca un fallo. En una arquitectura en la que solo puede haber un servidor principal, y en la que solo se pueden realizar operaciones de actualización sobre el servidor principal, este proceso es crítico. En el próximo artículo, veremos como se comporta un conjunto de réplicas a la hora de realizar cambios, y qué pasa cuándo un servidor falla y tiene que actualizar sus datos.



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