Imagen destacada

desarrollo   2023-02-23 2023-02-23   4 minutos de lectura

Supervisores: Apagado Elegante sin Perder Datos

Supervisores: Apagado Elegante sin Perder Datos

  0   erlang     elixir     otp     supervisor     0   erlang   elixir   otp   supervisor

Los supervisores son un comportamiento fundamental en OTP y dentro de BEAM, la máquina virtual de Erlang y donde también se emplean Elixir y Gleam entre otros. Los supervisores mantienen procesos bajo supervisión y permiten su reanimación en caso de terminar, pero ¿sabías que también se emplean para terminar de forma elegante los sistemas?

Hay muchos sistemas desarrollados en Erlang que a día de hoy son elementos básicos para muchos sistemas. Podemos encontrarnos a RabbitMQ como uno de los sistemas de gestión de colas (o brokers) más empleados, también ejabberd como el sistema que dio las bases para empresas como Whatsapp o también la base de datos Couchbase empleada por muchas grandes empresas como PayPal.

Uno de los grandes atractivos de BEAM, Erlang y OTP es la capacidad para lidiar con grandes cantidades de peticiones sin degradación. La capacidad para manejar millones de procesos con una huella de memoria muy baja y un sistema de recolección de basura impecable gracias a elementos como la inmutabilidad de sus datos. Pero además de poder tratar con grandes cantidades de datos, todos estos sistemas necesitan mantener un nivel aceptable de consistencia. Si un nodo debe ser apagado y retirado, necesitamos detener su funcionamiento de una forma elegante (graceful) para no perder datos.

Apagado elegante

En inglés se suele denominar gracful shutdown (apagado elegante) a la forma de detener un sistema sin que queden tareas pendientes de realización. Por ejemplo, en un sistema como ejabberd donde hay miles de usuarios intercambiando mensajes, si el sistema se detuviese de repente, muchos mensajes quedarían en tránsito, no encontrarían al proceso de destino o no se almacenarían. Entonces, el apagado sucede por fases:

  1. Notificamos en las conexiones de los usuarios la desconexión del sistema. Esperamos hasta que todos los usuarios han sido desconectados.
  2. Mandamos a los supervisores de los procesos de usuarios la señal de apagado y esperamos a su terminación. Los procesos de usuario deberán almacenar en la base de datos los mensajes no procesados y detener su ejecución.
  3. Mandamos mensaje de detención a las conexiones a las bases de datos. Estas deberán terminar sus procesos y terminar cuando hayan finalizado el almacenamiento pendiente de todo.
  4. Una vez todos los supervisores han sido apagados, la aplicación puede detenerse.
  5. Cuando todas las aplicaciones se detienen, BEAM se detiene.

Obviamente, los pasos a dar dependen del sistema pero si hemos realizado la configuración de nuestro sistema de forma adecuada, la detención de los supervisores según su configuración nos ayudará de forma fácil y segura a realizar todas estas acciones.

La acción de los Supervisores

Cuando escribí el libro sobre Elixir/OTP lo hice pensando en proporcionar un conocimiento sobre comportamientos de OTP a programadores de Elixir para cuando necesiten desarrollar algunos elementos de backend más complejos que una simple página web sepan cómo realizar estas arquitecturas. Hablando recientemente con un compañero me preguntó: ¿pero realmente hace falta un supervisor?

Es decir, si dentro de un proceso nosotros lanzamos el proceso invocando la típica función start_link tendremos la funcionalidad requerida. Al igual que si dentro de otro proceso invocamos una tarea (Task), esta se lanza y no hay mayor problema. Hasta que decidamos detener el sistema. Al quedar fuera del árbol de supervisión, este proceso no es controlado para su detención por el supervisor, por lo tanto podría seguir funcionando hasta recibir la señal imperativa de BEAM de detener todo para finalizar al completo y dejar tareas pendientes.

Además, el árbol de supervisión de una aplicación parte del supervisor principal y de ahí puede ramificarse en otros supervisores y todos los procesos que supervisa. Lanzando un proceso con start_link hacemos que quede fuera del árbol de supervisión. Es más, queda enlazado a otro proceso y si alguno de los dos tiene un fallo o es detenido con una señal de terminación diferente a normal, el otro proceso cae con él. El supervisor proporciona una forma de reiniciar el proceso en caso de fallo y un aislamiento entre ambos para agregar una tolerancia a fallos.

Señales shutdown

Las señales de apagado se propagan desde el proceso de la aplicación a través de sus supervisores a todos los procesos enlazados a algún proceso dentro del árbol de supervisión o bajo la supervisión de algún supervisor. Si nuestro proceso no es crítico la señal lo forzará a abandonar su ejecución y dejará de atender mensajes para proceder con el apagado. Por lo tanto no ejecuta la retro-llamada de terminación (terminate) disponible en la mayoría de los comportamientos.

Si el proceso es crítico solemos configurarlo con un tiempo de apagado acorde al tiempo que necesita el proceso para su apagado completo (por defecto se dejan 5 segundos) y el proceso debe capturar las señales. Esto es:

Process.flag(:trap_exit, true)

O en Erlang:

process_flag(trap_exit, true)

Cuando una señal llegue al proceso, en lugar de finalizar inmediatamente, el proceso recibirá el mensaje y podrá realizar alguna tarea en consecuencia o al menos terminar la tarea en curso. Por lo tanto, no solo depende del supervisor, sino también de cada proceso entender que se encuentra en modo de apagado.

Conclusiones

Erlang fue diseñado con la metodología non-stop o run forever pero eso no siempre es cierto. En muchos momentos necesitaremos reiniciar el nodo para actualizar la versión de Erlang de forma segura, o reiniciar la máquina para actualizar el sistema operativo, o detener nuestra aplicación porque la actualización es demasiado compleja. Sea cual sea el motivo, necesitamos poder detener nuestro sistema y salvo excepciones donde un apagado o detener el servicio puntualmente no es un problema, siempre da una mejor experiencia de usuario cuando el reinicio de los sistemas es rápido y casi imperceptible.

Gracias por leer y recuerda que puedes dejarnos tu comentario.

Comentarios

No hay comentarios. Inicia sesión para enviar un comentario.