DEV Community

Matheus de Camargo Marques
Matheus de Camargo Marques

Posted on

A nuvem chegou às mesmas conclusões que Joe Armstrong e a equipe da Ericsson chegaram em 1986.

A nuvem chegou às mesmas conclusões que Joe Armstrong e a equipe da Ericsson chegaram em 1986.

Olá, me chamo Matheus de Camargo Marques. Ao aprofundar meus estudos em conceitos modernos de Cloud, deparei-me com o Scheduler Agent Supervisor pattern e tive uma sensação imediata de: "eu já vi isso antes".

Trazendo um contexto mais formal: nos últimos dez anos, a engenharia de software presenciou uma corrida frenética em direção aos microsserviços, arquiteturas orientadas a eventos e padrões sofisticados de resiliência como o Scheduler Agent Supervisor, Sagas, Circuit Breakers e Dead-Letter Queues. Fornecedores de nuvem como Microsoft Azure e AWS investiram bilhões promovendo ferramentas gerenciadas para orquestrar falhas de rede e consistência eventual.

No entanto, quando despimos essa infraestrutura da sua roupagem comercial, chegamos a uma constatação histórica fascinante: a nuvem moderna passou quatro décadas reinventando a roda para entregar, via rede, os mesmos princípios que Joe Armstrong, Robert Virding e Mike Williams implementaram dentro da máquina virtual BEAM (Erlang/OTP) em 1986. Este artigo demonstra como a convergência da arquitetura em nuvem valida as decisões históricas da Ericsson e por que entender a BEAM é o maior atalho para projetar sistemas verdadeiramente escaláveis e econômicos hoje.


1. A Grande Convergência Arquitetural

Se você observar o diagrama de arquitetura de uma aplicação moderna resiliente na nuvem (Azure ou AWS), encontrará uma topologia bastante padronizada:

  • Um orquestrador de estado (Azure Durable Functions ou AWS Step Functions);
  • Uma fila de mensagens para desacoplar a comunicação (Azure Service Bus ou AWS SQS);
  • Agentes de execução epêmeros (Azure Functions, AWS Lambda ou containers Docker);
  • Um banco de dados NoSQL durável para guardar checkpoints do fluxo (CosmosDB ou DynamoDB);
  • Um mecanismo de supervisão em background (Cron jobs ou Dead-Letter Queues) para detectar tarefas expiradas e disparar re-tentativas ou transações compensatórias.

Esse arranjo é formalmente chamado de padrão Scheduler Agent Supervisor.

Mas se você apresentar essa mesma arquitetura a um desenvolvedor Erlang ou Elixir veterano, a resposta dele será um sorriso irônico:

"Espere um pouco... você montou quatro serviços gerenciados na nuvem, com dezenas de chamadas de rede, serialização JSON e custos por requisição, apenas para reproduzir o que um GenServer, um Supervisor e uma tabela ETS fazem nativamente na memória da BEAM desde a década de 80?"

A nuvem não inventou uma nova ciência de sistemas distribuídos. Ela apenas re-descobriu na rede os axiomas que a Ericsson provou no nível do tempo de execução (runtime).


2. A História Se Repete: O Problema da Ericsson nos Anos 80

Para entender por que a BEAM acertou há 40 anos, precisamos olhar para o problema que Joe Armstrong e sua equipe precisavam resolver nos laboratórios da Ericsson em Estocolmo.

O Desafio dos Comutadores Telefônicos AXE

Nos anos 80, a Ericsson construía as centrais telefônicas da série AXE. Os requisitos não eram muito diferentes do que exigimos hoje das maiores plataformas digitais do mundo:

  1. Concorrência Massiva: Milhares de ligações telefônicas simultâneas, cada uma com seu próprio estado.
  2. Zero Downtime: O sistema não podia parar sob nenhuma hipótese.
  3. Hot Code Reloading: Atualizar o código do sistema com chamadas em andamento sem derrubar conexões.
  4. Resiliência Absoluta: O lendário limiar de 99,9999999% de disponibilidade (nine nines), correspondendo a menos de 1 segundo de inatividade em décadas.

O Modelo de Atores (Carl Hewitt, 1973) na Prática

Para atingir esse nível de tolerância a falhas, a equipe abandonou a programação imperativa tradicional e o modelo de memória compartilhada. Adotaram o Modelo de Atores teorizado por Carl Hewitt no MIT em 1973.

No Erlang:

  • Tudo é um processo (Ator): Não uma thread pesada do sistema operacional, mas um processo verde (green thread) ultraleve gerido pela máquina virtual.
  • Isolamento Absoluto de Memória: Cada processo possui sua própria pilha (stack) e seu próprio monte (heap). Não existem variáveis compartilhadas.
  • Passagem de Mensagens Assíncronas: A comunicação ocorre enviando mensagens para a caixa de correio (mailbox) do processo de destino via seu Identificador de Processo (PID).

Sem memória compartilhada, a BEAM eliminou de uma só vez as maiores pragas do desenvolvimento concorrente: trincos (locks), semáforos, condições de corrida (race conditions) e impasses circulares (deadlocks).

A Virada Filosófica: "Let it Crash"

Joe Armstrong, em sua tese de doutorado de 2003 ("Making reliable distributed systems in the presence of software errors"), formalizou a filosofia que se tornaria a assinatura do Erlang: "Let it Crash" (Deixe Quebrar).

Em linguagens tradicionais (Java, C#, Python), ensina-se a programação defensiva: encher o código de blocos try/catch para capturar exceções. O problema é que, ao capturar uma exceção imprevista, o processo continua rodando com dados corrompidos na memória.

Armstrong defendeu o oposto:

"Se um processo encontrar um estado anômalo, não tente adivinhar como se recuperar. Morra imediatamente. Deixe que um processo supervisor limpo cuide da recuperação."

Como os processos BEAM são totalmente isolados, o colapso de um ator não afeta a memória dos vizinhos. O Supervisor, imune ao erro por não executar regras de negócio, detecta a morte do processo e o reinicia a partir de um estado estável e garantido.


3. Anatomia da Reinvenção: O Mapeamento Peça por Peça

Ao compararmos o padrão nativo da nuvem com as primitivas da BEAM, a equivalência é ponto por ponto:

Padrão Cloud (Azure / AWS) Equivalente Nativo BEAM (Elixir / Erlang OTP)
Agendador (Durable Functions / Step Functions) gen_statem (Máquina de Estados) / GenServer
Agente (Lambda / Azure Function / Container) Processo Ator verde em RAM (~2,4 KB de pegada)
Fila de Mensagens (Service Bus / SQS) Mailbox interna lock-free do processo BEAM
Banco de Estado (CosmosDB / DynamoDB) Heap privado local, Tabelas ETS ou Mnesia
Supervisor (Cron Polling + DLQ) Árvore de Supervisão reativa (Links e Monitors)

O Detetor de Anomalias: Polling em Rede vs. Sinalização Reativa

Aqui reside uma das diferenças mais dramáticas entre a nuvem e a BEAM.

No padrão Cloud SAS, o Supervisor precisa rodar uma rotina agendada (CRON) que faz queries periódicas no CosmosDB em busca de registros onde o tempo limite (LockedUntil) expirou sem confirmação. Isso consome ciclos de leitura no banco de dados, gera tráfego de rede desnecessário e introduz atrasos na detecção da falha.

Na BEAM, a supervisão é puramente reativa e em tempo real. A máquina virtual conecta o processo filho ao Supervisor usando primitivas de baixo nível chamadas links e monitors. Se o filho colapsa, a própria VM emite um sinal de morte (:EXIT) direto para a caixa de correio do Supervisor em microssegundos. A recuperação é instantânea, sem nenhuma chamada de rede ou polling.


4. Por Baixo do Capô da BEAM: A Física da Memória e o Código C

Para entender como a BEAM dispensa a infraestrutura pesada da nuvem, precisamos olhar para as especificações internas do seu motor. Tudo se resume a como o tempo de execução em linguagem C (erl_process.h) gerencia a memória física de forma isolada.

4.1 O Process Control Block (PCB)

Cada ator Erlang é representado internamente pela estrutura C process (ponteiro c_p).

  • Pegada de Memória: A criação de um processo BEAM exige apenas 327 a 338 palavras (words) — aproximadamente 2,5 a 3 KB em sistemas de 64 bits. Em comparação, uma thread nativa de sistema operacional aloca entre 512 KB e 8 MB.
  • PID de 28 bits: O identificador aloja 15 bits para índice de tabela, 13 bits para número de série (evitando reuso de PIDs mortos) e a identificação do nó distribuído.
  • Registradores e JIT: Durante a execução, os apontadores de memória do processo são mantidos diretamente nos registradores físicos da CPU através do compilador JIT (introduzido no Erlang/OTP 24, que proporcionou um ganho de performance massivo e consolidou a BEAM para tarefas de alto processamento).

4.2 Crescimento Bidirecional: Stack vs. Heap Contíguos

A BEAM aloca um único bloco contíguo de memória privada para cada processo:

+-----------------------------------------------------------------------+
|  Endereço Baixo                                       Endereço Alto   |
|  [ HEAP (Cresce para CIMA ^) ]  ---> <--- [ STACK (Cresce para BAIXO v) ]|
|  |--- htop ------------------------ stop (E) ------------------------|
+-----------------------------------------------------------------------+
                                   ^
                           Ponto de Colisão
                        (Dispara o GC Isolado)
Enter fullscreen mode Exit fullscreen mode

O Heap cresce para cima (armazenando listas, tuplas e floats). O Stack cresce para baixo (armazenando o fluxo do programa e quadros de funções).

Quando o topo do heap (htop) se cruza com a base da pilha (stop), a máquina virtual detecta o ponto exato de exaustão e aciona o Garbage Collection per-processo.

4.3 Zero Stop-The-World: O GC de Cheney Per-Processo

Linguagens como Java e Go sofrem com o problema do Garbage Collector global. Quando o GC precisa limpar a memória, a aplicação inteira é paralisada (Stop-The-World pause).

Na BEAM, não existe Garbage Collector global. Cada processo possui seu próprio GC independente baseado no Algoritmo de Cópia de Cheney:

  • Os dados vivos são copiados do From-Space para o To-Space, alinhando estruturas para maximizar a localidade no cache da CPU.
  • Os sobreviventes passam pela marca d'água (high_water) e são promovidos ao Old Heap.
  • Se um processo é de curta duração (ex: atende uma requisição e morre), ele é destruído com toda a sua memória privada de uma só vez, sem sequer rodar o Garbage Collector.

4.4 Refc Binaries e a Lista MSO List

Para evitar a cópia pesada de payloads grandes (como JSONs de 10 MB) entre atores, a BEAM classifica os binários:

  • Binários curtos ($\le 64$ bytes) ficam no heap privado do processo (Heap Binary).
  • Binários longos ($> 64$ bytes) ficam no Binary Heap Global (Refc Binary), gerenciados por contagem de referências.

Quando dois processos trocam um binário grande, a BEAM copia apenas um descritor de 5 palavras chamado ProcBin e incrementa o contador global de referências. A lista MSO (Mark and Sweep Object List) ancorada na estrutura ErlOffHeap do PCB garante que, quando o processo morre, a referência no Binary Heap Global é decrementada de forma atômica.

4.5 Preempção Cooperativa: O Orçamento de 2000 Reduções

Em beam_emu.c, o loop principal do emulador (process_main) executa os processos atribuindo a cada um um orçamento fixo de 2000 Reduções (time slice budget).

Cada chamada de função, operação de I/O ou passo de GC decrementa o contador. Ao chegar a zero reduções, o processo sofre um Yield Point: salvaguarda seus registradores no PCB e cede o lugar voluntariamente na Run Queue.

Isso garante resposta Soft Real-Time: nenhum processo de usuário, por mais complexo que seja, consegue monopolizar a CPU ou travar os outros atores.


5. O Modelo Cognitivo Bio-Inspirado: Memória & Homeostase

A arquitetura da BEAM e os padrões de resiliência distribuída podem ser interpretados através dos princípios da Neurociência Cognitiva:

  • Memória de Curto Prazo (STM / Working Memory): Onde o foco atencional reside. Na BEAM, é o estado efêmero do GenServer na memória RAM.
  • Memória de Longo Prazo (LTM / Consolidada): Onde os fatos são preservados permanentemente. Na BEAM, são as tabelas in-memory ultra-rápidas ETS, o banco de dados distribuído Mnesia ou a camada de persistência relacional (Postgres/Ecto).
  • Homeostase e Reidratação: Quando o organismo sofre um choque (crash do ator), a Homeostase aciona o mecanismo de resgate. A Árvore de Supervisão reinicia o ator e faz o recall da LTM, reidratando a Memória de Trabalho em microssegundos sem deixar o sistema em estado corrompido.

6. A Matemática da Nuvem vs. A Matemática da BEAM

Quando colocamos as duas abordagens na ponta do lápis, a diferença de eficiência é assustadora:

1. Latência (Tempo)

  • Nuvem (Pulos de Rede): Cada transição de estado entre a Fila, o Agente, o Scheduler e o Banco de Dados exige uma travessia pela rede do datacenter. Mesmo com gRPC (cerca de 130 µs), cada salto leva entre 0,5 ms e 1 ms. Uma etapa completa consome de 2 ms a 50 ms.
  • BEAM (Memória RAM): A troca de mensagens entre atores ocorre por cópia de ponteiros na própria RAM do servidor. A latência é de 1 µs — 1000 vezes mais rápida que a nuvem.

2. Densidade Computacional (Memória)

  • Threads de SO (Java / C#): Consomem ~1 MB de RAM por thread. O servidor satura com poucas dezenas de milhares de conexões.
  • Atores BEAM: Consomem ~2,4 KB. Um único servidor básico comporta centenas de milhares a milhões de atores concorrentes.

3. Custo Financeiro (Dinheiro)

  • Nuvem Serverless: Cobrança por invocação de função + tempo de execução em ms + leituras/escritas na fila + unidades de requisição (RUs) no CosmosDB + tráfego Egress. Em alta escala, a conta é punitiva.
  • BEAM em VPS / Dedicado: Monólito distribuído em Elixir rodando em uma instância VPS de US$ 40/mês. O custo permanece rigorosamente os mesmos US$ 40/mês, quer o sistema processe 1 milhão ou 1 bilhão de mensagens.

7. Evidências Empíricas da Indústria

Três casos emblemáticos da história recente do software comprovam o impacto prático dessa arquitetura:

Plataforma Arquitetura Adotada Resultado Métrico
WhatsApp Erlang c/ Atores por Usuário + Mnesia + Kernel FreeBSD ajustado (kern.ipc.maxsockets = 2.4M) > 2 Milhões conexões ativas por servidor. Apenas 50 engenheiros para 2 bilhões de usuários.
Bleacher Report Migração de Ruby on Rails para Elixir / Phoenix Redução brutal de 150 instâncias AWS EC2 para apenas 5 servidores nos picos de tráfego.
Discord Elixir (WebSockets) + Rust NIFs (Rustler) substituindo Go no serviço Read States Latência despencou de 27.000 µs (pausas de GC do Go) para 4 µs com > 11M conexões.
  1. WhatsApp: Com um ator Erlang dedicado a cada conexão de usuário (pegada de 2 KB) e pequenos ajustes de sysctl no FreeBSD (kern.ipc.maxsockets = 2.400.000), a empresa manteve mais de 2 milhões de conexões TCP ativas por servidor bare-metal. Apenas 50 engenheiros mantinham a infraestrutura para 2 bilhões de usuários.
  2. Bleacher Report: O portal de esportes encolheu sua frota de servidores na AWS de 150 instâncias EC2 (Ruby on Rails) para apenas 5 servidores ao migrar para Elixir e Phoenix, eliminando dependências externas pesadas de cache e mensageria.
  3. Discord: Gerencia mais de 11 milhões de usuários concorrentes em WebSockets mantidos pelo Elixir. Ao substituir um microserviço em Go (que sofria pausas de GC de 27.000 µs a cada 2 minutos no serviço Read States) por uma NIF em Rust conectada ao Elixir (Rustler), a latência despencou para 4 µs.

8. Conclusão: O Futuro Pertence a Quem Entende a História

A nuvem moderna não inventou a tolerância a falhas nem a orquestração distribuída. Ela apenas abraçou as conclusões que Joe Armstrong e a equipe da Ericsson consolidaram em 1986.

A diferença crucial é que a nuvem vende essa resiliência como um amontoado de serviços pagos por requisição e conectados pela rede, enquanto a BEAM entrega o mesmo rigor dentro de um tempo de execução coeso, elegante e extremamente rápido.

Para os arquitetos e engenheiros de software modernos, a lição é clara:

  • Se você está projetando um sistema concorrente e de alta disponibilidade, não precisa remontar a BEAM com dezenas de microsserviços e filas.
  • Olhar para o passado do software e aprender com quem resolveu a concorrência na década de 80 é o caminho mais rápido, sustentável e lucrativo para construir o futuro da tecnologia.

Vamos Continuar a Conversa?

E você, já enfrentou desafios de latência acumulada ou custos astronômicos de infraestrutura ao orquestrar microsserviços na nuvem? Já experimentou o Modelo de Atores no Elixir ou Erlang?

Fique à vontade para deixar suas impressões, dúvidas ou relatar suas experiências nos comentários! Se quiser explorar projetos onde aplico esses conceitos na prática — desde agentes bio-inspirados e pipelines RAG até web crawlers de altíssima performance —, confira meus repositórios no GitHub ou conecte-se comigo no LinkedIn.

Top comments (0)