1 puntos por GN⁺ 2025-04-13 | 1 comentarios | Compartir por WhatsApp
  • La clave que permitió a Erlang crear sistemas distribuidos confiables no estuvo tanto en los procesos ligeros en sí, sino en los behaviours, que generalizan patrones recurrentes de concurrencia y manejo de fallas
  • Los behaviours ofrecen puntos de implementación como si fueran interfaces, pero también ocultan el manejo de concurrencia dentro de componentes comunes como gen_server, haciendo que el código de aplicación se parezca más a lógica secuencial
  • La tesis de Joe Armstrong trata el caso de construir sistemas grandes como AXD301 usando solo pequeños componentes como gen_server, gen_event, gen_fsm, supervisor, application y release
  • supervisor reinicia procesos fallidos con estrategias como one_for_one y one_for_all, y la filosofía “Let it crash!” traslada la responsabilidad de recuperación al árbol de supervisors
  • Los behaviours con forma de máquina de estados facilitan las pruebas por simulación y la verificación formal, y permiten que los desarrolladores se concentren en la semántica del problema en vez de la complejidad de la concurrencia y del simulador

El problema que Erlang intentaba resolver

  • Erlang nació como una herramienta para crear sistemas distribuidos confiables
    • Al principio era una biblioteca de Prolog para crear sistemas distribuidos confiables; luego pasó por un dialecto de Prolog y terminó convirtiéndose en un lenguaje independiente
    • Se usó para programar centrales telefónicas de Ericsson, y en los años 80 y 90 manejó tráfico a escala de cientos de millones de personas y SLA estrictos
  • En 1998 Ericsson prohibió el uso de Erlang, y el equipo de desarrollo argumentó que, si iban a prohibirlo, debían publicarlo como open source; Ericsson lo hizo
  • Joe Armstrong fue una de las figuras clave en el diseño y la implementación de Erlang, y en 2002 empezó su tesis doctoral en SICS, que completó en 2003 con el título Making reliable distributed systems in the presence of software errors
  • Esta tesis se enfoca menos en matemáticas o teoría, y más en las ideas que sostienen Erlang y en la experiencia de construir sistemas distribuidos confiables
  • La gran idea de Erlang no son los procesos ligeros y el paso de mensajes, sino los componentes genéricos que en Erlang se llaman behaviours

behaviours: combinación de interfaz e infraestructura

  • Los behaviours de Erlang son conjuntos de firmas de tipo que pueden tener varias implementaciones, como las interfaces de Java o Go
  • Cuando el programador proporciona una implementación de esa interfaz, puede usar funciones genéricas escritas contra esa interfaz
  • La diferencia clave es que los behaviours van más allá de una simple interfaz y también proporcionan código de infraestructura
    • El programador de aplicaciones escribe la semántica del problema, es decir, la lógica de negocio
    • El código de infraestructura, como la concurrencia, lo proporciona automáticamente el behaviour
    • Los behaviours incorporan buenas prácticas escritas por especialistas con experiencia
    • Si todo el sistema reutiliza un conjunto pequeño de behaviours, cuando mejora la implementación de un behaviour, el sistema también puede mejorar sin cambios de código
    • El uso de behaviours impone estructura, lo que facilita las pruebas y la verificación formal

gen_server: concurrencia escondida detrás de código secuencial

  • El ejemplo de gen_server es un almacén clave-valor: store guarda pares clave-valor y lookup busca el valor de una clave
  • El callback central, handle_call, actualiza el estado Dict para una solicitud store, y en una solicitud lookup busca la clave en el estado
  • Si se entrega esta implementación a gen_server, se crea un servidor capaz de manejar solicitudes concurrentes de store y lookup
  • Lo importante es que handle_call en sí es completamente secuencial
    • La concurrencia queda oculta dentro del componente genérico gen_server
    • El código de aplicación se acerca a una forma que recibe estado y entrada, y produce un nuevo estado y una salida
  • La tesis de Joe Armstrong extiende este enfoque a gen_server, gen_event, gen_fsm, supervisor, application y release

El caso AXD301 y la escala de los behaviours

  • Joe Armstrong usa la central telefónica AXD301 de Ericsson como caso de estudio
  • El proyecto AXD301 incluía los siguientes componentes
    • 122 instancias de gen_server
    • 36 instancias de gen_event
    • 10 instancias de gen_fsm
    • 20 supervisors
    • 6 applications
    • Todo empaquetado como un solo release
  • AXD301 es un sistema con más de un millón de líneas de código Erlang
  • Que un sistema de esta escala se haya construido a partir de un conjunto pequeño de behaviours se vuelve una evidencia central de la estructura de Erlang

El rol de otros behaviours

  • gen_event es un administrador genérico de eventos
    • Registra event handlers y, cuando el administrador de eventos recibe mensajes relevantes, ejecuta el handler
    • Joe Armstrong menciona usos como el registro de errores
    • El logger de ejemplo puede registrar y reportar los últimos cinco mensajes de error
  • gen_fsm es un behaviour de máquina de estados que después pasó a llamarse gen_statem
    • Es más adecuado para implementar protocolos
    • Los protocolos a menudo se especifican como máquinas de estados
    • Se considera que cualquier gen_server puede implementarse como gen_statem, y viceversa
  • application se compone de un árbol de supervisors y los demás elementos necesarios para entregar una aplicación
  • release empaqueta una o más applications
    • También incluye código de manejo de upgrades
    • Si un upgrade falla, debería poder hacer rollback al estado estable anterior

supervisor y “Let it crash!”

  • supervisor es un proceso que verifica que otros procesos estén funcionando correctamente
  • Si un proceso supervisado falla, el supervisor puede reiniciarlo según una estrategia predefinida
  • La estrategia de ejemplo {one_for_one, 5, 1000} significa lo siguiente
    • Si falla uno de packet_assembler, kv o simple_logger, solo se reinicia el proceso fallido
    • Si durante 1000 segundos necesita reiniciarse más de 5 veces, el propio supervisor falla
  • permanent, 500, worker significa que ese proceso es un worker que siempre debe estar vivo, y que el supervisor le da 500 ms para terminar normalmente cuando intenta reiniciarlo
  • En la estrategia one_for_all, si un proceso falla se reinician todos los procesos hijos
  • Un supervisor puede supervisar a otros supervisors, y no necesariamente tienen que ejecutarse en la misma computadora
  • Esta estructura opera a nivel de hilos/procesos ligeros, no a nivel de contenedores Docker, por lo que es difícil verla simplemente como “Kubernetes y ya”
  • “Let it crash!” presupone que el proceso fallido será reiniciado
    • El programa expresa solo el camino normal
    • Si surge un problema en el camino normal, no intenta recuperarse directamente, sino que hace crash
    • Otro programa más arriba en el árbol de supervisors se encarga del manejo
  • La evidencia de estabilidad a largo plazo de AXD301 no se recopiló de forma sistemática, pero la tesis de Joe Armstrong señala que había una cifra en PowerPoint según la cual un cliente importante operaba un sistema de 11 nodos con 99.9999999% de confiabilidad
    • No se documentó cómo se obtuvo esa cifra
    • Otras cifras de reportes de downtime también vienen con la salvedad de que el método de cálculo no está claro

Si se implementaran behaviours en otros lenguajes

  • Los procesos ligeros y el paso de mensajes por sí solos no hacen que Erlang sea adecuado para sistemas confiables
  • Una explicación más honesta es la estructura que proporcionan los behaviours y la forma en que esa estructura conduce a software confiable
  • La firma de interfaz de gen_server puede verse con esta forma
    • Input -> State -> (State, Output)
    • Significa que recibe una entrada y el estado actual, y produce un nuevo estado y una salida
  • La forma de convertir esta firma secuencial en manejo de solicitudes concurrentes puede pensarse así
    • Un servidor HTTP convierte la solicitud en Input y la pone en una cola
    • Un event loop toma entradas de la cola y las entrega a la implementación secuencial
    • La salida se devuelve como respuesta al cliente
    • Para soportar varios gen_server, se podría asignar un nombre a cada servidor e incluir el nombre y la entrada en la solicitud
  • gen_event podría implementarse permitiendo registrar callbacks para tipos de eventos específicos en la cola
  • supervisor puede simplificarse envolviendo llamadas a funciones de gen_server con un manejador de excepciones y, si ocurre una excepción, notificando al supervisor
    • Si el supervisor no se ejecuta en la misma computadora, se vuelve más complejo
  • application y release son importantes porque la configuración, el despliegue y los upgrades son problemas difíciles, pero no se tratan en profundidad

behaviours y capacidad de prueba

  • La estructura de los behaviours de Erlang conduce a pruebas por simulación y verificación formal
  • Un tema de interés reciente son las pruebas de simulación de sistemas distribuidos al estilo FoundationDB
  • Las pruebas por simulación ejecutan el sistema dentro de un mundo simulado, donde la simulación controla por completo el momento de envío de los mensajes de red
  • FoundationDB creó un dialecto de C++ con actors, o bien su propio lenguaje de programación, para las pruebas por simulación
  • Se considera que este enfoque podría haber llegado bastante lejos solo con el siguiente tipo de máquina de estados
    • Input -> State -> (State, [Output])
    • [Output] es una secuencia de salidas
  • El simulador gestiona los mensajes con una cola de prioridad ordenada por hora de llegada
    • Extrae un mensaje
    • Avanza el reloj hasta la hora de llegada de ese mensaje
    • Entrega el mensaje a la máquina de estados receptora
    • Genera nuevas horas de llegada para los mensajes de salida y los vuelve a poner en la cola
  • Si todo es determinista y las horas de llegada se generan a partir de una seed, se pueden explorar varios interleavings y obtener fallas reproducibles
  • Este método es más rápido que Jepsen porque los mensajes se procesan en memoria y el reloj avanza hasta la hora de llegada sin esperar timeouts
  • Esta forma de máquina de estados se llama “network normal form” y lleva a la conjetura de que todo programa que reciba y envíe por red podría refactorizarse a esta forma
  • Que gen_server y gen_statem tengan básicamente la misma estructura de tipos puede verse como una señal de que esta estructura no es arbitraria

Verificación formal y estructura de máquina de estados

  • Joe Armstrong dijo alguna vez en una talk que es difícil implementar correctamente una elección de leader distribuida
  • Si hubiera un simulador, este problema podría simplificarse mucho
    • Igual que un túnel de viento ayuda en la fabricación de aviones, se podrían probar condiciones extremas como redes inestables o pérdida de energía antes de production
  • Este simulador podría ser genérico o parametrizable para los behaviours
    • El desarrollador no tendría que escribir un simulador por separado
    • La complejidad quedaría oculta detrás del behaviour, como el código de concurrencia de gen_server
  • Sobre las pruebas de FoundationDB, Kyle “aphyr” Kingsbury publicó un tuit indicando que las pruebas de FoundationDB parecían mucho más rigurosas que sus propias pruebas Jepsen
  • La verificación formal también se facilita cuando el programa está escrito como una máquina de estados
    • El model checking de TLA+ de Lamport asume que la especificación es una máquina de estados
    • Kleppmann muestra una forma de abordar el problema de explosión de estados mediante inducción estructural usando la estructura de máquina de estados
  • La estructura obtenida de los behaviours de Erlang puede reutilizarse para resolver con más facilidad los problemas que Joe Armstrong consideraba difíciles

Trabajos relacionados y referencias

  • Algunos trabajos relacionados que tomaron ideas de Erlang son los siguientes
    • Trabajo que toma ideas del LMAX Disruptor de Martin Thompson y de aeron para crear un event loop rápido donde se ejecuten los behaviours
    • Trabajo que agrega async I/O al tipo de máquina de estados
    • Trabajo que implementa con más detalle supervisors
    • swapping de hot code en máquinas de estados
  • Como referencias se mencionan la tesis doctoral de Joe Armstrong, los OTP design principles, y la documentación de gen_server, gen_event, gen_statem, supervisor, application y release
  • Sobre la relación entre el actor model y Erlang, se resume que los desarrolladores de Erlang crearon Erlang sin conocer el actor model, y que el artículo de Carl Hewitt documenta las diferencias entre los procesos de Erlang y el actor model
  • Akka tiene “actors” y supervisor trees, pero no parece tener equivalentes a los demás behaviours de Erlang, y el concepto de “behavior” de Akka también es distinto de los behaviours de Erlang

1 comentarios

 
GN⁺ 2025-04-13
Comentarios de Hacker News
  • Lo sorprendente de Erlang y BEAM es la profundidad de sus capacidades. En el texto original, la impresión principal parecía ser Behaviour/Interface, pero para mí pesa más que permite construir sistemas complejos con muchos menos recursos de desarrollo que otros lenguajes.
    OTP en sí mismo ya trae muchísimo. Hice trabajo para compilar Elixir de forma que corriera en dispositivos iOS, y no solo usé el proceso de release de Erlang, sino también la biblioteca ei para compilar un Node en C y comunicarlo con otros nodos de Erlang como Erlang, Elixir y Gleam, como si fuera una red distribuida normal.
    Además, mediante la biblioteca rpc de Erlang, incluso pude llamar desde C a funciones de una aplicación en Elixir. Hay sobrecosto de codificación/decodificación y un FFI sería más rápido, pero entraba de sobra en el presupuesto de latencia y pude montar en pocos días una capacidad de la que ni siquiera había oído hablar antes.
    El punto más grande es que Erlang ya resolvió hace décadas muchos de los problemas con los que hoy batallan los stacks tecnológicos modernos, tanto en escala como en costo de implementación. HN le tiene cierto cariño tipo clickbait a Erlang/Elixir, pero eso no suele traducirse en adopción real, y hay empresas quemando dinero para implementar cosas que en el stack de Erlang ya vienen gratis por defecto

    • Me cambié de una empresa que usaba backend en Elixir a otra que usaba Node.js. Casi no había usado Node.js antes, así que era bastante neutral.
      El proyecto en el que trabajé era un pipeline de datos de backend y ni siquiera procesaba tantos datos, pero aislar con precisión bugs críticos era increíblemente difícil.
      En ese proceso conocí varias características de Node.js y, al compararlo con Elixir/Erlang/OTP, llegué a la conclusión de que Node.js tiene una confiabilidad baja por diseño.
      También hice bastante Ruby y toqué Python, pero muchas de las plataformas de lenguajes de la generación actual tienen dificultades para construir sistemas distribuidos confiables. La VM de BEAM y la plataforma OTP ya resolvieron esa parte.
    • Los bloques de construcción básicos que ofrecen Erlang y sus descendientes son fáciles de manejar, y por eso también son fáciles de probar.
      Por ejemplo, GenServer, que hace de trabajador en la mayoría de los sistemas BEAM, al final no es más que una estructura que llama varias funciones con parámetros simples.
      Entonces puedes probarlo llamando esas funciones directamente, pasando los parámetros a mano y verificando solo la salida. No hace falta montar un sistema complejo de pruebas para lidiar con código asíncrono ni quedarse esperando en los tests a que termine el trabajo.
      Es algo que los juniors suelen pasar por alto, pero una vez que lo entiendes, se siente bastante liberador.
    • Los nodos C están subestimados. Estoy usando nodos C basados en Cgo para comunicar servicios en Go y Elixir que corren dentro del mismo pod de Kubernetes. La documentación de Erlang y de las bibliotecas C también está bastante bien.
    • Tengo curiosidad por cómo ve la comunidad la razón por la que, aunque HN parece querer mucho a Erlang/Elixir, eso no termina convirtiéndose en adopción.
  • He visto a algunas personas, sobre todo managers, decir que van a escribir libros basados en nuestra experiencia. Siempre me frustró que cada quien viera como central una parte distinta de por qué nuestro trabajo tuvo éxito. Lo que yo sentía como esencial ellos solían rebajarlo a algo simplemente deseable.
    Aquí también alguien dice que los procesos livianos y el paso de mensajes no son la salsa secreta, mientras pasa por alto que ver Erlang como procesos secuenciales comunicantes (CSP) no puede separarse de esas características. Y aun así, vuelve a mencionar CSP repetidamente como parte de la salsa secreta.
    Por ejemplo, que el programador de aplicaciones escribe código secuencial y la concurrencia queda escondida dentro de los behaviours, o que la lógica de negocio es secuencial y por eso es fácil para un nuevo integrante empezar, o que los supervisors y la filosofía de “let it crash” producen sistemas confiables.
    Los behaviours son interesantes, eran comunes en los 80 y resuelven problemas que algunos seguían resolviendo en los 2000, pero en Erlang son tanto un fin como un medio. Son la forma en que se implementaron esas otras características, aunque no sé si necesariamente tenía que ser así para que Erlang siguiera siendo Erlang.

    • Erlang no es CSP sino el modelo de actores. https://en.wikipedia.org/wiki/Actor_model
      CSP es la línea que, pasando por occam y otros lenguajes, terminó inspirando los canales de Go. La diferencia más clara es la sincronización en canales sin buffer, además de diferencias como el pattern matching sobre el buzón en el modelo de actores.
      La discusión entre CSP y el modelo de actores es bastante interesante porque se parecen por fuera, pero sus implicaciones reales son muy distintas.
    • ¿Ver Erlang como CSP? No siempre; yo lo veía como su propio modelo, donde actores con identificadores se comunican directamente. CSP me parecía más cercano a procesos anónimos usando mensajería por canales.
      Creo que el modelo de actores tiene más sentido, aunque puede variar bastante según la persona.
    • Los managers se inventan su propia narrativa para acomodarse al ambiente.
  • Entré porque quería saber más sobre por qué Ericsson dejó de usar Erlang y por qué despidieron a Joe.
    En resumen, parece que Ericsson movió sus proyectos nuevos a Java, y por eso Erlang quedó marginado. Después, Joe y sus colegas fundaron Bluetail en 1998 y Nortel la adquirió.
    Nortel era un gigante de telecomunicaciones que llegó a representar cerca de un tercio del valor de la Bolsa de Toronto. En 2000 su acción alcanzó los 125 dólares, pero en 2002 cayó por debajo de 1 dólar. Fue parte del colapso puntocom y, combinado con una fuerte caída del gasto en telecomunicaciones, Nortel quedó especialmente golpeada.
    Parece más seguro entender el despido de Joe como una situación en la que “su división fue de las primeras en hundirse en un barco que se estaba yendo a pique”. Nortel despidió a 60 mil personas, es decir, a más de dos tercios de toda su plantilla. Ese despido no era una señal de que Joe no estuviera cumpliendo su función, ni mostraba ineficiencia en la unidad de negocio; fue una medida masiva tomada por pura desesperación.

    • En este contexto se siente muy raro ver la palabra “fired”. “laid off” sería más adecuado.
      “fired” carga mucho juicio de valor y da la idea de que fue despedido por causa o por haber hecho algo mal. Aunque hubiera sido así, el autor del texto original no tendría cómo saberlo, ni le correspondería saberlo.
  • Volví a interesarme en Erlang por los procesos ligeros y el paso de mensajes, y hasta ahora behaviour había sido algo secundario.
    El proyecto consiste en llevar la programación visual basada en flujos (FBP) a Erlang. FBP parece hecho para Erlang, así que me sorprendió no ver una implementación ya existente.
    La herramienta que usaba principalmente para FBP era Node-RED, así que la idea básica es conectar el frontend de Node-RED a un backend en Erlang y convertir todos los nodos en procesos. El frontend de Node-RED es bueno para modelar el paso de mensajes entre nodos, así que permite un mapeo 1:1 muy simple con los procesos y mensajes de Erlang.
    Ya implementé parte de la funcionalidad básica y empecé a convertir los flujos en pruebas unitarias para ir sumando funciones poco a poco. Sería ideal lograr compatibilidad al 100% con Node-RED, cuyo backend es Node.js. Hay más detalles en el repositorio de GitHub → https://github.com/gorenje/erlang-red
    En general, Erlang encaja sorprendentemente bien aquí, y me asombra que nadie haya hecho algo parecido. ¿O acaso ya existía?
    [1] = https://jpaulm.github.io/fbp/index.html

    • Siempre he pensado que un lenguaje de programación visual sobre BEAM sería algo muy interesante.
    • A alto nivel, me pregunto si sería válido ver FBP como algo parecido a procesos de Erlang con flujo de mensajes en una sola dirección.
  • Para mí, la fuerza de Erlang/Elixir está en la implementación del modelo de actores, el matching heredado de Prolog, la inmutabilidad y, más que en behaviour en sí, en la intención de Joe de mostrar que se puede hacer más con menos.
    Es un sistema de cómputo bien diseñado y probado, con una coherencia poco común tanto frente a otros lenguajes como incluso en el mundo de la “web”. No es perfecto, pero sí bastante impresionante.
    Lamentablemente, creo que en el mundo del software falta bastante reconocimiento y adopción de lo que la simplicidad hace posible. La complejidad permite que la gente se vuelva experta, que los gerentes justifiquen equipos grandes y muchas reuniones, y que los especialistas sigan siendo especialistas.
    Erlang se desarrolló en una época en la que las empresas intentaban implementar soluciones de software con menos personal y recursos limitados. Durante las décadas siguientes, entró tanto dinero en esta área que el valor de “menos significa más de una manera buena para todos” pasó a parecer menos atractivo.

    • Alan Kay dijo alguna vez que la simplicidad se obtiene eligiendo bloques de construcción un poco más complejos. Erlang parece hacer eso.
    • Me hizo pensar en la charla Simple VS Easy de Rich Hickey.
    • Este comentario me dio ganas de dedicarle más tiempo a Erlang. Lo he probado un poco y, al menos en la superficie, me gusta más la sintaxis de Erlang que la de Elixir.
  • El concepto más interesante en Erlang/BEAM es que la recuperación parcial viene integrada desde el principio.
    Cuando se encuentra un estado inesperado, en lugar de matar todo el proceso o seguir adelante arriesgando corrupción, vuelve a un estado conocido y válido en el nivel más fino posible.
    Esta idea se estudió hace mucho con el nombre de “microreboots” y también estaba relacionada con “crash-only software”, pero solo Erlang/BEAM la convirtió en un concepto de primera clase del sistema operativo.

    • Aun así, hay que tener cuidado con los árboles de supervisión y con reiniciar solo partes del árbol. Por ejemplo, si todo el proceso del sistema operativo de Erlang muere de repente y luego se reinicia, el sistema puede seguir funcionando bien; pero si solo se reinicia una parte del árbol de procesos de Erlang, puede empezar a corromper datos.
      Erlang ofrece un buen modelo para tratar este tipo de problemas, pero eso no significa que ya no haya que pensar en ello. Si uno entra con la idea de “solo deja que reinicie y todo estará bien”, puede salir mal.
    • Me pregunto en qué se diferencia esa explicación del manejo de excepciones.
  • Erlang, OTP y BEAM ofrecen más que behaviour. La VM se parece más a un kernel virtual, con supervisor, procesos aislados y un modo distribuido que trata varias máquinas físicas/virtuales como si fueran un solo pool de recursos.
    OTP también ofrece varios modos útiles, como la base de datos Mnesia, contadores atómicos para caché y tablas ETS. El runtime también soporta hot reloading de bytecode, lo que permite aplicar parches sin detener el sistema. La sintaxis no es muy amigable para lectores de pantalla, pero se deja leer.
    La única plataforma que se me ocurre como algo parecido a BEAM en su capacidad para tratar los recursos de varias máquinas como un solo pool es Apache Mesos[1].
    Hace poco más de un año, mi pequeña consultora personal adoptó Erlang como lenguaje backend. Desde entonces, empecé a explorar el interior de BEAM, por ejemplo cambiando el stack basado en TCP por QUIC o integrando parches en Rust.
    Es una opción realmente excelente para sistemas ligeros y de alto rendimiento que no fallan salvo por kernel panic o corte de energía. Ahora mismo estamos construyendo software muy activo y con mucha concurrencia, como rastreadores de producción de cine/videojuegos y administradores de pipelines, y también preparando I+D para servicios de gestión de hospitales privados.
    [1]: https://mesos.apache.org/

    • No tengo intención de adoptar por completo Elixir o Gleam. Eso se debe a que su ecosistema está construido alrededor del framework Phoenix y de servicios/bases de datos externas.
      Es muy probable que tengamos que mantener nosotros mismos bindings o implementaciones internas para las cosas del lado de Elixir que no reciben mantenimiento.
      Además, tiene mucho azúcar sintáctico y sus usuarios muestran una extraña obsesión por abstraer cualquier cosa dentro de interfaces tipo DSL.
  • La pregunta de “¿por qué los diseñadores de lenguajes y bibliotecas solo copian la idea de los procesos ligeros y el paso de mensajes, pero no roban la estructura detrás de los behaviour de Erlang?” tiene una razón.
    Las firmas de función de los behaviour de Erlang están fuertemente ligadas a otras características de Erlang, en especial a su forma peculiar de usar la inmutabilidad. Por eso el servidor necesita una llamada init separada, y la gestión del estado también requiere una forma muy definida para funcionar del mismo modo.
    Pero para lograr el mismo objetivo en otros lenguajes, casi siempre no conviene copiar exactamente lo que hace Erlang. Si alguien dice “porteé gen_server a otro lenguaje” y muestra una interfaz exactamente igual a la de Erlang, yo lo vería como un port que no entendió a fondo qué hace Erlang.
    Cuando llevé la idea del supervisor tree a Go[1], también lo hice a la manera de Go. En el Go moderno, la interfaz correcta para algo “supervisable” no tiene una firma como la de Erlang, sino solo esta:
    type Service interface {
    Serve(context.Context)
    }
    En Go eso es todo, y eso es todo lo que debería usarse. En otros lenguajes puede ser distinto. Como Go tiene channels, no hace falta handle_event/2, y hay que usar eso. No porque los channels sean mejores o peores, sino porque así funciona ese lenguaje.
    En otros lenguajes se pueden usar otras cosas, y en otra infraestructura en vez de “llamar a handle_event/2” podrías enviar a Kafka o a un bus de eventos en la nube. El punto es construir un sistema basado en eventos, no copiar la implementación exacta de Erlang.
    En la comunidad de Erlang hay un problema de exceso de confianza en que hay algo increíblemente especial en la forma exacta en que Erlang hace las cosas, y que si se hace distinto entonces está mal y no es confiable. Tal vez eso era cierto en 2005, pero no en 2025.
    Hubo un tiempo en que Erlang era casi la única respuesta razonable, pero el problema en 2025 es abrirse paso entre una avalancha de respuestas. Recomiendo con fuerza aprender de Erlang para hacer software confiable, pero me opongo firmemente a portar a ciegas la forma exacta en que Erlang logra sus resultados a otros lenguajes. En casi cualquier otro contexto de lenguaje, es la respuesta equivocada. Incluso otros lenguajes inmutables tienen una estructura lo bastante distinta como para que no se pueda copiar tal cual.
    [1]: https://jerf.org/iri/post/2930/

    • A continuación, la siguiente pregunta natural me parece que es: “¿por qué otros lenguajes y sistemas con este tipo de enfoque e ideas sí tuvieron éxito en adopción de mercado, pero Erlang/Elixir no?”.
      Como alguien que usa Elixir profesionalmente, esa es justamente la pregunta más interesante sobre Erlang para mí.
      Viendo el enorme éxito de los productos de sistemas distribuidos de k8s, Kafka y AWS, está claro que la demanda por herramientas que ayuden a diseñar sistemas concurrentes confiables es gigantesca. Entonces, ¿por qué Erlang/Elixir no logró quedarse con una parte de ese mercado?
      Lo discuto seguido con mis amigos, pero no sé la respuesta.
    • Go es mi favorito, pero me genera dudas la frase “como Go tiene channels, no hace falta handle_event/2”. ¿Qué tipo de channel? Más importante todavía: los channels son locales al proceso, así que para hacerlos funcionar en red necesitas código pegamento.
      Yo diría que Erlang sí abstrajo el manejo de mensajes de red. Además, he visto 3 o 4 variantes del patrón que propusiste para cosas como servidores de larga duración.
      Estoy completamente de acuerdo en que al portar hay que usar componentes idiomáticos. Pero los lenguajes pueden tener mecanismos ocultos que hacen perder una esencia valiosa durante el proceso de portado. Podría llamarse una especie de antirrelativismo de lenguajes de programación.
      “¿Channels? Nada más envuélvelos con X” también podría ser mucho más dañino para la interoperabilidad de lo que suena. Por ejemplo, http.Handler en Go es simple, pero el hecho práctico de que esté en la biblioteca estándar tiene grandes implicaciones. Eso permite un ecosistema de middleware mayormente compatible entre sí sin coordinación previa.
      io.Reader y compañía son parecidos. Podría decirse que estas interfaces extremadamente simples valen más que su implementación.
      Mi suposición es que, si Erlang acertó con muchas de las interfaces para sistemas distribuidos confiables, quizá justamente eso fue lo que hizo posible todo el conjunto.
  • No estoy de acuerdo. Las interfaces son un concepto trivial que puede agregarse a cualquier lenguaje. Incluso en lenguajes sin sintaxis formal de interfaces, se pueden imitar dentro del espacio del programa.
    BEAM tuvo éxito porque puede ejecutar un millón de procesos en un solo nodo, expresar fácilmente máquinas de estado distribuidas complejas y reiniciar partes del sistema sin downtime. Hay muchas otras razones también.
    De verdad no creo que el behaviour/interface sea la pieza más central.

    • Hasta cierto punto estoy de acuerdo. Cuando ves Erlang por primera vez, parece que para cada función que tiene Erlang hay algo parecido en otros sistemas.
      Para heaps de proceso aislados puedes usar procesos del SO, para árboles de supervisión puedes usar Kubernetes, el paso de mensajes puede hacerse en Java con dos hilos y una cola compartida, la carga de código en caliente también es posible en Java, y el procesamiento de baja latencia incluso podría superar a Erlang si ajustas LMAX Disruptor, cosas así.
      Pero la clave es reunir todo eso en una sola plataforma o biblioteca. Los procesos del SO son pesados, y no es fácil correr 2 millones en un servidor. Si usas green threads o promises, pierdes los heaps aislados.
      Kubernetes también sirve hasta cierto punto, pero no maneja bien árboles de supervisión anidados. Se puede hacer, pero además del código terminas con pods, controllers, volumes y todo tipo de cosas más.
      Puedes hacer paso de mensajes con bibliotecas de actores en varios lenguajes, pero no integran de manera transparente cosas como hacer pattern matching en receive o enviar a otro hilo en otro nodo.
      La carga de código en caliente también es posible, pero el problema es cómo manejar las estructuras de datos y el estado en runtime. Erlang fue construido alrededor de eso, y como en gen_server el estado es inmutable y explícito, tiene callbacks para actualizar no solo el código sino también el propio estado.
    • No lo he usado lo suficiente, pero me parece que BEAM evita en parte el problema redescubierto en “Where Did It All Go Wrong?” de Ian Cooper: que los microservicios no optimizan la fricción intra e intermodular del sistema.
      No diría que BEAM elimina ese problema, pero sí parece reducir la pendiente de la curva. Sus modismos y capacidades autoconsistentes, incluyendo despliegue, autorecuperación y balanceo de carga, reducen la fricción entre módulos.
      Te permite construir sistemas donde 12 ingenieros pueden manejar fácilmente 30 endpoints, y cuya superficie sigue pudiendo obedecer una ley de potencias.
  • No estoy de acuerdo con el contenido de este texto. Los behaviours son posibles gracias a la arquitectura subyacente del sistema
    Un behaviour no es una interfaz, sino algo más cercano a un objeto abstracto en un lenguaje como Java. Implementa funcionalidad básica y autocontenida detrás de una interfaz de colaboración, pero no puede hacer muchas cosas sin la infraestructura subyacente que garantiza que cada proceso esté completamente aislado de los demás, que todos los procesos puedan cerrarse de forma segura sin fugas de memoria ni de recursos, y que no se puedan compartir punteros peligrosos entre dos procesos
    Lo que Joe mostró en el artículo fue cómo construir sistemas confiables, e incluso sistemas distribuidos razonablemente confiables, con un conjunto dado de bloques de Lego
    Para implementar bien algo así hace falta la VM de Erlang, y no se puede implementar por completo en otras VM. Sin esa plomería de base, los árboles de supervisión tendrían fugas. En Java no puedes matar un hilo que está reteniendo recursos y esperar que todo siempre salga bien, y tampoco hay suficientes formas de monitorear procesos distintos entre sí