Cada vez con menos frecuencia (porque Erlang es cada vez más popular), me preguntan por qué Membase eligió utilizar Erlang para nuestro componente de gestión de clusters y supervisión de procesos. Alternativas comunes que la gente sugiere son Java, C++, Python, Rubyy, más recientemente, node.js y Clojure (que sería mi primera opción si Erlang estuviera fuera de mis límites).
Sin duda, utilizar Erlang tiene muchas desventajas. En primer lugar, es otro entorno de ejecución que construir, empaquetar y soportar - dependemos del sistema Python en Linux y OS X y usamos py2exe para construir ejecutables para Windows, pero en realidad construimos y empaquetamos Erlang. Con Java, podríamos depender potencialmente de que el sistema ya tenga Java instalado - un poco más difícil en Linux pero más fácil en Windows. Además, eventualmente necesitaremos un runtime Java para poder ejecutar módulos Java NodeCode.
Además, no hay tantos programadores que conozcan Erlang como Java o C++, o de hecho cualquiera de las posibles alternativas que he mencionado. Su sintaxis y semántica son inusuales dado su Prolog y el hecho de que se trata de un lenguaje pragmático que lleva utilizándose y evolucionando desde 1986. Esto no ha resultado ser un problema para el equipo de ingenieros de Membase: cuatro de nosotros podemos trabajar en los componentes Erlang, y el código Erlang es lo suficientemente pequeño como para que cualquiera de nosotros pueda mantenerlo fácilmente. Sin embargo, definitivamente pone el listón más alto tanto para nuestros propios desarrolladores como para los colaboradores de la comunidad para poder contribuir al subsistema de gestión de clústeres si aún no conocen Erlang.
Merece la pena. Erlang (Erlang/OTP en realidad, que es a lo que la mayoría de la gente se refiere cuando dice "Erlang tiene X") hace desde el principio un montón de cosas que habríamos tenido que construir desde cero o intentar juntar bibliotecas existentes para hacerlas. Su sistema de tipos dinámico y concordancia de patrones (ala Haskell y ML) hacen que el código Erlang tienda a ser aún más conciso que Python y Ruby, dos lenguajes conocidos por su capacidad de hacer mucho en pocas líneas de código.
Para nosotros, la mayor ventaja de utilizar Erlang es su compatibilidad integrada con concurrencia. Erlang modela las tareas concurrentes como "procesa" que sólo pueden comunicarse entre sí mediante el paso de mensajes (¡que utiliza la concordancia de patrones!), en lo que se conoce como el sistema modelo de actor de concurrencia. Esto por sí solo hace que toda una clase de errores relacionados con la concurrencia sean completamente imposibles. Aunque no evita completamente el bloqueoresulta bastante difícil pasar por alto un posible escenario de bloqueo cuando se escribe código de esta manera. Si bien es cierto que posible para implementar el modelo de actor en la mayoría, si no en todos, de los entornos alternativos que he mencionado, y de hecho tales implementaciones existen, o bien están incompletas o sufren un desajuste de impedancia con las bibliotecas existentes que esperan que utilices hilos.
Los procesos Erlang, además de estar aislados unos de otros, son muy ligeros. Se pueden ejecutar fácilmente un cuarto de millón de ellos en una única máquina virtual Erlang. El coste de generarlos es también bastante bajo, lo que hace innecesarios hacks tontos para reutilizarlos. Dado que se ejecutan en el mismo espacio de memoria y sólo están aislados por software, el paso de mensajes entre ellos es extremadamente rápido y, aunque la mayoría de las veces implica copiar los datos del mensaje, los mensajes suelen ser pequeños y esto permite que los procesos Erlang se recolecten de forma independiente.
OTP organiza los procesos en un "árbol de supervisión" en el que los procesos supervisores controlan a los procesos hijos, que también pueden ser supervisores, y los reinician, y potencialmente a cualquier proceso dependiente, en caso de que se bloqueen. Junto con el aislamiento de procesos, esto hace que las aplicaciones Erlang sean extremadamente tolerantes a fallos: cualquier proceso Erlang dentro de nuestro subsistema de gestión de clústeres (que no sea el supervisor raíz) puede bloquearse sin que Membase se caiga. De hecho, hacemos muy buen uso de esto: cuando ocurren cosas inesperadas en cualquiera de nuestros procesos, generalmente dejamos que se bloqueen y se reinicien, devolviéndolos a un buen estado conocido. Por ejemplo, el proceso que gestiona la conexión administrativa al proceso local de memcached simplemente se bloqueará si la conexión se agota o devuelve cualquier error que no se espera. Esto hace que el manejo de errores sea exactamente igual que el arranque, lo que deja menos lugares para que se escondan los bugs y facilita las pruebas.
Para facilitar la estructuración de una aplicación compleja como un conjunto de procesos que interactúan, Erlang/OTP proporciona un conjunto estándar de "comportamientos"que pueden aplicar los módulos. El más común de ellos es gen_servidorel comportamiento genérico del servidor, que implementa un servidor básico con callbacks para las peticiones. Gen_fsm implementa una máquina de estados finitos, y gen_event es para implementar manejadores de eventos y gestores de eventos.
Los mensajes en Erlang se pueden enviar tan fácilmente a un proceso que reside en otro nodo como a procesos en el mismo nodo; esto es completamente transparente para el programador. Erlang/OTP tiene un montón de módulos que ayudan con el procesamiento distribuido. Por ejemplo, el módulo "global" proporciona un registro común de nombres globales y bloqueos, similar al gestor de bloqueos Chubby de Google. Las "aplicaciones distribuidas" de OTP proporcionan una forma de asegurar que un determinado conjunto de procesos sólo se inicie en un nodo. Mnesia es un DBMS distribuido con fuertes garantías de consistencia que utilizamos para almacenar estadísticas.
Erlang ofrece un excelente soporte para la depuración y la aplicación de parches en sistemas activos. Después de todo, se inventó para construir sistemas con cinco nueves de tiempo de actividad. Si conoce la cookie criptográfica que los nodos utilizan para autenticarse entre sí, es trivial adjuntar otro nodo Erlang al clúster y ejecutar comandos arbitrarios en cualquier otro nodo, incluyendo "recargar este módulo". Este tipo de capacidad es muy valiosa cuando se necesita para corregir o trabajar en torno a un error y no se puede tomar abajo de un clúster o incluso un solo nodo para reemplazar el código. También es muy valiosa para las pruebas o cualquier situación en la que nosotros (o un cliente) queremos hacer cambios en el estado del clúster que aún no se han implementado en la API REST.
Al fin y al cabo, la verdadera cuestión no es si nos habría sido posible implementar nuestra gestión de clústeres en otro lenguaje; es realmente una cuestión de esfuerzo y mantenibilidad del resultado. Con cualquier otro entorno, habríamos tenido que reimplementar al menos parte de lo que Erlang/OTP proporciona, mientras que nosotros realmente no nos hemos encontrado reimplementando características proporcionadas por ningún otro entorno.