Cada vez com menos frequência (porque o Erlang está se tornando mais popular), perguntam-me por que o Membase escolheu usar Erlang para nosso componente de gerenciamento de cluster e supervisão de processos. As alternativas mais comuns sugeridas são Java, C++, Python, Rubie, mais recentemente, node.js e Clojure (que seria minha principal escolha se o Erlang estivesse fora dos meus limites).
Certamente há muitas desvantagens no uso do Erlang. Em primeiro lugar, é necessário criar, empacotar e dar suporte a outro ambiente de tempo de execução - dependemos do sistema Python no Linux e no OS X e usamos py2exe para criar executáveis para o Windows, mas na verdade criamos e empacotamos o Erlang. Com o Java, poderíamos depender do fato de o sistema já ter o Java instalado, o que é um pouco mais difícil no Linux, mas mais fácil no Windows. Além disso, eventualmente precisaremos de um tempo de execução Java para poder executar módulos Java NodeCode.
Além disso, nem tantos programadores conhecem Erlang quanto Java ou C++ ou, de fato, qualquer uma das alternativas possíveis que mencionei. Sua sintaxe e sua semântica são incomuns, considerando sua Prolog A natureza funcional, concorrente e pragmática de Erlang e o fato de ser uma linguagem pragmática que vem sendo usada e evoluindo desde 1986. Isso não chegou a ser um problema para a equipe de engenharia da Membase - quatro de nós podem trabalhar nos componentes Erlang, e o código Erlang é pequeno o suficiente para que possa ser facilmente mantido por qualquer um de nós. No entanto, isso definitivamente eleva o nível de exigência para que nossos próprios desenvolvedores e colaboradores da comunidade possam contribuir com o subsistema de gerenciamento de cluster se ainda não conhecerem Erlang.
Vale muito a pena. O Erlang (na verdade, Erlang/OTP, que é o que a maioria das pessoas quer dizer quando falam "Erlang tem X") faz muitas coisas que teríamos que construir do zero ou tentar juntar as bibliotecas existentes para fazer. Suas sistema de tipos dinâmicos e correspondência de padrões (ala Haskell e ML) fazem com que o código Erlang tenda a ser ainda mais conciso do que Python e Ruby, duas linguagens conhecidas por sua capacidade de fazer muito em poucas linhas de código.
Para nós, a maior vantagem de usar Erlang deve ser seu suporte integrado para concorrência. Erlang modela tarefas simultâneas como "processos" que podem se comunicar entre si apenas por meio de passagem de mensagens (que faz uso de correspondência de padrões!), no que é conhecido como modelo de ator de concorrência. Isso, por si só, torna completamente impossível toda uma classe de bugs relacionados à simultaneidade. Embora isso não impede completamente o impasseSe você escrever o código dessa forma, será muito difícil não perceber um cenário de deadlock em potencial. Embora seja certamente possível para implementar o modelo de ator na maioria, se não em todos, os ambientes alternativos que mencionei e, de fato, essas implementações existem, mas são incompletas ou sofrem de uma incompatibilidade de impedância com as bibliotecas existentes que esperam que você use threads.
Os processos Erlang, além de serem isolados uns dos outros, são muito leves. É possível executar facilmente um quarto de milhão deles em uma única VM Erlang. O custo de gerá-los também é bastante baixo, o que torna desnecessários hacks bobos para reutilizá-los. Como eles são executados no mesmo espaço de memória e são isolados apenas por software, a passagem de mensagens entre eles é extremamente rápida e, embora envolva a cópia dos dados na mensagem na maioria das vezes, as mensagens tendem a ser pequenas e isso permite que os processos Erlang sejam coletados independentemente.
O OTP organiza os processos em uma "árvore de supervisão", na qual os processos supervisores monitoram os processos filhos, que também podem ser supervisores, e os reiniciam e, potencialmente, qualquer processo dependente, caso eles falhem. Juntamente com o isolamento de processos, isso torna os aplicativos Erlang extremamente tolerantes a falhas: qualquer processo Erlang dentro do nosso subsistema de gerenciamento de cluster (que não seja o supervisor raiz) pode falhar sem derrubar o Membase. Na verdade, usamos isso muito bem: quando acontecem coisas inesperadas em qualquer um dos nossos processos, geralmente deixamos que eles simplesmente travem e reiniciem, trazendo-os de volta a um estado bom conhecido. Por exemplo, o processo que gerencia a conexão administrativa com o processo local do memcached simplesmente será interrompido se a conexão atingir o tempo limite ou retornar qualquer erro que não seja esperado. Isso faz com que o tratamento de erros seja exatamente igual ao da inicialização, o que deixa menos lugares para os bugs se esconderem e facilita os testes.
Para facilitar a estruturação de um aplicativo complexo como um conjunto de processos que interagem, o Erlang/OTP fornece um conjunto padrão de "comportamentos" que os módulos podem implementar. O mais comum deles é gen_servidorO comportamento genérico do servidor, que implementa um servidor básico com retornos de chamada para solicitações. Gen_fsm implementa uma máquina de estado finito, e gen_event serve para implementar manipuladores de eventos e gerenciadores de eventos.
As mensagens em Erlang podem ser enviadas tão facilmente para um processo que reside em outro nó quanto para processos no mesmo nó; isso é totalmente transparente para o programador. O Erlang/OTP tem vários módulos que ajudam no processamento distribuído. Por exemplo, o módulo "global" fornece um registro comum de nomes e bloqueios globais, semelhante ao gerenciador de bloqueios Chubby do Google. Os "aplicativos distribuídos" do OTP fornecem uma maneira de garantir que um determinado conjunto de processos seja iniciado somente em um nó. O Mnesia é um DBMS distribuído com fortes garantias de consistência que usamos para armazenar estatísticas.
O Erlang oferece excelente suporte para depuração e aplicação de patches em sistemas ativos. Afinal de contas, ele foi inventado para criar sistemas com cinco noves de tempo de atividade. Se você conhece o cookie criptográfico que os nós usam para se autenticarem uns aos outros, é trivial anexar outro nó Erlang ao cluster e executar comandos arbitrários em qualquer outro nó, inclusive "recarregar este módulo". Esse tipo de recurso é inestimável quando você precisa corrigir ou contornar um bug e não pode derrubar um cluster ou mesmo um único nó para substituir o código. Também é inestimável para testes ou para qualquer situação em que nós (ou um cliente) desejemos fazer alterações no estado do cluster que ainda não tenham sido implementadas na API REST.
No final das contas, a verdadeira questão não é se teria sido possível implementar o gerenciamento de clusters em outra linguagem; é realmente uma questão de esforço e capacidade de manutenção do resultado. Em qualquer outro ambiente, teríamos que reimplementar pelo menos parte do que o Erlang/OTP oferece, embora não tenhamos nos deparado com a reimplementação de recursos oferecidos por nenhum outro ambiente.