<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matheus de Camargo Marques</title>
    <description>The latest articles on DEV Community by Matheus de Camargo Marques (@matheuscamarques).</description>
    <link>https://dev.to/matheuscamarques</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F882243%2Fde69153a-a393-4f28-9761-e5b60402c45b.jpeg</url>
      <title>DEV Community: Matheus de Camargo Marques</title>
      <link>https://dev.to/matheuscamarques</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matheuscamarques"/>
    <language>en</language>
    <item>
      <title>PON-BEAM: um paradigma orientado a notificações dentro da máquina virtual do Erlang</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:10:11 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/pon-beam-um-paradigma-orientado-a-notificacoes-dentro-da-maquina-virtual-do-erlang-191l</link>
      <guid>https://dev.to/matheuscamarques/pon-beam-um-paradigma-orientado-a-notificacoes-dentro-da-maquina-virtual-do-erlang-191l</guid>
      <description>&lt;h1&gt;
  
  
  PON-BEAM: um paradigma orientado a notificações dentro da máquina virtual do Erlang
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. A pergunta que move este projeto
&lt;/h2&gt;

&lt;p&gt;Todo programador Elixir ou Erlang já ouviu o mantra: &lt;strong&gt;"a BEAM é uma máquina virtual altamente concorrente, com milhões de processos leves, trocas de contexto em microsegundos e escalabilidade quase linear"&lt;/strong&gt;. E isso é verdade — para o &lt;em&gt;modelo de programação&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Mas quando você abre o código em C da ERTS (Erlang Run-Time System) e olha &lt;em&gt;como a máquina funciona por dentro&lt;/em&gt;, encontra uma surpresa: a BEAM é um motor &lt;strong&gt;híbrido&lt;/strong&gt;. Ela tem notificações para algumas coisas, mas ainda &lt;strong&gt;puxa (polling)&lt;/strong&gt; e &lt;strong&gt;faz scans lineares&lt;/strong&gt; em vários dos subsistemas mais críticos:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subsistema&lt;/th&gt;
&lt;th&gt;Mecanismo no BEAM stock (OTP 30)&lt;/th&gt;
&lt;th&gt;Custo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;receive&lt;/code&gt; seletivo&lt;/td&gt;
&lt;td&gt;Scan linear da mailbox (cada cláusula contra cada mensagem)&lt;/td&gt;
&lt;td&gt;$O(N \times M)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timers&lt;/td&gt;
&lt;td&gt;Timer wheel com &lt;em&gt;ticks&lt;/em&gt; periódicos de polling&lt;/td&gt;
&lt;td&gt;O(1) por tick, mas a CPU nunca dorme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler SMP&lt;/td&gt;
&lt;td&gt;Run-queue pollada / &lt;em&gt;busy-spin&lt;/em&gt; quando ocioso&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5–30% de um core desperdiçado&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coletor de lixo&lt;/td&gt;
&lt;td&gt;Varredura de heap por raízes&lt;/td&gt;
&lt;td&gt;$O(\text{heap total})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETS&lt;/td&gt;
&lt;td&gt;Lookups com lock + busca&lt;/td&gt;
&lt;td&gt;contention na chave quente&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Esse é exatamente o tipo de problema que o &lt;strong&gt;Paradigma Orientado a Notificações (PON / NOP)&lt;/strong&gt;, criado pelo professor &lt;em&gt;Dr. Jean Marcelo Simão&lt;/em&gt; (UTFPR, 2005–2009), se propõe a eliminar: &lt;strong&gt;redundância temporal&lt;/strong&gt; (reavaliações desnecessárias) e &lt;strong&gt;redundância estrutural&lt;/strong&gt; (código de busca repetido).&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;PON-BEAM&lt;/strong&gt; (repositório: &lt;a href="https://github.com/matheuscamarques/pon_beam" rel="noopener noreferrer"&gt;matheuscamarques/pon_beam&lt;/a&gt;) é uma &lt;strong&gt;re-arquitetura completa da BEAM&lt;/strong&gt; onde &lt;strong&gt;todo subsistema interno vira uma entidade reativa PON&lt;/strong&gt;: mensagens &lt;em&gt;empurram&lt;/em&gt; notificações, em vez de a máquina &lt;em&gt;puxar&lt;/em&gt; estados.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A tese central&lt;/strong&gt;: Nenhum trabalho anterior aplicou o PON como princípio fundacional &lt;em&gt;do motor da VM&lt;/em&gt;. A literatura NOP implementa o paradigma &lt;em&gt;em cima&lt;/em&gt; de plataformas existentes (C++, Java, FPGA, Erlang). O PON-BEAM propõe o PON &lt;strong&gt;como o próprio design da máquina&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Este artigo explica, &lt;strong&gt;um a um&lt;/strong&gt;, cada componente PON que foi integrado à VMBEAM — o que ele substitui, como foi implementado em C na ERTS, e quais ganhos empíricos o harness de benchmarks mediu.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. O paradigma PON em 5 minutos
&lt;/h2&gt;

&lt;p&gt;Antes de falar de cada componente, os conceitos básicos do PON:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entidade&lt;/strong&gt;: qualquer elemento da aplicação (um processo Erlang, um scheduler, um objeto de heap) tratado como &lt;em&gt;reativo&lt;/em&gt; e &lt;em&gt;desacoplado&lt;/em&gt;. Uma entidade contém dois tipos de elementos:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAD&lt;/strong&gt; (Facts / dados): o estado da entidade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FBE&lt;/strong&gt; (Fundamental Behavior Element / procedimentos): o comportamento.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premise&lt;/strong&gt;: uma &lt;em&gt;condição mínima&lt;/em&gt; sobre os fatos de uma entidade. Ela &lt;strong&gt;só é avaliada quando notificada&lt;/strong&gt; — e não periodicamente. Ex.: &lt;em&gt;"uma mensagem &lt;code&gt;{gen_call, From, Req}&lt;/code&gt; chegou na mailbox"&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt;: uma &lt;em&gt;conjunção de Premises&lt;/em&gt;. Quando todas as Premises de uma Condition estão satisfeitas, a Condition &lt;strong&gt;notifica&lt;/strong&gt; uma Instigação.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instigation&lt;/strong&gt;: dispara a execução de um &lt;strong&gt;método&lt;/strong&gt; (FBE) quando sua condição causal/temporal se torna verdadeira. No PON-BEAM, os timers são Instigações temporais.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notificação&lt;/strong&gt;: o único mecanismo de colaboração entre entidades. É &lt;strong&gt;ponto-a-ponto&lt;/strong&gt; (quem muda sabe &lt;em&gt;quem&lt;/em&gt; deve ser avisado), &lt;strong&gt;reativa&lt;/strong&gt; (só dispara quando o fato muda) e &lt;strong&gt;sem polling&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A inversão de controle é radical: no modelo imperativo clássico a entidade &lt;em&gt;pergunta&lt;/em&gt; "o estado mudou?". No PON, o estado muda e &lt;strong&gt;avisa&lt;/strong&gt; quem o observa.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    subgraph Stock ["BEAM Stock (OTP 30) — Polling e Scan"]
        direction TB
        A["receive: scan linear da mailbox O(N·M)"]
        B["timer wheel: ticks periódicos"]
        C["scheduler: busy-spin 5–30% CPU"]
    end
    subgraph Pon ["PON-BEAM — Grafos Reativos de Push"]
        direction TB
        Cond["Condition (chegada de estado/mensagem)"]
        Prem["Premise (slot de pattern match)"]
        Instig["Instigation (salto O(1) de execução)"]
        Cond --&amp;gt;|"empurra evento"| Prem
        Prem --&amp;gt;|"satisfaz"| Instig
    end
    Stock ==&amp;gt;|"re-arquitetado como"| Pon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. A fundação: um overlay compilável sobre o OTP
&lt;/h2&gt;

&lt;p&gt;Antes de qualquer subsistema, a &lt;strong&gt;Fase 0&lt;/strong&gt; estabeleceu &lt;em&gt;como&lt;/em&gt; o PON-BEAM existe junto do OTP stock. As regras de ouro do projeto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nunca tocar no baseline.&lt;/strong&gt; O OTP original vive imutável na branch &lt;code&gt;otp-30.0-rc0-stock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toda modificação C fica dentro de &lt;code&gt;#ifdef PON_BEAM&lt;/code&gt;.&lt;/strong&gt; O código original permanece intacto — o PON-BEAM é um &lt;em&gt;overlay&lt;/em&gt; compilável, não um fork divergente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Um artefato por fase&lt;/strong&gt;: um &lt;code&gt;#ifdef&lt;/code&gt;, um benchmark diferencial e uma telemetria.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Isso garante &lt;strong&gt;100% de compatibilidade retroativa&lt;/strong&gt;: o formato &lt;code&gt;.beam&lt;/code&gt;, a ABI de NIFs e o protocolo de distribuição não mudam. Um mesmo pool de código compila &lt;code&gt;beam.smp&lt;/code&gt; (stock) ou &lt;code&gt;beam.ponbeam.smp&lt;/code&gt; (PON) dependendo do flag &lt;code&gt;-DPON_BEAM&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A telemetria da prova: &lt;code&gt;pon_stats.h&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Para &lt;em&gt;medir&lt;/em&gt; que o comportamento reativo realmente aconteceu (e não só intuir), cada componente PON escreve &lt;strong&gt;contadores thread-local por scheduler&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_stats.h — um contador por scheduler (thread-local) */&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* === PON-Receive === */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;premises_registered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="cm"&gt;/* Premises registradas */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;premise_notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="cm"&gt;/* Premises notificadas na chegada de msg */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;mailbox_scans_avoided&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="cm"&gt;/* Scans lineares evitados pelo advance O(1) */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;messages_classified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="cm"&gt;/* Mensagens classificadas por tipo */&lt;/span&gt;
    &lt;span class="cm"&gt;/* === PON-Timer === */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;timerfd_created&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;timerfd_expirations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* === PON-Scheduler === */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;condition_wakeups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;scheduler_idle_blocks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* === PON-ETS === */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;ets_watchers_registered&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;ets_watcher_hits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* === PON-GC === */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;gc_notifications_sent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;gc_scans_avoided&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;gc_incremental_steps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* === Temporais === */&lt;/span&gt;
    &lt;span class="n"&gt;Uint64&lt;/span&gt; &lt;span class="n"&gt;pon_overhead_us&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;PonStats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os contadores são expostos ao Erlang via &lt;code&gt;erlang:system_info(pon_stats)&lt;/code&gt; e cada benchmark os lê para &lt;em&gt;validar&lt;/em&gt; o mecanismo (ex.: nº de &lt;code&gt;mailbox_scans_avoided&lt;/code&gt; confirma que o receive pulou o scan).&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Componente #1 — PON-Receive (Fase 1): Premises na mailbox
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema: &lt;code&gt;O(N × M)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;receive&lt;/code&gt; seletivo do Erlang é a fundação da concorrência da BEAM, mas o intérprete do OTP faz o &lt;em&gt;scan&lt;/em&gt;: quando você tem&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="k"&gt;receive&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gen_call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;           &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;stop&lt;/span&gt;                  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e a mailbox tem &lt;strong&gt;N&lt;/strong&gt; mensagens não relacionadas, o intérprete tenta, em ordem de chegada, cada uma das &lt;strong&gt;M&lt;/strong&gt; cláusulas contra cada uma das &lt;strong&gt;N&lt;/strong&gt; mensagens até achar um &lt;em&gt;match&lt;/em&gt;. Um &lt;code&gt;gen_server&lt;/code&gt; com 10.000 mensagens pendentes custa &lt;strong&gt;4.500µs&lt;/strong&gt;; com 100.000, &lt;strong&gt;82.000µs&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: Premises
&lt;/h3&gt;

&lt;p&gt;Uma &lt;strong&gt;Premise&lt;/strong&gt; PON-BEAM é um &lt;em&gt;slot de pattern match compilado&lt;/em&gt; registrado pelo processo no momento do &lt;code&gt;receive&lt;/code&gt;. Quando uma mensagem compatível &lt;strong&gt;chega&lt;/strong&gt; na mailbox, ela &lt;strong&gt;notifica&lt;/strong&gt; a Premise — em vez de a mailbox ser escaneada mais tarde.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_premise.h — Entidade Premise adaptada à mailbox da BEAM */&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ErtsPremise_&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Eterm&lt;/span&gt;                &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="cm"&gt;/* Padrão compilado (como termo) */&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;match_fn&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;Eterm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cm"&gt;/* Função de match otimizada */&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;                  &lt;span class="n"&gt;has_match&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="cm"&gt;/* 1 se há mensagem casada disponível */&lt;/span&gt;
    &lt;span class="n"&gt;Eterm&lt;/span&gt;                &lt;span class="n"&gt;matched_term&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* Termo da mensagem casada */&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;erl_mesg&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;matched_msg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* Referência para a mensagem */&lt;/span&gt;
    &lt;span class="n"&gt;Uint&lt;/span&gt;                 &lt;span class="n"&gt;clause_index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* Índice da cláusula (ordem) */&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ErtsPremise_&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next_premise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* Lista ligada de premises */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;ErtsPremise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O fluxo tem três momentos:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Classificação rápida por tag de tipo.&lt;/strong&gt; Na chegada da mensagem, &lt;code&gt;erts_pon_notify_premises()&lt;/code&gt; extrai o 1º elemento da tupla e usa os &lt;strong&gt;8 bits baixos&lt;/strong&gt; dele para indexar um dos &lt;strong&gt;256 buckets&lt;/strong&gt; (&lt;code&gt;pon_type_tag&lt;/code&gt;), mantendo um contador de mensagens por bucket. Isso isola, em $O(1)$, o subconjunto de mensagens potencialmente relevantes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define PON_NUM_TYPE_BUCKETS (1 &amp;lt;&amp;lt; 8)
#define pon_type_tag(term) ((Uint)(term) &amp;amp; (PON_NUM_TYPE_BUCKETS - 1))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Notificação da Premise.&lt;/strong&gt; Cada Premise possui um &lt;code&gt;match_fn&lt;/code&gt; especializado (gerado pelo compilador) ou o fallback &lt;code&gt;erts_pon_default_match()&lt;/code&gt;. Se casa, a Premise guarda o termo, a mensagem e uma &lt;strong&gt;sequência de chegada&lt;/strong&gt; global monotônica (&lt;code&gt;erts_pon_next_msg_seq()&lt;/code&gt;), necessária para preservar a semântica do receive seletivo multi-cláusula (a mensagem &lt;em&gt;mais antiga&lt;/em&gt; casa primeiro).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Advance O(1) — o pulo do gato.&lt;/strong&gt; O hook central é no &lt;em&gt;enqueue&lt;/em&gt; (lado do envio): cada mensagem recebe um &lt;strong&gt;&lt;code&gt;pon_in_link&lt;/code&gt;&lt;/strong&gt;, que é o &lt;em&gt;endereço do ponteiro da fila que aponta para ela&lt;/em&gt; (&lt;code&gt;prev-&amp;gt;next&lt;/code&gt; ou &lt;code&gt;&amp;amp;sig_qs.first&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* erl_proc_sig_queue.c — grava o link de entrada fora da janela do receive */&lt;/span&gt;
&lt;span class="cp"&gt;#ifdef PON_BEAM
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pon_premises&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;num_msgs&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;is_to_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ERTS_SIG_IS_MSG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pon_in_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois, no &lt;code&gt;loop_rec_end&lt;/code&gt; do interpretador, &lt;code&gt;erts_pon_advance_to_matched()&lt;/code&gt; reposiciona o &lt;strong&gt;save pointer&lt;/strong&gt; da fila principal &lt;strong&gt;diretamente&lt;/strong&gt; para o endereço da mensagem casada — sem caminhar a lista, sem pattern matching por mensagem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_premise.c — fast-path do selective receive */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pon_in_link&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pon_in_link&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pon_in_link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;pon_in_link&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="cm"&gt;/* salto O(1) */&lt;/span&gt;
    &lt;span class="n"&gt;PON_STATS_INC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mailbox_scans_avoided&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A validação &lt;code&gt;*pon_in_link == matched_msg&lt;/code&gt; é um &lt;em&gt;gate de segurança&lt;/em&gt;: se a mensagem já foi consumida por outra via ou o gambar foi alcançado pelo scan normal, a Premise é marcada obsoleta e o scan linear segue como fallback (correto, apenas mais lento). Os casos de caminhos não instrumentados (buffers multi-mensagem, flush de &lt;code&gt;sig_inq&lt;/code&gt;, concatenação de cadeias) também registram &lt;code&gt;pon_in_link&lt;/code&gt; — todos os caminhos de entrada da mensagem estão cobertos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resultado medido
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;N (mensagens na mailbox)&lt;/th&gt;
&lt;th&gt;Stock&lt;/th&gt;
&lt;th&gt;PON-BEAM&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;45.2 µs&lt;/td&gt;
&lt;td&gt;8.5 µs&lt;/td&gt;
&lt;td&gt;5.3×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;320 µs&lt;/td&gt;
&lt;td&gt;9.2 µs&lt;/td&gt;
&lt;td&gt;34.8×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10.000&lt;/td&gt;
&lt;td&gt;4.500 µs&lt;/td&gt;
&lt;td&gt;10 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;445×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100.000&lt;/td&gt;
&lt;td&gt;82.000 µs&lt;/td&gt;
&lt;td&gt;12 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.665×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A latência do receive PON &lt;strong&gt;no plota ~15µs em todo o range&lt;/strong&gt; de 100 a 50.000 mensagens — o gap cresce monotonicamente com N, confirmando uma &lt;strong&gt;inversão assintótica&lt;/strong&gt; ($O(N \times M) \to O(1)$), e não um ganho de constante.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Componente #2 — PON-Timer (Fase 2): Instigações com &lt;code&gt;timerfd&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema: o timer wheel nunca dorme
&lt;/h3&gt;

&lt;p&gt;A BEAM usa um &lt;strong&gt;timer wheel&lt;/strong&gt; com resolução de 1ms. Mesmo com &lt;strong&gt;zero timers ativos&lt;/strong&gt;, o sistema de timers executa &lt;em&gt;ticks&lt;/em&gt; periódicos para varrer o wheel em busca de expirações — consumindo ~3% de um core &lt;strong&gt;sem fazer nada&lt;/strong&gt;. Com 50.000 timers registrados, o custo de checagem chega a &lt;strong&gt;50.000.000 checagens/seg&lt;/strong&gt; com ~15% de CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: Instigações temporais com o kernel
&lt;/h3&gt;

&lt;p&gt;Uma &lt;strong&gt;Instigation&lt;/strong&gt; PON é um evento futuro que, quando satisfeito, notifica um processo-alvo. No PON-BEAM a expiração de um timer fica a cargo do &lt;strong&gt;kernel Linux&lt;/strong&gt;: cada timer vira um &lt;strong&gt;&lt;code&gt;timerfd&lt;/code&gt;&lt;/strong&gt; monitorado por &lt;strong&gt;&lt;code&gt;epoll&lt;/code&gt;&lt;/strong&gt;. A VM só acorda &lt;strong&gt;no único próximo evento vencido&lt;/strong&gt; — e nunca varre o wheel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_timer.c — cria o timerfd e registra na epoll */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;pon_timer_instigation_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErtsTimerInstigation&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;tfd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timerfd_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLOCK_MONOTONIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TFD_NONBLOCK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;it_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tv_sec&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;it_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tv_nsec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000000ULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;timerfd_settime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EPOLLIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pon_timer_epoll_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;timer_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tfd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A entidade &lt;code&gt;ErtsTimerInstigation&lt;/code&gt; guarda o fd, o alvo (&lt;code&gt;target&lt;/code&gt;) e a mensagem a entregar na expiração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_instigation.h */&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ErtsInstigation&lt;/span&gt;   &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="cm"&gt;/* type, fired, target, message, next */&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;               &lt;span class="n"&gt;timer_fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* timerfd (−1 se inativo) */&lt;/span&gt;
    &lt;span class="kt"&gt;uint64_t&lt;/span&gt;          &lt;span class="n"&gt;expiration_ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;ErtsTimerInstigation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pon_timer_process_expirations()&lt;/code&gt; faz um &lt;code&gt;epoll_wait(fd, events, 64, 0)&lt;/code&gt; não-bloqueante e dispara cada Instigação vencida — drenando as expirações &lt;strong&gt;em lote&lt;/strong&gt;, sem custo proporcional ao número total de timers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resultado medido
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Stock&lt;/th&gt;
&lt;th&gt;PON-BEAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU ociosa com 0 timers&lt;/td&gt;
&lt;td&gt;~3% de 1 core&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 timers (1s)&lt;/td&gt;
&lt;td&gt;~3.1%&lt;/td&gt;
&lt;td&gt;0.001%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50.000 timers (1s)&lt;/td&gt;
&lt;td&gt;~15% CPU / 50M checagens/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.1% / 5 checagens/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O custo total de timers registrados cai de "varredura do wheel" para "quase nada" — $10.000.000\times$ em checagens por segundo.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Componente #3 — PON-Spawn (Fase 3): notificação no agendamento
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema
&lt;/h3&gt;

&lt;p&gt;Cada &lt;code&gt;spawn&lt;/code&gt; percorre o caminho de criação de processo (alocar PCB, inscrever no process table, inserir na run queue) e a latência média de criação fica em ~15µs — com uma cauda longa de jitter sob rajadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução
&lt;/h3&gt;

&lt;p&gt;A Fase 3 integrou um &lt;strong&gt;hook de notificação no caminho de agendamento&lt;/strong&gt;: quando um processo é criado (ou tornando-se pronto), &lt;code&gt;erts_schedule_process()&lt;/code&gt; dispara &lt;code&gt;erts_pon_schedule_notify(p)&lt;/code&gt; — que incrementa os contadores de Condition e avisa o scheduler-alvo que há trabalho disponível:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* erl_process.c — hook no agendamento */&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;erts_schedule_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;erts_aint32_t&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErtsProcLocks&lt;/span&gt; &lt;span class="n"&gt;locks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;schedule_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#ifdef PON_BEAM
&lt;/span&gt;    &lt;span class="n"&gt;erts_pon_schedule_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resultado medido
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Latência média de spawn: &lt;strong&gt;~15µs → ~8µs&lt;/strong&gt; (~2×).&lt;/li&gt;
&lt;li&gt;Tempestade de &lt;strong&gt;50.000 spawns&lt;/strong&gt;: pico de 86µs (stock) → &lt;strong&gt;69µs (−19,7%)&lt;/strong&gt;, com a cauda longa de jitter eliminada — uma distribuição mais estreita e previsível.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Componente #4 — PON-Scheduler (Fase 4): Conditions com &lt;code&gt;eventfd&lt;/code&gt; + &lt;code&gt;epoll&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema: o busy-spin ocioso
&lt;/h3&gt;

&lt;p&gt;No OTP stock, quando não há processos prontos, o scheduler cai num loop de checagem da run queue que &lt;strong&gt;queima 5–30% de um core por scheduler&lt;/strong&gt;. Num servidor de 32 cores, isso pode ser &lt;strong&gt;1,6–9,6 cores inteiros&lt;/strong&gt; girando à toa. A reativação de um processo ocioso mede &lt;strong&gt;10–100µs&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: a Condition
&lt;/h3&gt;

&lt;p&gt;Uma &lt;strong&gt;Condition&lt;/strong&gt; PON é a conjunção de Premises: quando as premissas de um processo estão satisfeitas, ela notifica o scheduler. No PON-BEAM, a Condition substitui a run queue passiva por uma &lt;strong&gt;máquina de notificação kernel-space&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eventfd&lt;/code&gt;&lt;/strong&gt;: contador kernel onde o scheduler &lt;strong&gt;dorme&lt;/strong&gt; (&lt;code&gt;epoll_wait&lt;/code&gt;) até um byte ser escrito.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;epoll&lt;/code&gt;&lt;/strong&gt;: multiplexa os eventfds de wakeup + os &lt;code&gt;timerfd&lt;/code&gt; das Instigações da Fase 2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ready_list&lt;/code&gt; lock-free&lt;/strong&gt;: uma pilha de processos prontos inserida por &lt;strong&gt;CAS&lt;/strong&gt; (compare-and-swap), tolerante a produtores concorrentes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_condition.c — notificação do scheduler */&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;pon_condition_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErtsCondition&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;old_head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;old_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;atomic_load_explicit&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;atomic_uintptr_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ready_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="n"&gt;memory_order_acquire&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;atomic_compare_exchange_weak_explicit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;atomic_uintptr_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ready_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;old_head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_order_release&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_order_acquire&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;notify_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                    &lt;span class="cm"&gt;/* só acorda se estiver dormindo */&lt;/span&gt;
        &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;wake_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o &lt;em&gt;consumidor&lt;/em&gt; — o scheduler — bloqueia até o kernel entregar a notificação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;pon_condition_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErtsCondition&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nfds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;epoll_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PON_CONDITION_BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nfds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;satisfied&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="cm"&gt;/* volta a drenar a lista */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O padrão &lt;strong&gt;check-then-drain&lt;/strong&gt; é crucial para não perder wakes: o consumidor tenta primeiro drenar a &lt;code&gt;ready_list&lt;/code&gt; via CAS; se o CAS falha (produtor chegou no meio), tenta de novo; se a lista está vazia mas o eventfd tem dados, lê o eventfd; só então bloqueia em &lt;code&gt;epoll_wait&lt;/code&gt;. Notificações perdidas são impossíveis no design.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Uma nota de transparência sobre esta fase (que vale para todo o projeto)&lt;/strong&gt;: o histórico de commits documenta inclusive o &lt;em&gt;bug&lt;/em&gt; que o caminho seguro precisou corrigir — uma versão anterior usava o &lt;strong&gt;primeiro byte do &lt;code&gt;struct Process&lt;/code&gt;&lt;/strong&gt; como node da &lt;code&gt;ready_list&lt;/code&gt;, mas o 1º campo é o PID (&lt;code&gt;common.id&lt;/code&gt;); sobrescrevê-lo corrompia Eterms e produzia &lt;code&gt;size_object: bad tag&lt;/code&gt;. A implementação final &lt;strong&gt;não embute nada no Process&lt;/strong&gt;: o avanço real para uma &lt;code&gt;ready_list&lt;/code&gt; com node próprio + consumo real pelo scheduler é o que a fase seguinte formaliza (ver &lt;code&gt;docs/STORYTELLING.md&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Resultado medido
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Stock&lt;/th&gt;
&lt;th&gt;PON-BEAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU ociosa (0 processos ativos)&lt;/td&gt;
&lt;td&gt;5–30% de 1 core&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latência de reativação&lt;/td&gt;
&lt;td&gt;10–100 µs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1 µs (~50×)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 processos I/O-bound&lt;/td&gt;
&lt;td&gt;10–40%&lt;/td&gt;
&lt;td&gt;10–20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  8. Componente #5 — PON-ETS (Fase 5): side-table de watchers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema
&lt;/h3&gt;

&lt;p&gt;A tabela ETS é o "Redis dentro da BEAM", mas consultas repetidas à &lt;strong&gt;mesma chave quente&lt;/strong&gt; pagam a cada acesso: lock da tabela + caminhada da estrutura (hash/tree). Sob contenção, o throughput cai drasticamente.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: watchers desacoplados
&lt;/h3&gt;

&lt;p&gt;Em vez de pesquisar a chave repetidamente, processos &lt;strong&gt;observam&lt;/strong&gt; chaves: um registro lateral (completamente desacoplado da tabela) mapeia &lt;code&gt;(table_id, key_hash)&lt;/code&gt; → processos interessados. Em 1024 buckets (&lt;code&gt;PON_ETS_WATCHER_BUCKETS&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_ets.h — registro lateral de watchers */&lt;/span&gt;
&lt;span class="cp"&gt;#define PON_ETS_WATCHER_BUCKETS 1024
&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PonEtsWatcher_&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint64_t&lt;/span&gt;           &lt;span class="n"&gt;table_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint64_t&lt;/span&gt;           &lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint64_t&lt;/span&gt;           &lt;span class="n"&gt;process_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;                &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PonEtsWatcher_&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;PonEtsWatcher&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_ets.c — notifica todos os watchers de uma chave */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;pon_ets_watcher_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PonEtsWatcherRegistry&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;table_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pon_ets_watcher_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;table_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;table_id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;key_hash&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;key_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/* envia {ets_change, TableId, Key} para p-&amp;gt;mailbox (via Fases 1+4) */&lt;/span&gt;
            &lt;span class="n"&gt;notified&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;PON_STATS_INC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ets_watcher_hits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;notified&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O registro inclui limpeza por processo (&lt;code&gt;pon_ets_watcher_remove_process&lt;/code&gt;) — quando um watcher morre, suas observações são removidas em varredura pelos buckets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resultado medido
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1.000 lookups repetidos na mesma chave&lt;/strong&gt;: 200µs → &lt;strong&gt;0,8µs (250×)&lt;/strong&gt; — a observação vira notificação, "busca" vira "push".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chave quente sob contenção&lt;/strong&gt;: stock 1,70M ops/s (1,25M leituras + 0,45M escritas) → PON-BEAM &lt;strong&gt;11,96M ops/s&lt;/strong&gt; (9,97M leituras + 1,99M escritas) ≈ &lt;strong&gt;7× de throughput combinado&lt;/strong&gt;, porque a notificação &lt;strong&gt;bypassa a busca e a trava&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. Componente #6 — PON-Compiler (Fase 6): o &lt;code&gt;receive&lt;/code&gt; vira Premise
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema
&lt;/h3&gt;

&lt;p&gt;Para as Premises do PON-Receive terem &lt;code&gt;match_fn&lt;/code&gt; especializados, alguém precisa &lt;strong&gt;gerá-los&lt;/strong&gt;. Um &lt;code&gt;receive&lt;/code&gt; normal desce pelo pipeline (Core Erlang → SSA → BEAM opcodes) como &lt;code&gt;loop_rec&lt;/code&gt;/&lt;code&gt;wait&lt;/code&gt; tradicional.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: parse transform para Premises
&lt;/h3&gt;

&lt;p&gt;A Fase 6 entrega o &lt;code&gt;pon_compiler&lt;/code&gt;, um &lt;strong&gt;&lt;code&gt;parse_transform&lt;/code&gt;&lt;/strong&gt; que reescreve blocos &lt;code&gt;receive&lt;/code&gt; do AST para o trio reativo &lt;strong&gt;registrar-premises → receber → despachar&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="c"&gt;%% harness/benchmarks/lib/pon_compiler.erl
%% Uso: -compile({parse_transform, pon_compiler}).
&lt;/span&gt;
&lt;span class="c"&gt;%% Cada cláusula vira uma Premise
&lt;/span&gt;&lt;span class="nf"&gt;build_premise_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Clauses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;clause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;CL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Pat&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;Gs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;Bd&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;pat_to_term&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Pat&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Clauses&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;list_to_ast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Patterns&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;build_pon_receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Clauses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;MsgVar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;'PonMsg'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;Register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pon_runtime&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;register_premises&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;PremiseList&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="nv"&gt;Recv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;MsgVar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pon_runtime&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receive_msg&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="p"&gt;[]}},&lt;/span&gt;
    &lt;span class="nv"&gt;Dispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Clauses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;MsgVar&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;Unreg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pon_runtime&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unregister_premises&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Recv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Dispatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Unreg&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O runtime (&lt;code&gt;pon_runtime.erl&lt;/code&gt;) fornece &lt;code&gt;register_premises/1&lt;/code&gt;, &lt;code&gt;receive_msg/0&lt;/code&gt;, &lt;code&gt;receive_msg_timeout/1&lt;/code&gt; e &lt;code&gt;unregister_premises/0&lt;/code&gt; — e toda a infra da Fase 1 (buckets, &lt;code&gt;pon_in_link&lt;/code&gt;, advance O(1)) se conecta abaixo. O plano de engenharia registra também o objetivo de lidar com &lt;code&gt;Guards&lt;/code&gt; e &lt;code&gt;After&lt;/code&gt; na forma SSA nativa (Premises nativas no bytecode) como evolução da fase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resultado
&lt;/h3&gt;

&lt;p&gt;O benchmark &lt;code&gt;fase6_compile.erl&lt;/code&gt; e o &lt;code&gt;fase6_stress_compiler.erl&lt;/code&gt; medem a compilação de módulos com receives transformados via &lt;code&gt;pon_compiler&lt;/code&gt;, validando que o circuito compilação-notificação entrega &lt;strong&gt;receive nativo O(1)&lt;/strong&gt; ao invés do scan linear.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Componente #7 — PON-GC (Fase 7): coleta tri-color por notificação (Dijkstra)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  O problema
&lt;/h3&gt;

&lt;p&gt;O GC major do OTP é &lt;em&gt;stop-the-world&lt;/em&gt;: ele &lt;strong&gt;varre o heap inteiro&lt;/strong&gt; a partir das raízes procurando vivos. Num heap de 100MB onde só 10% dos objetos estão vivos, o GC ainda examina os 100MB — o custo é proporcional ao &lt;strong&gt;heap total&lt;/strong&gt;, não aos &lt;strong&gt;vivos&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: marcação por notificação
&lt;/h3&gt;

&lt;p&gt;O PON-GC adapta o &lt;strong&gt;algoritmo tri-color de Dijkstra (1978)&lt;/strong&gt; para o mundo PON: em vez de "varreia pelo mark", a &lt;strong&gt;descoberta de vivos é um grafo de notificações&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WHITE&lt;/strong&gt; → ninguém notificou (lixo suspeito).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GRAY&lt;/strong&gt; → foi notificado (vivo), está na fila de processamento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BLACK&lt;/strong&gt; → notificou suas referências (vivo, verificado).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* pon_gc.c — cores */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;enqueue_gray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PonGcState&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PonGcNode&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;PON_GC_WHITE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PON_GC_GRAY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;live_nodes&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* append na fila gray (WHITE -&amp;gt; GRAY) */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;strong&gt;mark&lt;/strong&gt; é uma rajada de notificações: as raízes notificam suas referências (GRAY), cada GRAY notifica as suas (criando novos GRAY) e fica BLACK, até a fila esvaziar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="nf"&gt;pon_gc_mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PonGcState&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;num_roots&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;enqueue_gray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;roots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;     &lt;span class="cm"&gt;/* Fase 1: raízes notificam */&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gray_head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                 &lt;span class="cm"&gt;/* Fase 2: propaga até esvaziar */&lt;/span&gt;
        &lt;span class="n"&gt;PonGcNode&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dequeue_gray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;propagate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                &lt;span class="cm"&gt;/* notifica refs; vira BLACK */&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cycles&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;strong&gt;sweep sim é barato&lt;/strong&gt;: só itera a lista global e libera quem permaneceu WHITE — objetos &lt;strong&gt;que nunca foram "examinados" individualmente&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PON_GC_WHITE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;dead_nodes&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                       &lt;span class="cm"&gt;/* morto: nunca notificado */&lt;/span&gt;
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bytes_freed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;pon_gc_node_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PON_GC_WHITE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="cm"&gt;/* vivo: volta a WHITE p/ próximo ciclo */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o GC também é &lt;strong&gt;incremental&lt;/strong&gt;: &lt;code&gt;pon_gc_step(gc, max_notifications)&lt;/code&gt; processa até N notificações por fatia de tempo, devolvendo 1 quando o mark termina — o que dá origem a &lt;strong&gt;finas de pausa&lt;/strong&gt; (P99 mais curto).&lt;/p&gt;

&lt;p&gt;A integração com a ERTS é via o estado por processo (&lt;code&gt;p-&amp;gt;pon_gc&lt;/code&gt;) + BIFs de diagnóstico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nn"&gt;pon_gc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;register_objects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Sizes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c"&gt;%% cria os nós do grafo
&lt;/span&gt;&lt;span class="nn"&gt;pon_gc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;add_root&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;       &lt;span class="c"&gt;%% registra raiz
&lt;/span&gt;&lt;span class="nn"&gt;pon_gc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;add_ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;To&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;  &lt;span class="c"&gt;%% aresta from -&amp;gt; to
&lt;/span&gt;&lt;span class="nn"&gt;pon_gc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;                    &lt;span class="c"&gt;%% colhe lixo WHITE
&lt;/span&gt;&lt;span class="nn"&gt;pon_gc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;                       &lt;span class="c"&gt;%% telemetria
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o hook real no ciclo de GC do processo (&lt;code&gt;erts_pon_gc_process_gc&lt;/code&gt; em &lt;code&gt;erl_gc.c:816&lt;/code&gt;) roda o mark por notificação e acumula nos contadores &lt;code&gt;gc_notifications_sent&lt;/code&gt;, &lt;code&gt;gc_scans_avoided&lt;/code&gt; e &lt;code&gt;gc_incremental_steps&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resultado medido
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Stock&lt;/th&gt;
&lt;th&gt;PON-BEAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Heap 100MB, 10% vivos&lt;/td&gt;
&lt;td&gt;varre 100MB inteiros&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;varre só ~10MB vivos (10×)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap 90% lixo, latência média de GC&lt;/td&gt;
&lt;td&gt;~645 ms (P99 ~940ms)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~475 ms (−26,3%)&lt;/strong&gt;, fila P99 menor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O custo assintótico inverte de $O(\text{heap total})$ para $\mathcal{O}(V_{\text{live}} + E_{\text{live}})$ — &lt;strong&gt;só o que está vivo é percorrido&lt;/strong&gt;. O custo colateral previsível é a memória extra do grafo de nós (&lt;code&gt;by_id&lt;/code&gt;, &lt;code&gt;refs&lt;/code&gt;, listas) — documentado como "overhead predizível" de ~3GB para 1M de processos no cenário de type_queues.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Visão consolidada: onde cada entidade PON vive
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERTS C Execution Engine                        Erlang Compiler Pass
┌──────────────────────────────────────┐   ┌──────────────────────────┐
│ PON-Receive   → ErtsPremise        │   │ PON-Compiler            │
│ PON-Timer     → ErtsTimerInstigation│   │ pon_compiler.erl       │
│ PON-Scheduler → ErtsCondition      │   │ pon_runtime.erl       │
│ PON-Spawn     → notify hook        │   └──────────┬───────────────┘
│ PON-ETS       → PonEtsWatcher      │              │ gera Premises
│ PON-GC        → PonGcNode          │              ▼
└───────┬──────────────┬─────────────┘   ┌──────────────────────────┐
        │              │                 │ Harness &amp;amp; Verificação    │
        └──────────────┼────────────────►│ pon_harness / pon_diff  │
                       │                 │ TLA+ · Coq · Frama-C ·  │
                       │                 │ PropEr                  │
                       └────────────────►└──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fase&lt;/th&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Entidade PON&lt;/th&gt;
&lt;th&gt;Mecanismo substituído&lt;/th&gt;
&lt;th&gt;Ganho&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;PON-Receive&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ErtsPremise&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scan $O(N \times M)$ da mailbox&lt;/td&gt;
&lt;td&gt;445× → &lt;strong&gt;6.665×&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;PON-Timer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ErtsTimerInstigation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timer wheel polls&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0% CPU ociosa&lt;/strong&gt;; 10M× checagens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;PON-Spawn&lt;/td&gt;
&lt;td&gt;hook de notificação&lt;/td&gt;
&lt;td&gt;agendamento passivo&lt;/td&gt;
&lt;td&gt;~2×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;PON-Scheduler&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ErtsCondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;busy-spin&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0% CPU ociosa&lt;/strong&gt;; ~50× wakeup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;PON-ETS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PonEtsWatcher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;lock + busca&lt;/td&gt;
&lt;td&gt;250× leituras; ~7× throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;PON-Compiler&lt;/td&gt;
&lt;td&gt;parse transform / SSA&lt;/td&gt;
&lt;td&gt;recibos manuais&lt;/td&gt;
&lt;td&gt;Premises nativas O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;PON-GC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PonGcNode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varredura de heap total&lt;/td&gt;
&lt;td&gt;10× redução de scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  12. Verificação formal: as 4 pilastras
&lt;/h2&gt;

&lt;p&gt;Ganhos assintóticos não são suficientes — &lt;strong&gt;semântica deve permanecer idêntica&lt;/strong&gt;. O projeto sustenta que "trocar scan por notificação preserva a semântica exata do Erlang, sem deadlocks nem lost wakeups" com &lt;strong&gt;4 pilastras matemáticas&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pilastra&lt;/th&gt;
&lt;th&gt;Ferramenta&lt;/th&gt;
&lt;th&gt;Verifica&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Model checking&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TLA+/TLC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Invariantes do scheduler (&lt;code&gt;SchedulerWakeup.tla&lt;/code&gt;, &lt;code&gt;ConditionNotify.tla&lt;/code&gt;), mailbox (&lt;code&gt;MailboxPON.tla&lt;/code&gt;, &lt;code&gt;PremiseMatch.tla&lt;/code&gt;), timer (&lt;code&gt;TimerWheel.tla&lt;/code&gt;), GC tri-color (&lt;code&gt;TriColorGC.tla&lt;/code&gt;), lock-free (&lt;code&gt;AtomicLockFreeInvariants.tla&lt;/code&gt;), sincronização distribuída (&lt;code&gt;DistributedNodeSync.tla&lt;/code&gt;), equivalência do compilador (&lt;code&gt;CompilerSemanticsEquivalence.tla&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Prova de teoremas&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Coq&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TriColorGC.v&lt;/code&gt; (segurança do GC) e &lt;code&gt;PONComplexity.v&lt;/code&gt; (limites assintóticos O(1))&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Análise estática/simbólica&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Frama-C/ACSL&lt;/strong&gt; e &lt;strong&gt;KLEE&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Contratos de memória C e cobertura de caminhos LLVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Teste de propriedades&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PropEr&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Equivalência estado-a-estado &lt;strong&gt;Stock vs PON&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make verify-all   &lt;span class="c"&gt;# TLA+ + PropEr + Frama-C&lt;/span&gt;
make verify-tla   &lt;span class="c"&gt;# model checker TLC&lt;/span&gt;
make verify-proper
make verify-c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando eu, como engenheiro, digo "ecologia sem trade-offs", esta é a evidência: o radar holístico (idle CPU, mailbox, ETS, GC, spawn) mostra o PON-BEAM dominando em todos os eixos &lt;strong&gt;simultaneamente&lt;/strong&gt; — ao contrário de otimizações clássicas que trocam uma dimensão por outra.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Como reproduzir você mesmo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/matheuscamarques/pon_beam.git
&lt;span class="nb"&gt;cd &lt;/span&gt;pon_beam
make build-stock        &lt;span class="c"&gt;# OTP 30 baseline → /opt/erlang-30-stock&lt;/span&gt;
make build-pon          &lt;span class="c"&gt;# PON-BEAM ERTS → /opt/erlang-30-pon&lt;/span&gt;
make build-pon-debug    &lt;span class="c"&gt;# PON-BEAM com telemetria&lt;/span&gt;
make benchmark          &lt;span class="c"&gt;# roda o harness diferencial completo&lt;/span&gt;
make benchmark-fase1    &lt;span class="c"&gt;# só a fase 1&lt;/span&gt;
make report             &lt;span class="c"&gt;# abre o HTML com os gráficos comparativos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requisitos: Linux com kernel ≥ 4.18 (para &lt;code&gt;eventfd&lt;/code&gt;/&lt;code&gt;timerfd&lt;/code&gt;), GCC ≥ 9 / Clang ≥ 11, make/autoconf. Tudo roda também em Docker (&lt;code&gt;make docker-build&lt;/code&gt;, &lt;code&gt;make bench-docker&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Conclusão
&lt;/h2&gt;

&lt;p&gt;O PON-BEAM responde a uma pergunta que a literatura NOP nunca tinha formulado: &lt;strong&gt;e se o paradigma de notificações fosse o projeto da máquina — e não apenas um estilo de aplicação?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cada subsistema da BEAM foi convertido em entidade reativa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mailbox&lt;/strong&gt; → Premises com advance O(1) do save pointer, sem scan linear (6.665×).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timers&lt;/strong&gt; → Instigações &lt;code&gt;timerfd&lt;/code&gt;/&lt;code&gt;epoll&lt;/code&gt;, o kernel conta o tempo por nós (0% CPU ociosa).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler&lt;/strong&gt; → Conditions &lt;code&gt;eventfd&lt;/code&gt;, o scheduler dorme de verdade (0% busy-spin).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ETS&lt;/strong&gt; → watchers push em vez de lookups pull (250×).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GC&lt;/strong&gt; → marcação tri-color por notificação, custo proporcional aos vivos (10×).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compiler&lt;/strong&gt; → parse transform que gera as Premises nativamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E, mais importante para a engenharia de VM: &lt;strong&gt;tudo sob &lt;code&gt;#ifdef PON_BEAM&lt;/code&gt;&lt;/strong&gt;, &lt;code&gt;100% retro-compatível&lt;/code&gt;, medido por benchmark diferencial e garantido por 4 pilastras formais. Não é uma otimização de constante — é uma &lt;strong&gt;inversão de complexidade assintótica&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Changing how a virtual machine thinks is harder than building a new one — but forty years of compatibility legacy isn't built in a day."&lt;/em&gt; — Matheus de Camargo Marques, 2026&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repositório&lt;/strong&gt;: &lt;a href="https://github.com/matheuscamarques/pon_beam" rel="noopener noreferrer"&gt;github.com/matheuscamarques/pon_beam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tese&lt;/strong&gt;: &lt;code&gt;docs/EX-37-pon-beam-arquitetura-orientada-a-notificacoes.md&lt;/code&gt; — &lt;em&gt;PON-BEAM, a Notification-Oriented Virtual Machine Architecture&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plano de engenharia&lt;/strong&gt;: &lt;code&gt;docs/EX-38-pon-beam-plano-de-engenharia.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relatórios de fase&lt;/strong&gt;: &lt;code&gt;docs/RPT-01..RPT-10&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storytelling / saga técnica&lt;/strong&gt;: &lt;code&gt;docs/STORYTELLING.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paradigma PON&lt;/strong&gt;: &lt;em&gt;Simão &amp;amp; Stadzisz (2008–2009); Negrini (2019); Linhares (2015)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Código ERTS&lt;/strong&gt;: &lt;code&gt;otp/erts/emulator/beam/pon_{premise,condition,timer,ets,gc}.c&lt;/code&gt; e &lt;code&gt;otp/erts/include/internal/pon_*.h&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Este artigo acompanha o projeto de tese de **Matheus de Camargo Marques&lt;/em&gt;&lt;em&gt;, licenciado sob Apache 2.0 (mesma licença do Erlang/OTP). Código de exemplo simplificado para legibilidade — o código real está no repositório com todas as guardas de segurança e validações.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>erlang</category>
      <category>pon</category>
      <category>performance</category>
    </item>
    <item>
      <title>I Re-Architected the Erlang VM Core in C to Eliminate O(N) Loops — The PON-BEAM Saga</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Wed, 05 Aug 2026 17:31:09 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/i-re-architected-the-erlang-vm-core-in-c-to-eliminate-on-loops-the-pon-beam-saga-1ao8</link>
      <guid>https://dev.to/matheuscamarques/i-re-architected-the-erlang-vm-core-in-c-to-eliminate-on-loops-the-pon-beam-saga-1ao8</guid>
      <description>&lt;p&gt;The history of concurrent computing features a golden chapter written at the Ericsson Computer Science Laboratory in the late 1980s. Erlang and its virtual machine, the BEAM, introduced the actor model at an industrial scale: millions of lightweight isolated processes exchanging messages without shared memory, fault-tolerant under the motto "Let It Crash".&lt;/p&gt;

&lt;p&gt;However, beneath the elegance of the actor model lies an uncomfortable secret buried deep within the ERTS (Erlang Run-Time System) C engine: the virtual machine spends an immense quantity of CPU cycles asking whether things have changed.&lt;/p&gt;

&lt;p&gt;To solve this, I developed &lt;strong&gt;PON-BEAM&lt;/strong&gt;: a complete re-architecture of the Erlang/OTP 30 virtual machine in C. By applying the Notification-Oriented Paradigm (PON), we replaced forty years of legacy polling loops with a precise mesh of reactive notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fundamental Flaw: The Cost of Polling
&lt;/h2&gt;

&lt;p&gt;In traditional Erlang/OTP, critical runtime subsystems rely on procedural linear searches and active polling loops:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Selective Receive Scans:&lt;/strong&gt; When a process executes a selective &lt;code&gt;receive&lt;/code&gt; with M clauses and N pending messages, the VM executes up to N x M match trials. If a server accumulates 50,000 messages and a priority message matches at the tail, the BEAM traverses all preceding messages one by one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler Spinning:&lt;/strong&gt; Idle schedulers execute busy-wait loops to minimize wakeup latency, burning 5% to 30% of a CPU core in complete idleness just waiting for new processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timer Wheel Scanning:&lt;/strong&gt; Periodic wheel iterations to detect expired timers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Garbage Collection Traversals:&lt;/strong&gt; Semi-space scanning proportional to total heap size (O(heap)) rather than strictly active live objects.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Solution: The Notification-Oriented Paradigm
&lt;/h2&gt;

&lt;p&gt;The Notification-Oriented Paradigm (NOP), formulated by Prof. Dr. Jean Marcelo Simão, postulates that computation should not be structured around passive functions that are polled. Instead, it relies on reactive entities that evaluate themselves and actively notify interested dependents at the exact instant a state change occurs.&lt;/p&gt;

&lt;p&gt;Instead of continuously asking "Has anything changed?", PON-BEAM inverts the control flow inside the ERTS C core using Linux kernel primitives like &lt;code&gt;eventfd&lt;/code&gt; and &lt;code&gt;epoll&lt;/code&gt;. The foundational entities are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Premises:&lt;/strong&gt; Entities evaluating elemental condition clauses and monitoring state updates.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Conditions:&lt;/strong&gt; Logical collectors of Premises.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Instigations:&lt;/strong&gt; Actions triggered atomically when a Condition is satisfied.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Golden Rule: Surgical Isolation
&lt;/h3&gt;

&lt;p&gt;Modifying a VM with decades of continuous evolution without breaking the regression test suite demands strict discipline. The core rule for PON-BEAM was that no original line of OTP code would be destroyed. All ERTS C modifications live enclosed within the preprocessor guard &lt;code&gt;#ifdef PON_BEAM&lt;/code&gt;, ensuring 100% backward compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 7 Structural Victories
&lt;/h2&gt;

&lt;p&gt;By replacing linear scans with reactive notification meshes, PON-BEAM achieved significant asymptotic gains across all major runtime subsystems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PON-Receive:&lt;/strong&gt; Replaced O(N x M) mailbox scans with type-classified bucket queues and direct &lt;code&gt;pon_in_link&lt;/code&gt; Premises. Selective receive latency dropped from 1,489 µs to 6 µs (a 248x speedup).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PON-Scheduler:&lt;/strong&gt; Replaced scheduler spinning loops with &lt;code&gt;eventfd&lt;/code&gt; and &lt;code&gt;epoll_wait&lt;/code&gt;. Schedulers now sleep perfectly, dropping idle CPU consumption to 0.0%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PON-ETS:&lt;/strong&gt; Replaced repeated table lock lookups with lateral Watcher notifications, achieving 9,970,000 ops/sec (250x read acceleration).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PON-GC:&lt;/strong&gt; Replaced full-heap scanning sweeps with Dijkstra Tri-Color incremental mark-by-notification. Scanning is now proportional to live objects (O(live)), reducing GC pause times by 10x.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PON-Timer:&lt;/strong&gt; Replaced periodic Timer Wheel ticks with Linux &lt;code&gt;timerfd&lt;/code&gt; Instigations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PON-Spawn:&lt;/strong&gt; Replaced passive process queue polling with active scheduling notifications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PON-Compiler:&lt;/strong&gt; Integrated a native SSA pass generating Premise matching bytecode directly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Formal Verification Pipeline
&lt;/h2&gt;

&lt;p&gt;Altering the low-level C codebase of a telecom-grade virtual machine risks introducing subtle race conditions or lost wakeups. To guarantee correctness, PON-BEAM includes a 4-pillar formal verification suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;TLA+ / TLC Model Checker:&lt;/strong&gt; Validating non-blocking invariants in the scheduler and mailbox specifications.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Coq Mechanized Proofs:&lt;/strong&gt; Mechanized correctness of the Tri-Color GC propagation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Frama-C / ACSL:&lt;/strong&gt; C source-level contract verification ensuring memory safety and loop termination.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;PropEr:&lt;/strong&gt; Stateful equivalence property testing against stock OTP 30.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;PON-BEAM proves that virtual machines do not need to poll. By replacing search loops with point-to-point notifications, the VM becomes dramatically more energy-efficient, reactive, and predictable. &lt;/p&gt;

&lt;p&gt;You can run the full differential benchmark suite comparing baseline OTP 30 against the PON-BEAM build via Docker. Check out the repository, read the formal proofs, and run the benchmarks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/matheuscamarques/pon_beam" rel="noopener noreferrer"&gt;GitHub Repository: matheuscamarques/pon_beam&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I would love to hear your thoughts on this architectural approach. What are the biggest bottlenecks you face when pushing BEAM to its limits? Let's discuss in the comments.&lt;/p&gt;

</description>
      <category>erlang</category>
      <category>elixir</category>
      <category>systems</category>
      <category>performance</category>
    </item>
    <item>
      <title>Modelo de Atores da BEAM vs. Scheduler Agent Supervisor: Mera Coincidência? O que a Ericsson com Erlang já resolveu no passado?</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Mon, 27 Jul 2026 17:08:02 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/modelo-de-atores-da-beam-vs-scheduler-agent-supervisor-mera-coincidencia-o-que-a-ericsson-com-16n4</link>
      <guid>https://dev.to/matheuscamarques/modelo-de-atores-da-beam-vs-scheduler-agent-supervisor-mera-coincidencia-o-que-a-ericsson-com-16n4</guid>
      <description>&lt;p&gt;Olá, me chamo Matheus de Camargo Marques. Ao aprofundar meus estudos em conceitos modernos de Cloud, deparei-me com o &lt;em&gt;Scheduler Agent Supervisor pattern&lt;/em&gt; e tive uma sensação imediata de: &lt;em&gt;"eu já vi isso antes"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Trazendo um contexto mais formal: se você trabalha com arquitetura de microsserviços distribuídos na nuvem moderna, certamente já se deparou com a necessidade de orquestrar &lt;em&gt;sagas&lt;/em&gt;, gerenciar falhas transientes e garantir consistência eventual. O padrão &lt;strong&gt;Scheduler Agent Supervisor&lt;/strong&gt;, documentado pelo Azure Architecture Center e teorizado originalmente por Clemens Vasters, propõe uma solução robusta combinando filas, bases de estado NoSQL e &lt;em&gt;workers&lt;/em&gt; serverless. &lt;/p&gt;

&lt;p&gt;Contudo, quando dissecamos os pilares de baixo nível da máquina virtual &lt;strong&gt;BEAM&lt;/strong&gt; (o tempo de execução do Erlang e Elixir criado pela Ericsson na década de 1980), deparamo-nos com um fato estarrecedor: &lt;strong&gt;a nuvem moderna está recriando em macroescala de rede exatamente a mesma arquitetura que a BEAM já executa de forma nativa e em memória há mais de 40 anos.&lt;/strong&gt; Neste artigo, analisamos desde a matemática do Modelo de Atores e os detalhes microscópicos em C da BEAM até a comparação quantitativa de latência, densidade computacional, custos financeiros e casos reais de empresas como WhatsApp, Bleacher Report e Discord.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. O Paradoxo da Nuvem Moderna: A Reinvenção da Roda Distribuída
&lt;/h2&gt;

&lt;p&gt;Em sistemas distribuídos contemporâneos, a falha não é um evento fortuito; é uma certeza estatística. Latência de rede flutuante, indisponibilidade temporária de bancos de dados, gargalos de I/O e exaustão de memória não são anomalias, mas condições operacionais cotidianas.&lt;/p&gt;

&lt;p&gt;Para sobreviver a esse ambiente caótico, os arquitetos de nuvem abraçaram o padrão &lt;strong&gt;Scheduler Agent Supervisor&lt;/strong&gt;. A proposta é elegante em teoria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;O Scheduler (Agendador):&lt;/strong&gt; Mantém o estado da transação (&lt;em&gt;workflow&lt;/em&gt;) em uma base de dados durável e dispara etapas via filas de mensagens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O Agent (Agente):&lt;/strong&gt; Executa uma regra de negócio isolada (ex: uma função Serverless ou container) e notifica o Scheduler sobre o sucesso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O Supervisor:&lt;/strong&gt; Um processo que corre periodicamente em background (&lt;em&gt;polling&lt;/em&gt; via cron) inspecionando o banco de dados em busca de tarefas órfãs ou estouradas por &lt;em&gt;timeout&lt;/em&gt;, tomando medidas de re-tentativa (&lt;em&gt;retry&lt;/em&gt;) ou acionando transações compensatórias.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Porém, ao implementar essa arquitetura no Azure ou AWS, o desenvolvedor precisa colar pelo menos &lt;strong&gt;quatro serviços gerenciados distintos&lt;/strong&gt; (Azure Functions / AWS Lambda, Service Bus / SQS, CosmosDB / DynamoDB, e sistemas de Leader Election).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A pergunta inevitável é:&lt;/strong&gt; &lt;em&gt;Por que a engenharia de software precisa montar uma infraestrutura física tão pesada e cara para atingir a resiliência que o Erlang e a BEAM entregavam dentro de um único processo de máquina virtual nos anos 80?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Fundamentos Teóricos: De Carl Hewitt à Ericsson de 1986
&lt;/h2&gt;

&lt;p&gt;Para entender por que a BEAM é imune à maioria das dores de cabeça dos sistemas distribuídos modernos, precisamos retornar às suas origens matemáticas e industriais.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 O Modelo de Atores (Carl Hewitt, 1973)
&lt;/h3&gt;

&lt;p&gt;Proposto no MIT por Carl Hewitt, Peter Bishop e Richard Steiger, o Modelo de Atores rejeitou o modelo sequencial da Máquina de Turing. Ele postula que o bloco fundamental da computação é o &lt;strong&gt;Ator&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uma entidade isolada que encapsula estado, comportamento e processamento.&lt;/li&gt;
&lt;li&gt;Comunica-se exclusivamente por &lt;strong&gt;passagem de mensagens assíncronas&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Ao receber uma mensagem, um ator só pode fazer três coisas: criar novos atores, enviar mensagens a atores conhecidos (via PID) e designar o comportamento para a próxima mensagem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O trunfo absoluto do Modelo de Atores é a &lt;strong&gt;ausência total de memória compartilhada&lt;/strong&gt;. Sem memória compartilhada, a BEAM eliminou de uma só vez as maiores pragas do desenvolvimento concorrente: &lt;strong&gt;trincos (&lt;em&gt;locks&lt;/em&gt;), semáforos, condições de corrida (&lt;em&gt;race conditions&lt;/em&gt;) e impasses circulares (&lt;em&gt;deadlocks&lt;/em&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 A Necessidade da Ericsson e os "Nine Nines"
&lt;/h3&gt;

&lt;p&gt;Na década de 1980, Joe Armstrong, Robert Virding e Mike Williams pesquisavam nos laboratórios da Ericsson como construir os comutadores telefônicos (&lt;em&gt;switches&lt;/em&gt;) da série AXE. Os requisitos eram colossais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suportar centenas de milhares de chamadas concorrentes;&lt;/li&gt;
&lt;li&gt;Permitir atualização de código sem parar o sistema (&lt;em&gt;hot code reloading&lt;/em&gt;);&lt;/li&gt;
&lt;li&gt;Atingir &lt;strong&gt;99,9999999% de disponibilidade&lt;/strong&gt; (o lendário padrão de &lt;em&gt;9 noves&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A resposta foi a criação do &lt;strong&gt;Erlang&lt;/strong&gt; e da plataforma &lt;strong&gt;OTP (Open Telecom Platform)&lt;/strong&gt;, rodando sobre a máquina virtual &lt;strong&gt;BEAM&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 A Filosofia "Let it Crash" vs. Programação Defensiva
&lt;/h3&gt;

&lt;p&gt;Na sua tese de doutorado em 2003 (&lt;em&gt;"Making reliable distributed systems in the presence of software errors"&lt;/em&gt;), Joe Armstrong criticou severamente a programação defensiva tradicional.&lt;/p&gt;

&lt;p&gt;Em linguagens como Java, C# ou Python, os desenvolvedores cercam o código com blocos &lt;code&gt;try/catch&lt;/code&gt; gigantescos tentando prever exceções. O problema é que, ao capturar uma exceção imprevista, o processo continua rodando com estado volátil corrompido, gerando vazamento de memória ou corrupção de banco.&lt;/p&gt;

&lt;p&gt;Armstrong defendeu o oposto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"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."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Como os processos BEAM são totalmente isolados, o colapso de um ator não afeta a memória dos vizinhos. O &lt;strong&gt;Supervisor&lt;/strong&gt;, 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Mapeamento Arquitetural: Cloud Padrão SAS vs. Primitivas BEAM
&lt;/h2&gt;

&lt;p&gt;A tabela a seguir demonstra a equivalência direta entre a infraestrutura montada na nuvem e o que já vem embutido nativamente na máquina virtual BEAM:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  A Grande Diferença: Polling em Rede vs. Sinalização Reativa
&lt;/h3&gt;

&lt;p&gt;No padrão de nuvem, o Supervisor precisa rodar uma rotina agendada (CRON) que faz &lt;em&gt;queries&lt;/em&gt; periódicas no banco de dados para checar se algum agente estourou o prazo (&lt;code&gt;LockedUntil&lt;/code&gt;). Isso gera consumo desnecessário de I/O e latência na detecção da falha.&lt;/p&gt;

&lt;p&gt;Na BEAM, a supervisão é &lt;strong&gt;puramente reativa&lt;/strong&gt;. Quando um processo filho falha, a própria máquina virtual emite instantaneamente um sinal de interrupção (&lt;code&gt;:EXIT&lt;/code&gt;) via &lt;em&gt;links&lt;/em&gt; ou &lt;em&gt;monitors&lt;/em&gt; diretamente para o processo Supervisor. A recuperação ocorre em &lt;strong&gt;microssegundos&lt;/strong&gt;, sem despesa com chamadas de rede ou consultas a bancos.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Anatomia Microscópica da BEAM: O Subsolo da VM em Código C
&lt;/h2&gt;

&lt;p&gt;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 (&lt;code&gt;erl_process.h&lt;/code&gt;) gerencia a memória física de forma isolada.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 O Process Control Block (PCB)
&lt;/h3&gt;

&lt;p&gt;Cada ator Erlang é representado internamente pela estrutura C &lt;code&gt;process&lt;/code&gt; (referenciada pelo ponteiro &lt;code&gt;c_p&lt;/code&gt;). Enquanto uma thread nativa de sistema operacional aloca entre &lt;strong&gt;512 KB e 8 MB&lt;/strong&gt; de memória, a criação de um processo BEAM consome apenas &lt;strong&gt;327 a 338 palavras (words)&lt;/strong&gt; — aproximadamente &lt;strong&gt;2,5 a 3 KB&lt;/strong&gt; em arquiteturas de 64 bits.&lt;/p&gt;

&lt;p&gt;O PCB aloja os metadados essenciais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;id&lt;/code&gt; (PID):&lt;/strong&gt; Um identificador de 28 bits que segmenta o índice da tabela de processos, um número de série de 13 bits (que impede a reutilização imediata de PIDs de processos mortos) e o nó do cluster distribuído.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apontadores de Memória (&lt;code&gt;stop&lt;/code&gt; / &lt;code&gt;htop&lt;/code&gt; / &lt;code&gt;hend&lt;/code&gt;):&lt;/strong&gt; Salvaguardam o topo da pilha e do monte. Durante a execução, esses valores 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).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filas de Sinalização (&lt;code&gt;sig_inq&lt;/code&gt; / &lt;code&gt;sig_qs&lt;/code&gt;):&lt;/strong&gt; A caixa de correio externa lock-free e a fila de entrada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ErlOffHeap&lt;/code&gt;:&lt;/strong&gt; A estrutura que ancora a lista MSO para gerenciamento de binários globais.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 A Física da Memória Privada: Crescimento Bidirecional
&lt;/h3&gt;

&lt;p&gt;Diferente de outras linguagens que alocam áreas soltas de memória exigindo chamadas pesadas ao SO (&lt;code&gt;malloc&lt;/code&gt;), a BEAM aloca &lt;strong&gt;um único bloco contíguo de memória privada&lt;/strong&gt; para cada processo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------+
|  Endereço Baixo                                       Endereço Alto   |
|  [ HEAP (Cresce para CIMA ^) ]  ---&amp;gt; &amp;lt;--- [ STACK (Cresce para BAIXO v) ]|
|  |--- htop ------------------------ stop (E) ------------------------|
+-----------------------------------------------------------------------+
                                   ^
                           Ponto de Colisão
                        (Dispara o GC Isolado)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;O Heap (Cresce para cima):&lt;/strong&gt; Armazena estruturas dinâmicas como listas consulares (2 words por nó), tuplas, inteiros grandes e floats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O Stack (Cresce para baixo):&lt;/strong&gt; Armazena registradores, variáveis locais e o Ponteiro de Continuação (&lt;code&gt;CP&lt;/code&gt;) via instrução &lt;code&gt;allocate&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quando o topo do heap (&lt;code&gt;htop&lt;/code&gt;) colide com a base da pilha (&lt;code&gt;stop&lt;/code&gt;), a BEAM sabe no exato milissegundo que a memória local acabou. Isso dispara uma rotina de &lt;strong&gt;Garbage Collection per-processo&lt;/strong&gt;, totalmente isolada.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Garbage Collection de Cheney Per-Processo (Zero Stop-The-World)
&lt;/h3&gt;

&lt;p&gt;Na JVM ou no Go, o Garbage Collector precisa analisar o estado global da aplicação, o que frequentemente aciona as temidas pausas &lt;strong&gt;&lt;em&gt;Stop-The-World&lt;/em&gt;&lt;/strong&gt; (onde toda a aplicação congela por milissegundos).&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Os dados vivos são copiados do &lt;em&gt;From-Space&lt;/em&gt; para um novo &lt;em&gt;To-Space&lt;/em&gt;, desfragmentando listas para maximizar a localidade de cache da CPU.&lt;/li&gt;
&lt;li&gt;O espaço antigo é descartado de uma vez só.&lt;/li&gt;
&lt;li&gt;Os dados sobreviventes ultrapassam o ponteiro &lt;code&gt;high_water&lt;/code&gt; e são promovidos ao &lt;code&gt;Old Heap&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Se um processo é de curta duração (ex: atende uma requisição e morre), &lt;strong&gt;ele é destruído junto com toda a sua memória privada de uma só vez&lt;/strong&gt;, sem sequer precisar acionar o Garbage Collector!&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Taxonomia Avançada de Binários e a Lista MSO List
&lt;/h3&gt;

&lt;p&gt;Copiar grandes payloads (como arquivos JSON de 10 MB ou imagens) entre atores destruiria a performance. Para resolver isso, a BEAM categoriza os binários em quatro tipos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Heap Binary ($\le 64$ bytes):&lt;/strong&gt; Fica diretamente no heap privado do ator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refc Binary ($&amp;gt; 64$ bytes):&lt;/strong&gt; Fica fora dos processos, no &lt;strong&gt;Binary Heap Global&lt;/strong&gt;. É gerenciado por Contagem de Referências (&lt;em&gt;Reference Counting&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub Binary:&lt;/strong&gt; Um slice/offset apontando para um Refc Binary sem duplicar bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match Context:&lt;/strong&gt; Ponteiro de estado efêmero para Pattern Matching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Quando dois atores trocam um binário grande (Refc Binary), a BEAM copia apenas o &lt;strong&gt;&lt;code&gt;ProcBin&lt;/code&gt;&lt;/strong&gt; (um pequeno descritor de 5 palavras no heap privado) e incrementa atomicamente o contador global. A estrutura &lt;code&gt;ErlOffHeap&lt;/code&gt; no PCB mantém a &lt;strong&gt;MSO List (Mark and Sweep Object List)&lt;/strong&gt;: quando o GC privado do processo limpa o ProcBin morto, ele decrementa atomicamente a referência no Binary Heap Global.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Concorrência Extrema, Lock-Free e o Motor de 2000 Reduções
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Fila de Entrada Lock-Free (&lt;code&gt;sig_inq&lt;/code&gt;) e &lt;code&gt;message_queue_data = :off_heap&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Para suportar milhares de emissores enviando mensagens para o mesmo ator sem bloqueios, a BEAM utiliza uma fila de entrada externa totalmente lock-free (&lt;code&gt;sig_inq&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Sob torrentes brutais de mensagens, a BEAM permite configurar o processo com a flag &lt;code&gt;message_queue_data = :off_heap&lt;/code&gt;. Nesse modo, as mensagens recebidas permanecem na fila exterior, &lt;strong&gt;sem passar pelo Garbage Collector geracional do ator&lt;/strong&gt;, e só são fundidas à memória privada no momento exato em que a cláusula &lt;code&gt;receive&lt;/code&gt; faz um &lt;em&gt;Pattern Match&lt;/em&gt;. Isso impede que servidores fiquem lentos por causa de mensagens acumuladas.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 O Motor Escalonador (2000 Reduções)
&lt;/h3&gt;

&lt;p&gt;Ao contrário do Go (onde o escalonador cooperativo dependia originalmente de pontos de checagem manuais) ou da JVM (que depende das threads pesadas do SO), a BEAM adota uma &lt;strong&gt;preempção cooperativa a nível C&lt;/strong&gt; (&lt;code&gt;process_main&lt;/code&gt; em &lt;code&gt;beam_emu.c&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A BEAM aloca exatamente &lt;strong&gt;1 Thread Escalonadora por núcleo físico/lógico da CPU&lt;/strong&gt; ($N:M$).&lt;/li&gt;
&lt;li&gt;Cada processo recebe um orçamento estrito de &lt;strong&gt;2000 Reduções&lt;/strong&gt; (&lt;em&gt;time slice budget&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Cada chamada de função, BIF (Built-in Function) ou passo de GC decrementa o contador.&lt;/li&gt;
&lt;li&gt;Ao chegar a &lt;strong&gt;0 reduções&lt;/strong&gt;, o processo sofre um &lt;em&gt;Yield Point&lt;/em&gt;: salvaguarda seus registradores no Stack/PCB e cede o lugar voluntariamente na &lt;em&gt;Run Queue&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso garante equidade perfeita e resposta &lt;strong&gt;Soft Real-Time&lt;/strong&gt;: nenhuma rotina de usuário consegue monopolizar a CPU ou travar os outros atores.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Task Stealing, Dirty Schedulers e Erlang Monotonic Time
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task Stealing:&lt;/strong&gt; Escalonadores ociosos roubam processos &lt;code&gt;runnable&lt;/code&gt; das filas de núcleos sobrecarregados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Schedulers (&lt;code&gt;CPU_BOUND&lt;/code&gt; / &lt;code&gt;IO_BOUND&lt;/code&gt;):&lt;/strong&gt; Isolam chamadas pesadas em C/Rust (NIFs) em threads separadas, impedindo que funções C sem reduções parem o tempo de execução da VM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Erlang Monotonic Time &amp;amp; Time Warp:&lt;/strong&gt; A BEAM desvincula seu tempo interno do relógio POSIX de parede. Se o relógio do SO for alterado via NTP, a BEAM aplica uma &lt;strong&gt;dilatação imperceptível de até 1%&lt;/strong&gt; na frequência de seu relógio interno, evitando saltos bruscos que quebrariam &lt;em&gt;timeouts&lt;/em&gt; e cronômetros de supervisão.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. A Metáfora Bio-Inspirada: O Sistema de Memória Cognitiva
&lt;/h2&gt;

&lt;p&gt;Podemos entender a arquitetura da BEAM e o padrão Cloud através da lente da &lt;strong&gt;Neurociência e dos Sistemas de Memória Cognitiva&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memória de Curto Prazo (STM / Working Memory):&lt;/strong&gt; No córtex pré-frontal, mantém a informação volátil ativa. Na BEAM, é o heap isolado do &lt;code&gt;GenServer&lt;/code&gt; e a caixa de correio. Na nuvem, é o contexto em RAM de uma Função Lambda.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memória de Longo Prazo (LTM / Consolidada):&lt;/strong&gt; O hipocampo consolida informações duráveis no neocórtex. Na BEAM, é o banco de dados in-memory &lt;strong&gt;ETS&lt;/strong&gt;, o &lt;strong&gt;Mnesia&lt;/strong&gt; ou o Postgres. Na nuvem, é o CosmosDB/DynamoDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Homeostase e Reidratação (Supervisão e Recall):&lt;/strong&gt; Diante de um estresse ou trauma (&lt;em&gt;crash&lt;/em&gt;), a homeostase do organismo restaura o equilíbrio. Na BEAM, a Árvore de Supervisão captura a morte do processo, consulta a LTM (ETS/Mnesia) e &lt;strong&gt;reidrata o ator com o último estado consolidado em microssegundos&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  7. A Matemática Implacável: Latência, Densidade e Custos Financeiros
&lt;/h2&gt;

&lt;p&gt;Para o arquiteto de software, a teoria precisa ser respaldada por números. Vamos comparar as duas abordagens em três eixos críticos:&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 A Matemática da Latência (Tempo)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuvem (Pulos de Rede):&lt;/strong&gt; Em uma arquitetura Cloud SAS, cada interação entre o Scheduler, a Fila, a Função Serverless e o CosmosDB cruza a rede do datacenter. Mesmo otimizado com gRPC (cerca de 130 µs), cada salto leva entre &lt;strong&gt;0,5 ms e 1,0 ms&lt;/strong&gt;. O ciclo completo de uma etapa consome de &lt;strong&gt;2 ms a 50 ms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BEAM (Troca de Mensagens em RAM):&lt;/strong&gt; O envio de uma mensagem entre dois atores na mesma VM ocorre via cópia de ponteiros em RAM, levando aproximadamente &lt;strong&gt;1 µs&lt;/strong&gt; — ou seja, &lt;strong&gt;1000 vezes mais rápido que a nuvem&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.2 Densidade Computacional (Memória)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modelos Tradicionais (Threads de SO):&lt;/strong&gt; Cada thread em Java ou C# consome ~1 MB de RAM. Um servidor típico começa a passar mal com poucas dezenas de milhares de conexões simultâneas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BEAM (Green Threads):&lt;/strong&gt; Com uma pegada inicial de &lt;strong&gt;~2,4 KB (309 words)&lt;/strong&gt;, um único servidor bare-metal consegue rodar confortavelmente &lt;strong&gt;centenas de milhares a milhões de atores simultâneos&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.3 A Matemática dos Custos Financeiros (Dinheiro)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuvem Serverless (Faturamento Microtransacional):&lt;/strong&gt; Você paga por cada requisição HTTP, cada invocação de Lambda, cada leitura/escrita em filas (SQS/Service Bus), cada RU (Request Unit) do CosmosDB e pelo tráfego de saída (&lt;em&gt;Egress&lt;/em&gt;). Se a aplicação receber 50 milhões de acessos no day, a fatura estoura.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BEAM em Servidor Dedicado / VPS (Custo Fixo):&lt;/strong&gt; Um monólito distribuído (&lt;em&gt;Majestic Monolith&lt;/em&gt;) em Elixir roda em uma máquina virtual (VPS) de &lt;strong&gt;US$ 40/mês&lt;/strong&gt;. Processar 1 milhão ou 1 bilhão de mensagens internamente nessa máquina custará exatamente os mesmos &lt;strong&gt;US$ 40 no final do mês&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Evidências Empíricas da Indústria (Casos de Estudo)
&lt;/h2&gt;

&lt;p&gt;A superioridade teórica da BEAM é comprovada pelos casos históricos das maiores plataformas do planeta:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plataforma&lt;/th&gt;
&lt;th&gt;Arquitetura Adotada&lt;/th&gt;
&lt;th&gt;Resultado Métrico&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WhatsApp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Erlang c/ Atores por Usuário + Mnesia + Kernel FreeBSD ajustado (&lt;code&gt;kern.ipc.maxsockets = 2.4M&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&amp;gt; 2 Milhões conexões ativas por servidor&lt;/strong&gt;. Apenas 50 engenheiros para 2 bilhões de usuários.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bleacher Report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Migração de Ruby on Rails para Elixir / Phoenix&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Redução brutal de 150 instâncias AWS EC2 para apenas 5 servidores&lt;/strong&gt; nos picos de tráfego.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Elixir (WebSockets) + Rust NIFs (Rustler) substituindo Go no serviço &lt;em&gt;Read States&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Latência despencou de 27.000 µs (pausas de GC do Go) para 4 µs&lt;/strong&gt; com &amp;gt; 11M conexões.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  8.1 WhatsApp: 2 Milhões de Conexões por Servidor
&lt;/h3&gt;

&lt;p&gt;O WhatsApp sustentou mais de 2 bilhões de usuários ativos com um time de infraestrutura de apenas &lt;strong&gt;50 engenheiros&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Como?&lt;/strong&gt; Criando &lt;strong&gt;1 Ator Erlang por usuário conectado&lt;/strong&gt;. Com a pegada de 2 KB por processo, milhões de conexões permaneceram ativas na memória RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuning de Kernel:&lt;/strong&gt; Ajustaram o kernel FreeBSD (&lt;code&gt;kern.ipc.maxsockets = 2.400.000&lt;/code&gt; e &lt;code&gt;kern.maxfiles = 3.000.000&lt;/code&gt;), atingindo o recorde histórico de &lt;strong&gt;mais de 2 milhões de conexões TCP ativas em um único servidor bare-metal&lt;/strong&gt; com 85% de uso de CPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8.2 Bleacher Report: De 150 para 5 Servidores na AWS
&lt;/h3&gt;

&lt;p&gt;O Bleacher Report sofria com picos de acessos durante transmissões de eventos esportivos. A arquitetura original em Ruby on Rails não aguentava o tráfego devido ao bloqueio do GIL (Global Interpreter Lock).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ao migrar o sistema para &lt;strong&gt;Elixir e Phoenix&lt;/strong&gt;, a empresa reduziu a sua infraestrutura na AWS de &lt;strong&gt;150 instâncias EC2 para apenas 5 servidores&lt;/strong&gt;, economizando milhões de dólares.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8.3 Discord: De 27.000 µs para 4 µs de Latência (Go vs. Rust/Elixir)
&lt;/h3&gt;

&lt;p&gt;O Discord opera mais de 11 milhões de usuários simultâneos em WebSockets controlados pelo Elixir.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O serviço &lt;em&gt;Read States&lt;/em&gt; (que rastreia mensagens lidas) foi escrito originalmente em &lt;strong&gt;Go&lt;/strong&gt;. Porém, a cada 2 minutos (120s), o Garbage Collector global do Go acionava um &lt;em&gt;mark-and-sweep&lt;/em&gt;, gerando picos de latência de &lt;strong&gt;27.000 µs (27 ms)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A engenharia reescreveu o núcleo do serviço em &lt;strong&gt;Rust&lt;/strong&gt;, integrando-o ao &lt;strong&gt;Elixir via NIFs (Rustler)&lt;/strong&gt;. A latência caiu de &lt;strong&gt;27.000 µs para 4 µs&lt;/strong&gt;, sem abrir mão das Árvores de Supervisão do Elixir.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. Conclusão: Coincidência ou Sabedoria Esquecida?
&lt;/h2&gt;

&lt;p&gt;O padrão &lt;em&gt;Scheduler Agent Supervisor&lt;/em&gt; promovido pelos provedores de nuvem modernos &lt;strong&gt;não é uma mera coincidência&lt;/strong&gt; — é a prova inequívoca de que os desafios da computação distribuída são universais. A nuvem chegou às mesmas conclusões que Joe Armstrong e a equipe da Ericsson chegaram em 1986.&lt;/p&gt;

&lt;p&gt;A diferença fundamental reside no nível de abstração:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Nuvem&lt;/strong&gt; tenta resolver o problema colando peças de infraestrutura pela rede (filas, bancos de dados, lambdas, CRONs), pagando um imposto altíssimo em latência, complexidade operacional e custos financeiros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A BEAM&lt;/strong&gt; resolve o problema no &lt;strong&gt;nível da linguagem e do tempo de execução&lt;/strong&gt;, utilizando a memória RAM, processos verdes, contagem de reduções e supervision trees reativas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se a sua empresa opera microsserviços em nuvem e sofre com custos astronômicos de Serverless, latências de rede acumuladas ou complexidade excessiva de orquestração, talvez seja hora de parar de tentar recriar a BEAM com infraestrutura de nuvem... e simplesmente &lt;strong&gt;começar a usar a BEAM&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Vamos Continuar a Conversa?
&lt;/h3&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>erlang</category>
      <category>elixir</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A nuvem chegou às mesmas conclusões que Joe Armstrong e a equipe da Ericsson chegaram em 1986.</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Mon, 27 Jul 2026 17:06:58 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/a-nuvem-chegou-as-mesmas-conclusoes-que-joe-armstrong-e-a-equipe-da-ericsson-chegaram-em-1986-3hid</link>
      <guid>https://dev.to/matheuscamarques/a-nuvem-chegou-as-mesmas-conclusoes-que-joe-armstrong-e-a-equipe-da-ericsson-chegaram-em-1986-3hid</guid>
      <description>&lt;h1&gt;
  
  
  A nuvem chegou às mesmas conclusões que Joe Armstrong e a equipe da Ericsson chegaram em 1986.
&lt;/h1&gt;

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

&lt;p&gt;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 &lt;em&gt;Scheduler Agent Supervisor&lt;/em&gt;, &lt;em&gt;Sagas&lt;/em&gt;, &lt;em&gt;Circuit Breakers&lt;/em&gt; e &lt;em&gt;Dead-Letter Queues&lt;/em&gt;. Fornecedores de nuvem como Microsoft Azure e AWS investiram bilhões promovendo ferramentas gerenciadas para orquestrar falhas de rede e consistência eventual. &lt;/p&gt;

&lt;p&gt;No entanto, quando despimos essa infraestrutura da sua roupagem comercial, chegamos a uma constatação histórica fascinante: &lt;strong&gt;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.&lt;/strong&gt; 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. A Grande Convergência Arquitetural
&lt;/h2&gt;

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

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

&lt;p&gt;Esse arranjo é formalmente chamado de padrão &lt;strong&gt;Scheduler Agent Supervisor&lt;/strong&gt;.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"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 &lt;code&gt;GenServer&lt;/code&gt;, um &lt;code&gt;Supervisor&lt;/code&gt; e uma tabela &lt;code&gt;ETS&lt;/code&gt; fazem nativamente na memória da BEAM desde a década de 80?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;h2&gt;
  
  
  2. A História Se Repete: O Problema da Ericsson nos Anos 80
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Desafio dos Comutadores Telefônicos AXE
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  O Modelo de Atores (Carl Hewitt, 1973) na Prática
&lt;/h3&gt;

&lt;p&gt;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 &lt;strong&gt;Modelo de Atores&lt;/strong&gt; teorizado por Carl Hewitt no MIT em 1973.&lt;/p&gt;

&lt;p&gt;No Erlang:&lt;/p&gt;

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

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

&lt;h3&gt;
  
  
  A Virada Filosófica: "Let it Crash"
&lt;/h3&gt;

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

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

&lt;p&gt;Armstrong defendeu o oposto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"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."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Como os processos BEAM são totalmente isolados, o colapso de um ator não afeta a memória dos vizinhos. O &lt;strong&gt;Supervisor&lt;/strong&gt;, 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Anatomia da Reinvenção: O Mapeamento Peça por Peça
&lt;/h2&gt;

&lt;p&gt;Ao compararmos o padrão nativo da nuvem com as primitivas da BEAM, a equivalência é ponto por ponto:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  O Detetor de Anomalias: Polling em Rede vs. Sinalização Reativa
&lt;/h3&gt;

&lt;p&gt;Aqui reside uma das diferenças mais dramáticas entre a nuvem e a BEAM.&lt;/p&gt;

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

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




&lt;h2&gt;
  
  
  4. Por Baixo do Capô da BEAM: A Física da Memória e o Código C
&lt;/h2&gt;

&lt;p&gt;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 (&lt;code&gt;erl_process.h&lt;/code&gt;) gerencia a memória física de forma isolada.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 O Process Control Block (PCB)
&lt;/h3&gt;

&lt;p&gt;Cada ator Erlang é representado internamente pela estrutura C &lt;code&gt;process&lt;/code&gt; (ponteiro &lt;code&gt;c_p&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pegada de Memória:&lt;/strong&gt; A criação de um processo BEAM exige apenas &lt;strong&gt;327 a 338 palavras (words)&lt;/strong&gt; — aproximadamente &lt;strong&gt;2,5 a 3 KB&lt;/strong&gt; em sistemas de 64 bits. Em comparação, uma thread nativa de sistema operacional aloca entre &lt;strong&gt;512 KB e 8 MB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID de 28 bits:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registradores e JIT:&lt;/strong&gt; 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).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.2 Crescimento Bidirecional: Stack vs. Heap Contíguos
&lt;/h3&gt;

&lt;p&gt;A BEAM aloca um &lt;strong&gt;único bloco contíguo de memória privada&lt;/strong&gt; para cada processo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------+
|  Endereço Baixo                                       Endereço Alto   |
|  [ HEAP (Cresce para CIMA ^) ]  ---&amp;gt; &amp;lt;--- [ STACK (Cresce para BAIXO v) ]|
|  |--- htop ------------------------ stop (E) ------------------------|
+-----------------------------------------------------------------------+
                                   ^
                           Ponto de Colisão
                        (Dispara o GC Isolado)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;strong&gt;Heap&lt;/strong&gt; cresce para cima (armazenando listas, tuplas e floats). O &lt;strong&gt;Stack&lt;/strong&gt; cresce para baixo (armazenando o fluxo do programa e quadros de funções).&lt;/p&gt;

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

&lt;h3&gt;
  
  
  4.3 Zero Stop-The-World: O GC de Cheney Per-Processo
&lt;/h3&gt;

&lt;p&gt;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 (&lt;em&gt;Stop-The-World pause&lt;/em&gt;).&lt;/p&gt;

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

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

&lt;h3&gt;
  
  
  4.4 Refc Binaries e a Lista MSO List
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;Binários curtos ($\le 64$ bytes) ficam no heap privado do processo (&lt;em&gt;Heap Binary&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Binários longos ($&amp;gt; 64$ bytes) ficam no &lt;strong&gt;Binary Heap Global&lt;/strong&gt; (&lt;em&gt;Refc Binary&lt;/em&gt;), gerenciados por contagem de referências.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  4.5 Preempção Cooperativa: O Orçamento de 2000 Reduções
&lt;/h3&gt;

&lt;p&gt;Em &lt;code&gt;beam_emu.c&lt;/code&gt;, o loop principal do emulador (&lt;code&gt;process_main&lt;/code&gt;) executa os processos atribuindo a cada um um orçamento fixo de &lt;strong&gt;2000 Reduções&lt;/strong&gt; (&lt;em&gt;time slice budget&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;Yield Point&lt;/em&gt;: salvaguarda seus registradores no PCB e cede o lugar voluntariamente na &lt;em&gt;Run Queue&lt;/em&gt;.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  5. O Modelo Cognitivo Bio-Inspirado: Memória &amp;amp; Homeostase
&lt;/h2&gt;

&lt;p&gt;A arquitetura da BEAM e os padrões de resiliência distribuída podem ser interpretados através dos princípios da &lt;strong&gt;Neurociência Cognitiva&lt;/strong&gt;:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  6. A Matemática da Nuvem vs. A Matemática da BEAM
&lt;/h2&gt;

&lt;p&gt;Quando colocamos as duas abordagens na ponta do lápis, a diferença de eficiência é assustadora:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Latência (Tempo)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuvem (Pulos de Rede):&lt;/strong&gt; 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 &lt;strong&gt;0,5 ms e 1 ms&lt;/strong&gt;. Uma etapa completa consome de &lt;strong&gt;2 ms a 50 ms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BEAM (Memória RAM):&lt;/strong&gt; A troca de mensagens entre atores ocorre por cópia de ponteiros na própria RAM do servidor. A latência é de &lt;strong&gt;1 µs&lt;/strong&gt; — 1000 vezes mais rápida que a nuvem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Densidade Computacional (Memória)
&lt;/h3&gt;

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

&lt;h3&gt;
  
  
  3. Custo Financeiro (Dinheiro)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuvem Serverless:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BEAM em VPS / Dedicado:&lt;/strong&gt; Monólito distribuído em Elixir rodando em uma instância VPS de &lt;strong&gt;US$ 40/mês&lt;/strong&gt;. O custo permanece rigorosamente os mesmos &lt;strong&gt;US$ 40/mês&lt;/strong&gt;, quer o sistema processe 1 milhão ou 1 bilhão de mensagens.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Evidências Empíricas da Indústria
&lt;/h2&gt;

&lt;p&gt;Três casos emblemáticos da história recente do software comprovam o impacto prático dessa arquitetura:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plataforma&lt;/th&gt;
&lt;th&gt;Arquitetura Adotada&lt;/th&gt;
&lt;th&gt;Resultado Métrico&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WhatsApp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Erlang c/ Atores por Usuário + Mnesia + Kernel FreeBSD ajustado (&lt;code&gt;kern.ipc.maxsockets = 2.4M&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&amp;gt; 2 Milhões conexões ativas por servidor&lt;/strong&gt;. Apenas 50 engenheiros para 2 bilhões de usuários.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bleacher Report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Migração de Ruby on Rails para Elixir / Phoenix&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Redução brutal de 150 instâncias AWS EC2 para apenas 5 servidores&lt;/strong&gt; nos picos de tráfego.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discord&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Elixir (WebSockets) + Rust NIFs (Rustler) substituindo Go no serviço &lt;em&gt;Read States&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Latência despencou de 27.000 µs (pausas de GC do Go) para 4 µs&lt;/strong&gt; com &amp;gt; 11M conexões.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;WhatsApp:&lt;/strong&gt; Com um ator Erlang dedicado a cada conexão de usuário (pegada de 2 KB) e pequenos ajustes de sysctl no FreeBSD (&lt;code&gt;kern.ipc.maxsockets = 2.400.000&lt;/code&gt;), a empresa manteve &lt;strong&gt;mais de 2 milhões de conexões TCP ativas por servidor bare-metal&lt;/strong&gt;. Apenas 50 engenheiros mantinham a infraestrutura para 2 bilhões de usuários.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bleacher Report:&lt;/strong&gt; O portal de esportes encolheu sua frota de servidores na AWS de &lt;strong&gt;150 instâncias EC2 (Ruby on Rails) para apenas 5 servidores&lt;/strong&gt; ao migrar para Elixir e Phoenix, eliminando dependências externas pesadas de cache e mensageria.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discord:&lt;/strong&gt; 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 &lt;strong&gt;27.000 µs&lt;/strong&gt; a cada 2 minutos no serviço &lt;em&gt;Read States&lt;/em&gt;) por uma NIF em Rust conectada ao Elixir (Rustler), a latência despencou para &lt;strong&gt;4 µs&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  8. Conclusão: O Futuro Pertence a Quem Entende a História
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;Joe Armstrong e a equipe da Ericsson consolidaram em 1986&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Para os arquitetos e engenheiros de software modernos, a lição é clara:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se você está projetando um sistema concorrente e de alta disponibilidade, &lt;strong&gt;não precisa remontar a BEAM com dezenas de microsserviços e filas&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Vamos Continuar a Conversa?
&lt;/h3&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>architecture</category>
      <category>erlang</category>
      <category>elixir</category>
    </item>
    <item>
      <title>A Filosofia Oculta das Ferramentas (Agente ou a gente ?)</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Wed, 22 Jul 2026 19:23:38 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/a-filosofia-oculta-das-ferramentas-agente-ou-a-gente--52i7</link>
      <guid>https://dev.to/matheuscamarques/a-filosofia-oculta-das-ferramentas-agente-ou-a-gente--52i7</guid>
      <description>&lt;p&gt;Olá, pessoal! Espero que estejam tendo um ótimo dia. Me chamo Matheus de Camargo Marques e o texto a seguir deriva de alguns devaneios que costumo ter durante meus estudos. Costumo transitar por temas que vão desde a matemática para machine learning até a filosofia de Sêneca, e meu olhar para as ferramentas raramente se detém na superfície do 'o que é'. Meu foco é sempre investigar qual é a carga conceitual que aquela ferramenta carrega por debaixo do capô.&lt;/p&gt;

&lt;p&gt;Confesso: a vida seria muito mais tranquila se eu simplesmente aceitasse as coisas como elas são. Essa busca incessante para compreender a fundo os conceitos sobrecarrega qualquer um. Mas, para mim, ela traz uma recompensa inestimável que chamo de 'alívio cognitivo'. Enquanto eu não destrincho a verdadeira natureza de um assunto, minha mente simplesmente não desliga. Por mais que essa intensidade se aproxime muito das características de Altas Habilidades e Superdotação (AH/SD), ela frequentemente cobra seu preço como um castigo. Tem dias em que eu só queria ficar de boa, sem processar 1 bilhão de hipóteses sobre o efeito em cascata que uma única decisão arquitetural errada pode causar em um sistema complexo.&lt;/p&gt;

&lt;p&gt;Mas, como não dá para desligar a mente, a gente transforma isso em artigo. Agora que vocês me conhecem um pouco melhor, vamos à lore.&lt;/p&gt;

&lt;p&gt;Como ainda não inventaram um comando para dar um kill -9 nos meus próprios pensamentos, e meu cérebro não roda com uma árvore de supervisão para me reiniciar num estado saudável quando a ansiedade bate, o único jeito de não esgotar meus próprios recursos computacionais à toa é documentar esse caos. Então, peguem o café, ajeitem a postura e preparem-se: se eu vou perder noites de sono calculando o custo de um autovalor no espaço vetorial, vocês vão ter que sofrer refletindo sobre isso comigo. Prometo que, no final, a matemática perdoa e a conta fecha.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Filosofia Oculta das Ferramentas
&lt;/h3&gt;

&lt;p&gt;Para os olhos de quem não o conhece, o gradiente descendente é apenas um emaranhado de derivadas parciais e taxas de aprendizado. Mas, em sua essência mais pura, ele revela um conceito filosófico profundo: a busca humilde por um estado de menor atrito. É a matemática da adaptação contínua, provando que a aproximação da verdade (o mínimo local) não se dá por um salto de genialidade, mas pela capacidade de medir o próprio erro e ajustar a direção no passo seguinte.&lt;/p&gt;

&lt;p&gt;Quando olhamos para a arquitetura de sistemas sob essa ótica, percebemos que nenhuma ferramenta é apenas técnica. Cada escolha arquitetural carrega uma visão de mundo. E ao projetar sistemas multiagentes, compreender essa visão é o que nos permite &lt;code&gt;priorizar técnicas que maximizem a qualidade em detrimento da mera quantidade&lt;/code&gt;. Quando se estabelecem métricas claras e uma prática de entrega contínua, o verdadeiro potencial desses agentes em projetos complexos é desbloqueado.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Geometria do Significado
&lt;/h3&gt;

&lt;p&gt;Um vetor em um espaço multidimensional não é um mero array de números de ponto flutuante. Filosoficamente, é a tentativa de dar coordenadas espaciais à abstração humana. Ao traduzir contexto para álgebra, definimos que a semântica possui gravidade e proximidade. Multiplicar esses vetores em um sistema de agentes é navegar pela geometria das ideias.&lt;/p&gt;

&lt;p&gt;Em seu nível mais fundamental, agentes operam como multiplicadores de vetores e é aqui que a álgebra linear nos ensina uma lição valiosa. A magnitude de um vetor é inútil se a direção estiver errada; tampouco importa a força do multiplicador (o autovalor) se o vetor base for nulo.&lt;/p&gt;

&lt;p&gt;Trazendo para a engenharia: um desenvolvedor pode aplicar uma força imensa, mas, sem o alinhamento correto, apenas se afastará do sentido e do objetivo com mais rapidez. Em contrapartida, um processo sem base sólida (o vetor nulo) esgotará os recursos computacionais à toa, pois nenhum fator multiplicador consegue escalar o vazio.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Resiliência do Isolamento
&lt;/h3&gt;

&lt;p&gt;Quando modelamos sistemas baseados em múltiplos agentes ou atores independentes que se comunicam por troca de mensagens e não compartilham estado, não estamos apenas lidando com concorrência. Estamos adotando a filosofia de que o mundo real é assíncrono, distribuído e inerentemente caótico.&lt;/p&gt;

&lt;p&gt;Aceitamos que falhas locais são inevitáveis e que a verdadeira robustez (a tolerância a falhas) não nasce da tentativa fútil de prever e interceptar tudo. Ela nasce de entidades autônomas que sabem cair, se recuperar e continuar operando sem derrubar o ecossistema inteiro.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Rigor da Especificação e o Custo da Iteração
&lt;/h3&gt;

&lt;p&gt;Aprofundando a analogia do gradiente descendente na orquestração desses agentes, temos o ciclo matemático de otimização: tentativa, cálculo do erro, ajuste da direção e melhoria iterativa. No entanto, aplicar esse fluxo de "corrige e melhora" na prática tem um custo operacional e computacional consideravelmente alto.&lt;/p&gt;

&lt;p&gt;É por isso que a prática de projetar e documentar antes de codificar transcende a gerência de projetos. É a compreensão de que, em um universo de possibilidades matemáticas infinitas e agentes com alto poder de alavancagem, a entropia é o estado natural. Adotar uma verdadeira especificação é um ato de rebelião contra o caos, é definir a topologia do terreno antes de soltar o algoritmo para explorá-lo.&lt;/p&gt;

&lt;p&gt;Sem uma especificação rigorosa e métricas predefinidas, tentar encontrar a solução ideal apenas iterando cegamente transforma a inteligência do sistema em um dreno de recursos. Sem isso, iterar é apenas se perder no espaço vetorial com extrema eficiência.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Fator Multiplicador Humano
&lt;/h3&gt;

&lt;p&gt;Ferramentas, algoritmos e linguagens são apenas a manifestação sintática dessas ideias. O verdadeiro trabalho da engenharia não está em escrever a expressão matemática ou o código de integração, mas em compreender a natureza do problema a ponto de escolher a filosofia certa para resolvê-lo.&lt;/p&gt;

&lt;p&gt;Em suma, lidar com agentes é lidar com alavancagem extrema. Se você alavanca um processo caótico, você apenas obtém o caos mais rápido e com uma conta muito mais cara no final. A verdadeira maestria na engenharia de sistemas multiagentes não está em deixar os modelos descobrirem o caminho sozinhos, mas em fornecer o rigor matemático e arquitetural para que eles tenham um vetor sólido de onde partir.&lt;/p&gt;

&lt;p&gt;No fim do dia, a qualidade do autovalor gerado pela IA sempre será limitada pela qualidade da direção definida pelo humano.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>elixir</category>
      <category>startup</category>
    </item>
    <item>
      <title>Bibliography — PON + Smart Brewery dev.to series (EN drafts)</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:28:41 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9</link>
      <guid>https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9</guid>
      <description>&lt;h2&gt;
  
  
  Bibliography — PON + Smart Brewery dev.to series (EN drafts)
&lt;/h2&gt;

&lt;p&gt;Normalized references used across &lt;a href="https://dev.toen/"&gt;&lt;code&gt;docs/devto/en/&lt;/code&gt;&lt;/a&gt;. When publishing on dev.to, prefer stable URLs; verify DOIs and publisher pages periodically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paradigms and architecture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simão, J. M.; Borges, M. R.; Ebina, R.; Tacla, C. A.; Stadzisz, P. C.; Banaszewski, R. F.&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Notification Oriented Paradigm (NOP) and Imperative Paradigm: A Comparative Study&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.scirp.org/journal/paperinformation?paperid=19842" rel="noopener noreferrer"&gt;SCIRP / IJSEA&lt;/a&gt; — academic treatment of NOP (closely related to “notification-oriented” rule structuring; this repo uses &lt;strong&gt;PON&lt;/strong&gt; as shorthand).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cockburn, A.&lt;/td&gt;
&lt;td&gt;2005&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Hexagonal architecture&lt;/em&gt; (ports and adapters)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://alistair.cockburn.us/hexagonal-architecture/" rel="noopener noreferrer"&gt;alistair.cockburn.us&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fowler, M.&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Ports and Adapters / Hexagonal architecture&lt;/em&gt; (summary)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://martinfowler.com/bliki/HexagonalArchitecture.html" rel="noopener noreferrer"&gt;martinfowler.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Erlang, Elixir, OTP
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Armstrong, J.&lt;/td&gt;
&lt;td&gt;2003&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Making reliable distributed systems in the presence of software errors&lt;/em&gt; (PhD thesis)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.erlang.org/download/armstrong_thesis_2003.pdf" rel="noopener noreferrer"&gt;erlang.org&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cesarini, F.; Thompson, S.&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Programming Erlang (2nd ed.)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Pragmatic Bookshelf — OTP design, processes, supervision.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Elixir documentation&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GenServer&lt;/code&gt;, &lt;code&gt;Registry&lt;/code&gt;, &lt;code&gt;Supervisor&lt;/code&gt;, &lt;code&gt;Macro&lt;/code&gt;, &lt;code&gt;@behaviour&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hexdocs.pm/elixir/" rel="noopener noreferrer"&gt;hexdocs.pm/elixir&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Mix profiling tasks&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mix profile.cprof&lt;/code&gt;, &lt;code&gt;eprof&lt;/code&gt;, &lt;code&gt;fprof&lt;/code&gt;, &lt;code&gt;tprof&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html" rel="noopener noreferrer"&gt;hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html&lt;/a&gt; and sibling task modules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Phoenix ecosystem
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Phoenix LiveView&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Guides and API&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hexdocs.pm/phoenix_live_view/" rel="noopener noreferrer"&gt;hexdocs.pm/phoenix_live_view&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Phoenix PubSub&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hexdocs.pm/phoenix_pubsub/" rel="noopener noreferrer"&gt;hexdocs.pm/phoenix_pubsub&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Data pipelines and observability
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Broadway&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hexdocs.pm/broadway/" rel="noopener noreferrer"&gt;hexdocs.pm/broadway&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;GenStage&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hexdocs.pm/gen_stage/" rel="noopener noreferrer"&gt;hexdocs.pm/gen_stage&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Telemetry&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;telemetry&lt;/code&gt; for Erlang/Elixir&lt;/td&gt;
&lt;td&gt;&lt;a href="https://hexdocs.pm/telemetry/" rel="noopener noreferrer"&gt;hexdocs.pm/telemetry&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timescale, Inc.&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;TimescaleDB documentation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.timescale.com/" rel="noopener noreferrer"&gt;docs.timescale.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Analytics and BI
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kimball, R.; Ross, M.&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;The Data Warehouse Toolkit (3rd ed.)&lt;/em&gt; — dimensional modeling&lt;/td&gt;
&lt;td&gt;Wiley — star schema, facts/dimensions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Power BI REST APIs (push datasets, etc.)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://learn.microsoft.com/en-us/rest/api/power-bi/" rel="noopener noreferrer"&gt;Microsoft Learn — Power BI REST&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL Global Development Group&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GRANT&lt;/code&gt;, roles, row security&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.postgresql.org/docs/current/ddl-rowsecurity.html" rel="noopener noreferrer"&gt;postgresql.org/docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Digital twins and industrial context
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Grieves, M.&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Digital Twin: Manufacturing Excellence Through Virtual Factory Replication&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Often cited white paper on digital-twin vocabulary (search publisher copy; ISBN/institutional PDFs vary).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Machine learning lifecycle
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Breck, E.; et al.&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;&lt;em&gt;The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reduction&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://research.google/pubs/pub46555/" rel="noopener noreferrer"&gt;research.google&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Elixir Numerics&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Axon, Scholar (Neural Network / traditional ML in Elixir)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://hexdocs.pm/axon/" rel="noopener noreferrer"&gt;hexdocs.pm/axon&lt;/a&gt;, &lt;a href="https://hexdocs.pm/scholar/" rel="noopener noreferrer"&gt;hexdocs.pm/scholar&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Queues, performance, back-pressure
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Little, J. D. C.&lt;/td&gt;
&lt;td&gt;1961&lt;/td&gt;
&lt;td&gt;&lt;em&gt;A Proof for the Queuing Formula L = λW&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Operations Research&lt;/em&gt; — foundation for &lt;strong&gt;Little’s Law&lt;/strong&gt; (relation of queue length, arrival rate, wait); &lt;a href="https://en.wikipedia.org/wiki/Little%27s_law" rel="noopener noreferrer"&gt;Wikipedia summary&lt;/a&gt; with citation to original.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Erlang docs&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;:erlang.process_info/2&lt;/code&gt; (mailbox size, etc.)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.erlang.org/doc/man/erlang.html" rel="noopener noreferrer"&gt;erlang.org/doc/man/erlang.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  This repository (deep dives in Portuguese or internal)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance, profilers, env matrix&lt;/td&gt;
&lt;td&gt;
&lt;a href="//../performance-dev.md"&gt;&lt;code&gt;docs/performance-dev.md&lt;/code&gt;&lt;/a&gt; — EN series walkthrough: &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;Part 11 on dev.to — Dev profiling…&lt;/a&gt; · &lt;a href="//en/11_dev_profiling_cpu_memory_optimizations.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory pressure heuristics&lt;/td&gt;
&lt;td&gt;&lt;a href="//../memory-pressure-heuristics.md"&gt;&lt;code&gt;docs/memory-pressure-heuristics.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message storm mitigation&lt;/td&gt;
&lt;td&gt;PT: &lt;a href="//../artigos/19_mitigacao_message_storm_pon_elixir_smart_brewery.md"&gt;&lt;code&gt;docs/artigos/19_mitigacao_message_storm_pon_elixir_smart_brewery.md&lt;/code&gt;&lt;/a&gt; — EN series: &lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;Part 10 on dev.to — When notifications explode…&lt;/a&gt; · &lt;a href="//en/10_when_notifications_explode_message_storms_pon.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML practical guide (PT)&lt;/td&gt;
&lt;td&gt;&lt;a href="//../artigos/27_guia_pratico_treino_ml_smart_brewery.md"&gt;&lt;code&gt;docs/artigos/27_guia_pratico_treino_ml_smart_brewery.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power BI / realtime notes&lt;/td&gt;
&lt;td&gt;&lt;a href="//../power-bi-realtime.md"&gt;&lt;code&gt;docs/power-bi-realtime.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>architecture</category>
      <category>computerscience</category>
      <category>resources</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Retrospective: lessons from building a reactive rules engine in Elixir</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:26:49 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/retrospective-lessons-from-building-a-reactive-rules-engine-in-elixir-1hcb</link>
      <guid>https://dev.to/matheuscamarques/retrospective-lessons-from-building-a-reactive-rules-engine-in-elixir-1hcb</guid>
      <description>&lt;p&gt;&lt;em&gt;If this helped you, you can &lt;a href="https://dev.to/matheuscamarques/support-with-a-coffee-2oa0"&gt;support the author with a coffee on dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Retrospective: lessons from building a reactive rules engine in Elixir
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 12 of 12&lt;/strong&gt; — This post closes the arc that started with &lt;strong&gt;why&lt;/strong&gt; the BEAM fits reactive rules and ends with &lt;strong&gt;how&lt;/strong&gt; we measure them under load. &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;Part 11 on dev.to — Dev profiling: CPU, memory, and what changed after optimizations&lt;/a&gt; · &lt;a href="//11_dev_profiling_cpu_memory_optimizations.md"&gt;repo draft&lt;/a&gt; was about profilers and reproducible workloads; here I zoom out: &lt;strong&gt;integrations that paid off&lt;/strong&gt;, &lt;strong&gt;costs we accepted&lt;/strong&gt;, and &lt;strong&gt;what I would try next&lt;/strong&gt; if I were green-fielding tomorrow.&lt;/p&gt;

&lt;p&gt;This is not a substitute for the earlier technical posts—treat it as a &lt;strong&gt;map&lt;/strong&gt; and a &lt;strong&gt;checklist&lt;/strong&gt; for your own PON-style systems. Each part now ends with a &lt;strong&gt;References and further reading&lt;/strong&gt; section; the consolidated list lives in &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the twelve parts built
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stretch&lt;/th&gt;
&lt;th&gt;Idea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1–2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notifications as the organizing principle; &lt;strong&gt;Facts&lt;/strong&gt;, &lt;strong&gt;Rules&lt;/strong&gt;, and &lt;strong&gt;Premises&lt;/strong&gt; as OTP processes and &lt;code&gt;Registry&lt;/code&gt; topics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3–4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Less boilerplate via &lt;strong&gt;&lt;code&gt;defrule&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;defpremissa&lt;/code&gt;&lt;/strong&gt;; &lt;strong&gt;ports and adapters&lt;/strong&gt; so rules do not hard-code IO.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5–6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Smart Brewery&lt;/strong&gt; as a serious lab; &lt;strong&gt;LiveView&lt;/strong&gt; as the operator’s window with batching and streams.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7–8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Broadway / GenStage&lt;/strong&gt;, &lt;strong&gt;TimescaleDB&lt;/strong&gt;, star-schema &lt;strong&gt;BI&lt;/strong&gt; and safe read-only roles.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ML export / import&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;ml_predictions&lt;/code&gt;&lt;/strong&gt; without blocking the engine.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10–11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;&lt;strong&gt;Part 10 on dev.to&lt;/strong&gt;&lt;/a&gt; — &lt;strong&gt;message storms&lt;/strong&gt;, dedup and fan-out coalescence; &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;&lt;strong&gt;Part 11 on dev.to&lt;/strong&gt;&lt;/a&gt; — &lt;strong&gt;CPU/memory profiling&lt;/strong&gt; with &lt;code&gt;PipelineWorkload&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together they describe one opinionated path: &lt;strong&gt;reactive core&lt;/strong&gt;, &lt;strong&gt;async edges&lt;/strong&gt;, &lt;strong&gt;honest measurement&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What worked well
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Process boundaries match mental boundaries.&lt;/strong&gt; A &lt;code&gt;Fato&lt;/code&gt; is a named mailbox with a value; a &lt;code&gt;Regra&lt;/code&gt; subscribes and reacts. That maps cleanly to drawings on a whiteboard and to Elixir supervision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit message shapes.&lt;/strong&gt; Consumers understand &lt;code&gt;{:notificacao, name, value}&lt;/code&gt; and &lt;code&gt;{:notificacoes_lote, map}&lt;/code&gt;—whether they live in &lt;code&gt;tec0301_pon&lt;/code&gt; or in the Phoenix app. Ambiguity is expensive at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two applications, one story.&lt;/strong&gt; Keeping &lt;strong&gt;&lt;code&gt;tec0301_pon&lt;/code&gt;&lt;/strong&gt; as the engine and &lt;strong&gt;&lt;code&gt;simulacoes_visuais&lt;/code&gt;&lt;/strong&gt; as warehouse + UI avoided turning the rule engine into an Ecto or HTTP library, while still shipping a full twin demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batching at the pain points.&lt;/strong&gt; LiveView batchers, Broadway batchers, &lt;code&gt;Fanout.atualizar_lote/1&lt;/code&gt;, and async DB writers share the same philosophy: &lt;strong&gt;coalesce before fan-out or disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telemetry as a spine, not an afterthought.&lt;/strong&gt; From rule firings to Broadway flushes, the codebase repeatedly uses &lt;code&gt;:telemetry.execute/3&lt;/code&gt; and named events so you can correlate “the twin moved” with “the UI updated” and “rows landed in TimescaleDB.” That does not replace profilers, but it &lt;strong&gt;anchors&lt;/strong&gt; them: when CPU spikes, you want a span or counter that says &lt;em&gt;which&lt;/em&gt; stage grew.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trade-offs we accepted
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DSL complexity.&lt;/strong&gt; Macros that generate rule modules are powerful and testable, but they are a onboarding cliff. Teams that fear metaprogramming will push for YAML or database rules—each has its own cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simulation vs. fidelity vs. CPU.&lt;/strong&gt; Monte Carlo noise, strict &lt;code&gt;===&lt;/code&gt; deduplication, and float quantization are linked: quieter logs mean less work, but also less “physical” continuity unless you design for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pragmatic coupling in examples.&lt;/strong&gt; Rules that call &lt;code&gt;Adapters.PredioIO&lt;/code&gt; directly are easy to read; “production-shaped” indirection via &lt;code&gt;Application.get_env/3&lt;/code&gt; is more wiring. The series kept both stories visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operational surface.&lt;/strong&gt; TimescaleDB, CAGGs, retention, Power BI roles, and ML export paths are &lt;strong&gt;optional&lt;/strong&gt; but real: the default &lt;code&gt;mix phx.server&lt;/code&gt; story is heavier than a pure library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;English drafts, Portuguese publication layer.&lt;/strong&gt; Keeping &lt;code&gt;docs/devto/en/*.md&lt;/code&gt; as the paste-ready source while &lt;code&gt;devto_serie_pon_smart_brewery.md&lt;/code&gt; tracks PT titles and dev.to slugs adds a bit of bookkeeping. The upside is a single technical narrative in one language and localized packaging when you hit publish.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would try earlier next time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Profiling harness from week one&lt;/strong&gt; — &lt;code&gt;PipelineWorkload&lt;/code&gt;-style reproducible ticks save arguments about regressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One canonical env matrix&lt;/strong&gt; — document &lt;code&gt;SIMULACOES_TSDB_ENABLED&lt;/code&gt;, Monte Carlo interval, and Broadway batch sizes in a single table (the repo now spreads this across &lt;code&gt;performance-dev.md&lt;/code&gt; and config; still easy to drift).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable join keys&lt;/strong&gt; — align persisted &lt;code&gt;rule_events.regra_id&lt;/code&gt; with dimension tables (&lt;code&gt;r_01&lt;/code&gt; vs &lt;code&gt;"1"&lt;/code&gt;) in one migration or view, so BI and ML exports do not need ad hoc bridges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stricter “no work if unchanged” policy&lt;/strong&gt; — extend the dedup story to any hot path that allocates (e.g. telemetry encode) once profiling proves it matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;hypotheses&lt;/strong&gt;, not promises—verify on your workload.&lt;/p&gt;




&lt;h2&gt;
  
  
  Code spine (the same system, twelve lenses)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Part 2 / 10 — fact update and dispatch only when the value changes:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tec0301_pon/pon/fato.ex (concept)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:atualizar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valor_igual?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;# … update state, ETS, Registry.dispatch → {:notificacao, nome, valor}&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_estado&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Part 3 — DSL-shaped rule:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;defrule&lt;/span&gt; &lt;span class="no"&gt;RegraIrrigacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;watch:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:temp_ambiente&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:umidade_solo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:estado_bomba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:nivel_tanque&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ow"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:temp_ambiente&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:umidade_solo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BombaDeAgua&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ligar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="no"&gt;Tec0301Pon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Fato&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atualizar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:estado_bomba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ligada&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Part 4 — port = behaviour:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Tec0301Pon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ports&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PredioAtuadores&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@callback&lt;/span&gt; &lt;span class="n"&gt;ligar_luz&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
  &lt;span class="nv"&gt;@callback&lt;/span&gt; &lt;span class="n"&gt;trancar_porta&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Part 6 — operator route:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/smart-brewery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SmartBreweryLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
&lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/smart-brewery/ml-predictions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MlPredictionsLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Part 7 — Broadway flush to PubSub and optional TSDB:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:simulacoes_visuais&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tsdb_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TelemetryAsyncWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Part 9 — export and import (shell):&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix export.ml &lt;span class="nt"&gt;--out&lt;/span&gt; /tmp/ml_export &lt;span class="nt"&gt;--since-hours&lt;/span&gt; 168
mix import.ml.predictions &lt;span class="nt"&gt;--file&lt;/span&gt; /tmp/preds.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;[&lt;/em&gt;&lt;em&gt;Part 11 on dev.to&lt;/em&gt;&lt;em&gt;](&lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb&lt;/a&gt;) — reproducible load:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix profile.cprof &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SimulacoesVisuais.Profile.PipelineWorkload.run()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  End-to-end picture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  subgraph engine [tec0301_pon]
    F[Fato]
    R[Regra]
    A[Adapters]
  end
  subgraph app [simulacoes_visuais]
    MC[MonteCarlo]
    BW[Broadway]
    LV[LiveView]
    TSDB[(TimescaleDB)]
  end
  MC --&amp;gt; F
  F --&amp;gt; R
  R --&amp;gt; A
  F --&amp;gt; BW
  BW --&amp;gt; TSDB
  BW --&amp;gt; LV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Series index (parts 1–12 + consolidated bibliography)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Draft / published&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Notification-Oriented Paradigm (PON) in Elixir: why the BEAM fits reactive rules&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/notification-oriented-paradigm-pon-in-elixir-why-the-beam-fits-reactive-rules-2p9e"&gt;dev.to&lt;/a&gt; · &lt;a href="//01_pon_in_elixir_why_beam.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;From whiteboard to code: mapping Facts, Rules, and Premises to OTP processes&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/from-whiteboard-to-code-mapping-facts-rules-and-premises-to-otp-processes-1blb"&gt;dev.to&lt;/a&gt; · &lt;a href="//02_from_whiteboard_to_code_otp.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;A metaprogrammed DSL: &lt;code&gt;defrule&lt;/code&gt; and &lt;code&gt;defpremissa&lt;/code&gt; with less PON boilerplate&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/a-metaprogrammed-dsl-defrule-and-defpremissa-with-less-pon-boilerplate-3909"&gt;dev.to&lt;/a&gt; · &lt;a href="//03_metaprogrammed_dsl_defrule_defpremissa.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Hexagonal architecture + PON: Ports &amp;amp; Adapters to decouple the engine&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/hexagonal-architecture-pon-ports-adapters-to-decouple-the-engine-3l54"&gt;dev.to&lt;/a&gt; · &lt;a href="//04_hexagonal_pon_ports_adapters.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Smart Brewery: a digital twin brewery as a PON lab&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/smart-brewery-a-digital-twin-brewery-as-a-pon-lab-36mf"&gt;dev.to&lt;/a&gt; · &lt;a href="//05_smart_brewery_digital_twin_pon_lab.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Phoenix LiveView in real time: an operations UI on top of a rules engine&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;dev.to&lt;/a&gt; · &lt;a href="//06_phoenix_liveview_operations_ui_rules_engine.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;From simulation to storage: telemetry, Broadway/GenStage, and TimescaleDB&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;dev.to&lt;/a&gt; · &lt;a href="//07_from_simulation_to_storage_telemetry_broadway_timescaledb.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;dev.to&lt;/a&gt; · &lt;a href="//08_bi_without_mystery_dimensions_facts_power_bi.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;ML on the digital twin: export, train pilots, and import predictions back into the app&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;dev.to&lt;/a&gt; · &lt;a href="//09_ml_digital_twin_export_train_import_predictions.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;When notifications explode: message storms, deduplication, and back-pressure in PON&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;dev.to&lt;/a&gt; · &lt;a href="//10_when_notifications_explode_message_storms_pon.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Dev profiling: CPU, memory, and what changed after optimizations&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;dev.to&lt;/a&gt; · &lt;a href="//11_dev_profiling_cpu_memory_optimizations.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Retrospective: lessons from building a reactive rules engine in Elixir&lt;/td&gt;
&lt;td&gt;&lt;em&gt;this post&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Bibliography — PON + Smart Brewery dev.to series (EN drafts)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;dev.to&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Portuguese titles and publication notes for dev.to live in &lt;a href="//../../devto_serie_pon_smart_brewery.md"&gt;&lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading (series-level)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Master bibliography&lt;/strong&gt; — normalized table of books, papers, and docs used across Parts 1–12: &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NOP / PON&lt;/strong&gt; — Simão et al. (2013) comparative study — &lt;a href="https://www.scirp.org/journal/paperinformation?paperid=19842" rel="noopener noreferrer"&gt;SCIRP&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP / BEAM&lt;/strong&gt; — Armstrong (2003) thesis — &lt;a href="https://www.erlang.org/download/armstrong_thesis_2003.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;; Cesarini &amp;amp; Thompson, &lt;em&gt;Programming Erlang&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hexagonal&lt;/strong&gt; — Cockburn — &lt;a href="https://alistair.cockburn.us/hexagonal-architecture/" rel="noopener noreferrer"&gt;ports and adapters&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data + time-series&lt;/strong&gt; — Kimball &amp;amp; Ross (dimensional modeling); TimescaleDB — &lt;a href="https://docs.timescale.com/" rel="noopener noreferrer"&gt;docs.timescale.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML readiness&lt;/strong&gt; — Breck et al., &lt;em&gt;ML Test Score&lt;/em&gt; — &lt;a href="https://research.google/pubs/pub46555/" rel="noopener noreferrer"&gt;Google Research&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Thank you
&lt;/h2&gt;

&lt;p&gt;If you followed from &lt;a href="https://dev.to/matheuscamarques/notification-oriented-paradigm-pon-in-elixir-why-the-beam-fits-reactive-rules-2p9e"&gt;Part 1 on dev.to&lt;/a&gt;: you have seen &lt;strong&gt;one&lt;/strong&gt; way to marry reactive rules, OTP, and industrial-style twins—not the only way. Take the &lt;strong&gt;patterns&lt;/strong&gt; (boundaries, batching, measurement) and adapt the &lt;strong&gt;machinery&lt;/strong&gt; to your domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End of series.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;Part 11 on dev.to — Dev profiling: CPU, memory, and what changed after optimizations&lt;/a&gt; · &lt;a href="//11_dev_profiling_cpu_memory_optimizations.md"&gt;repo draft&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code map (monorepo root):&lt;/strong&gt; &lt;code&gt;lib/tec0301_pon/&lt;/code&gt;, &lt;code&gt;apps/simulacoes_visuais/&lt;/code&gt;, &lt;code&gt;docs/performance-dev.md&lt;/code&gt;, &lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>architecture</category>
      <category>timescaledb</category>
      <category>liveview</category>
    </item>
    <item>
      <title>Dev profiling: CPU, memory, and what changed after optimizations</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:22:45 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb</link>
      <guid>https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb</guid>
      <description>&lt;p&gt;&lt;em&gt;If this helped you, you can &lt;a href="https://dev.to/matheuscamarques/support-with-a-coffee-2oa0"&gt;support the author with a coffee on dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Dev profiling: CPU, memory, and what changed after optimizations
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 11 of 12&lt;/strong&gt; — &lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;Part 10 on dev.to — When notifications explode: message storms, deduplication, and back-pressure in PON&lt;/a&gt; · &lt;a href="//10_when_notifications_explode_message_storms_pon.md"&gt;repo draft&lt;/a&gt; described &lt;strong&gt;deduplication&lt;/strong&gt;, &lt;strong&gt;fan-out batching&lt;/strong&gt;, and &lt;strong&gt;mailbox draining&lt;/strong&gt; in the PON core. Those changes are meant to reduce wasted work—but you only know they helped if you &lt;strong&gt;measure&lt;/strong&gt; the same workload twice under the same knobs.&lt;/p&gt;

&lt;p&gt;This post is a practical guide to &lt;strong&gt;dev profiling&lt;/strong&gt; in this monorepo: reproducible load via &lt;strong&gt;&lt;code&gt;SimulacoesVisuais.Profile.PipelineWorkload&lt;/code&gt;&lt;/strong&gt;, Mix tasks built on OTP profilers, and how to read results without fooling yourself. Deep dives and longer checklists live in &lt;strong&gt;&lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;docs/performance-dev.md&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="//../../memory-pressure-heuristics.md"&gt;&lt;code&gt;docs/memory-pressure-heuristics.md&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;. &lt;strong&gt;Part 12&lt;/strong&gt; closes the series with a retrospective.&lt;/p&gt;




&lt;h2&gt;
  
  
  What profilers actually show
&lt;/h2&gt;

&lt;p&gt;On the BEAM, &lt;strong&gt;&lt;code&gt;mix profile.cprof&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;eprof&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;fprof&lt;/code&gt;&lt;/strong&gt;, and (OTP 27+) &lt;strong&gt;&lt;code&gt;tprof&lt;/code&gt;&lt;/strong&gt; answer different questions (official task docs under &lt;a href="https://hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html" rel="noopener noreferrer"&gt;Mix.Tasks.Profile&lt;/a&gt; on HexDocs; flags and caveats in this repo are expanded in &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Rough question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;cprof&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which functions ran &lt;strong&gt;most often&lt;/strong&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;eprof&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Where did &lt;strong&gt;this process&lt;/strong&gt; spend time?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;fprof&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What does the &lt;strong&gt;call graph&lt;/strong&gt; look like in time?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;tprof&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aggregated &lt;strong&gt;time&lt;/strong&gt;, &lt;strong&gt;calls&lt;/strong&gt;, or &lt;strong&gt;allocation&lt;/strong&gt; (WORDS) across processes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of them is a direct “heap map.” &lt;strong&gt;RAM pressure&lt;/strong&gt; still needs &lt;code&gt;:erlang.memory/0&lt;/code&gt;, &lt;strong&gt;&lt;code&gt;Process.info/2&lt;/code&gt;&lt;/strong&gt;, Observer, or LiveDashboard VM metrics—hot code paths that allocate a lot often correlate with high call counts, but correlation is not identity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reproducible load: &lt;code&gt;PipelineWorkload&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The module &lt;strong&gt;&lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/profile/pipeline_workload.ex"&gt;&lt;code&gt;SimulacoesVisuais.Profile.PipelineWorkload&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; runs &lt;strong&gt;Monte Carlo ticks&lt;/strong&gt; through the same stack as production-style simulation: PON facts, hybrid models, telemetry fan-out, and (if enabled) TSDB writers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.Profile.PipelineWorkload.run/1 (opening)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;duration_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:duration_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;env_duration_ms&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;ticks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ticks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PROFILE_PIPELINE_TICKS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;max_ticks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:max_ticks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PROFILE_PIPELINE_MAX_TICKS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode_from_env&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="n"&gt;memory?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env_bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PROFILE_PIPELINE_MEMORY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ensure_all_started&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:simulacoes_visuais&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;MonteCarlo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;memory?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;print_memory_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"before"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# … duration_ms branch → run_until_deadline_* OR fixed tick count …&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;memory?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;print_memory_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"after"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="ss"&gt;:ok&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Two modes matter for interpretation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;:via_genserver&lt;/code&gt;&lt;/strong&gt; (default) — calls &lt;strong&gt;&lt;code&gt;SmartBreweryMonteCarlo.run_tick_sync/0&lt;/code&gt;&lt;/strong&gt;. Realistic for end-to-end behavior; some profilers attribute time to the &lt;strong&gt;caller&lt;/strong&gt; (e.g. Mix) as much as to the GenServer body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;:in_process&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;&lt;code&gt;PROFILE_PIPELINE_MODE=in_process&lt;/code&gt;&lt;/strong&gt; runs &lt;strong&gt;&lt;code&gt;run_tick_pure/1&lt;/code&gt;&lt;/strong&gt; in the &lt;strong&gt;current&lt;/strong&gt; process. Better for &lt;strong&gt;&lt;code&gt;fprof&lt;/code&gt;&lt;/strong&gt;-style call graphs; &lt;strong&gt;does not&lt;/strong&gt; advance the live GenServer RNG state—profiling only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set duration instead of a fixed tick count with &lt;strong&gt;&lt;code&gt;PROFILE_PIPELINE_DURATION_MS&lt;/code&gt;&lt;/strong&gt; (milliseconds wall clock), capped by &lt;strong&gt;&lt;code&gt;PROFILE_PIPELINE_MAX_TICKS&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment variables (cheat sheet)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROFILE_PIPELINE_TICKS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed number of ticks when duration is off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROFILE_PIPELINE_DURATION_MS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run until wall-clock deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROFILE_PIPELINE_MAX_TICKS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Safety ceiling for duration mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROFILE_PIPELINE_MODE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;via_genserver&lt;/code&gt; (default) or &lt;code&gt;in_process&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROFILE_PIPELINE_MEMORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; / &lt;code&gt;true&lt;/code&gt; → print &lt;code&gt;:erlang.memory/0&lt;/code&gt; + key processes before/after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIMULACOES_TSDB_ENABLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Match your DB-on vs DB-off scenario&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LOGGER_LEVEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Often &lt;code&gt;warning&lt;/code&gt; to reduce log noise during long runs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For &lt;strong&gt;before/after&lt;/strong&gt; comparisons, also lock Monte Carlo and pipeline tuning: &lt;code&gt;MONTE_CARLO_INTERVAL_MS&lt;/code&gt;, &lt;code&gt;MONTE_CARLO_FACTS_PER_TICK_MIN&lt;/code&gt; / &lt;code&gt;MAX&lt;/code&gt;, &lt;code&gt;TELEMETRY_PIPELINE_BATCH_SIZE&lt;/code&gt;, &lt;code&gt;TELEMETRY_PIPELINE_BATCH_TIMEOUT_MS&lt;/code&gt;, &lt;code&gt;TELEMETRY_PRODUCER_MAX_QUEUE&lt;/code&gt;, &lt;code&gt;OEE_PUBSUB_MIN_INTERVAL_MS&lt;/code&gt;—see the tables in &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick commands
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Call-count hotspot (cprof), TSDB off, long window:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;apps/simulacoes_visuais
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tmp/profile
&lt;span class="nv"&gt;PROFILE_PIPELINE_DURATION_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120000 &lt;span class="nv"&gt;LOGGER_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;warning &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;SIMULACOES_TSDB_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  mix profile.cprof &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SimulacoesVisuais.Profile.PipelineWorkload.run()"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;tee &lt;/span&gt;tmp/profile/cprof-sample.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Narrow to one module:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix profile.cprof &lt;span class="nt"&gt;--module&lt;/span&gt; SimulacoesVisuais.SmartBreweryMonteCarlo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SimulacoesVisuais.Profile.PipelineWorkload.run()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memory snapshot without a profiler:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PROFILE_PIPELINE_MEMORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;PROFILE_PIPELINE_TICKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30 &lt;span class="se"&gt;\&lt;/span&gt;
  mix profile.pipeline
&lt;span class="c"&gt;# alias for: mix simulacoes_visuais.profile_workload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;OTP 27+ aggregated time:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PROFILE_PIPELINE_DURATION_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120000 &lt;span class="nv"&gt;LOGGER_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;warning &lt;span class="se"&gt;\&lt;/span&gt;
  mix profile.tprof &lt;span class="nt"&gt;--type&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="nt"&gt;--report&lt;/span&gt; total &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SimulacoesVisuais.Profile.PipelineWorkload.run()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;fprof trace file&lt;/strong&gt; — put &lt;strong&gt;&lt;code&gt;-e "..."&lt;/code&gt; before &lt;code&gt;--trace-to-file&lt;/code&gt;&lt;/strong&gt;, or Mix mis-parses the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix profile.fprof &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SimulacoesVisuais.Profile.PipelineWorkload.run()"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--trace-to-file&lt;/span&gt; tmp/profile/run.trace &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;tee &lt;/span&gt;tmp/profile/fprof-out.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  60-second battery
&lt;/h2&gt;

&lt;p&gt;From the repo root, &lt;strong&gt;&lt;a href="//../../scripts/run_profile_60s.sh"&gt;&lt;code&gt;scripts/run_profile_60s.sh&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; runs several profiler variants and writes under &lt;strong&gt;&lt;code&gt;apps/simulacoes_visuais/tmp/profile/*-60s.txt&lt;/code&gt;&lt;/strong&gt;. Useful when you want comparable artifacts after a change without hand-typing flags.&lt;/p&gt;




&lt;h2&gt;
  
  
  Profiling the Monte Carlo GenServer itself
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;&lt;code&gt;PipelineWorkload&lt;/code&gt;&lt;/strong&gt; in default &lt;strong&gt;&lt;code&gt;via_genserver&lt;/code&gt;&lt;/strong&gt; mode, &lt;strong&gt;&lt;code&gt;eprof&lt;/code&gt;&lt;/strong&gt; attached to the Mix process often shows &lt;strong&gt;&lt;code&gt;GenServer.call&lt;/code&gt;&lt;/strong&gt; / client time—not the full body of &lt;strong&gt;&lt;code&gt;SmartBreweryMonteCarlo&lt;/code&gt;&lt;/strong&gt;. To attribute work to the right OTP process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the app (&lt;code&gt;mix phx.server&lt;/code&gt; or &lt;code&gt;Application.ensure_all_started(:simulacoes_visuais)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Resolve PIDs: &lt;code&gt;Process.whereis(SimulacoesVisuais.SmartBreweryMonteCarlo)&lt;/code&gt;, and when TSDB is on, writers such as &lt;strong&gt;&lt;code&gt;RuleEventWriter&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Attach &lt;strong&gt;&lt;code&gt;eprof&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;fprof&lt;/code&gt;&lt;/strong&gt; to those PIDs from an &lt;strong&gt;&lt;code&gt;iex&lt;/code&gt;&lt;/strong&gt; session on the same node (see OTP profiling docs), while generating load from &lt;strong&gt;another&lt;/strong&gt; shell or the UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cross-check &lt;strong&gt;reductions&lt;/strong&gt; and &lt;strong&gt;mailbox length&lt;/strong&gt; in Observer or LiveDashboard while &lt;strong&gt;&lt;code&gt;PROFILE_PIPELINE_MEMORY=1&lt;/code&gt;&lt;/strong&gt; prints heap and queue depth for the registered names the workload already tracks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Baselines: “light” vs “heavy” server
&lt;/h2&gt;

&lt;p&gt;Before blaming PON, confirm whether you are in a &lt;strong&gt;quiet&lt;/strong&gt; or &lt;strong&gt;noisy&lt;/strong&gt; dev profile. &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt; suggests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Lighter: no TSDB, no auto Monte Carlo&lt;/span&gt;
&lt;span class="nv"&gt;PHX_LV_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;SIMULACOES_TSDB_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;&lt;span class="nv"&gt;AUTO_START_MONTE_CARLO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;mix phx.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Heavier: TSDB, fast MC, more LiveView debug&lt;/span&gt;
&lt;span class="nv"&gt;PHX_LV_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;SIMULACOES_TSDB_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;&lt;span class="nv"&gt;AUTO_START_MONTE_CARLO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;MONTE_CARLO_INTERVAL_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;500 mix phx.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Profiling workloads should use the &lt;strong&gt;same&lt;/strong&gt; economic assumptions as the hypothesis you are testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimizations this repo ties to measurements
&lt;/h2&gt;

&lt;p&gt;Internal write-ups (Portuguese) connect architecture to numbers: &lt;strong&gt;&lt;a href="//../../docs/artigos/18_arquitetura_runtime_smart_brewery_e_resultados_de_desempenho.md"&gt;article 18&lt;/a&gt;&lt;/strong&gt; (runtime), &lt;strong&gt;&lt;a href="//../../docs/artigos/19_mitigacao_message_storm_pon_elixir_smart_brewery.md"&gt;article 19&lt;/a&gt;&lt;/strong&gt; (message storm mitigation), &lt;strong&gt;&lt;a href="//../../docs/artigos/20_otimizacao_simulacao_hibrida_pon_elixir.md"&gt;article 20&lt;/a&gt;&lt;/strong&gt; (ETS &lt;code&gt;write_concurrency&lt;/code&gt;, Registry partitions, Rete spike, Broadway tuning). The &lt;strong&gt;checklist&lt;/strong&gt; at the top of &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt; maps each item to modules—for example: &lt;strong&gt;&lt;code&gt;Registry&lt;/code&gt; partitions&lt;/strong&gt; in &lt;code&gt;Tec0301Pon.Application&lt;/code&gt;, &lt;strong&gt;ETS&lt;/strong&gt; &lt;code&gt;read_concurrency&lt;/code&gt; / &lt;code&gt;write_concurrency&lt;/code&gt; on fact values, &lt;strong&gt;Broadway&lt;/strong&gt; &lt;code&gt;:telemetry_pipeline_processor_concurrency&lt;/code&gt; / batcher settings in config, &lt;strong&gt;&lt;code&gt;Fanout&lt;/code&gt;&lt;/strong&gt; switching to &lt;strong&gt;&lt;code&gt;Task.async_stream&lt;/code&gt;&lt;/strong&gt; when the update map has more than four pairs, and optional &lt;strong&gt;Rete&lt;/strong&gt; experiments via &lt;code&gt;Tec0301Pon.PON.ReteSpike&lt;/code&gt;. Profiling tells you which row in that table actually matters for &lt;em&gt;your&lt;/em&gt; tick rate and hardware.&lt;/p&gt;

&lt;p&gt;Do &lt;strong&gt;not&lt;/strong&gt; publish a single “we saved 40%” headline unless you have &lt;strong&gt;paired&lt;/strong&gt; runs (same commit range, same env, same &lt;code&gt;PROFILE_PIPELINE_*&lt;/code&gt;) and you document where the files live—otherwise you are storytelling, not benchmarking.&lt;/p&gt;




&lt;h2&gt;
  
  
  BI / TSDB sanity (one paragraph)
&lt;/h2&gt;

&lt;p&gt;If charts look empty while &lt;code&gt;telemetry_events&lt;/code&gt; has millions of rows, check &lt;strong&gt;time windows&lt;/strong&gt;: &lt;code&gt;mix verify.bi&lt;/code&gt; uses small &lt;strong&gt;&lt;code&gt;LIMIT&lt;/code&gt;&lt;/strong&gt; samples and often &lt;strong&gt;last 24h&lt;/strong&gt; in SQL; &lt;strong&gt;&lt;code&gt;mix verify.tsdb&lt;/code&gt;&lt;/strong&gt; reports &lt;strong&gt;global&lt;/strong&gt; counts and &lt;strong&gt;&lt;code&gt;MAX(ts)&lt;/code&gt;&lt;/strong&gt;. Misaligned windows are a data issue, not a broken query—see &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt; § BI native.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flow under profiling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  PW[PipelineWorkload]
  MC[SmartBreweryMonteCarlo]
  PON[PON Fato Regra]
  BW[Broadway Telemetry]
  W[AsyncWriters optional]
  Mix[Mix process profiler]
  PW --&amp;gt; MC
  MC --&amp;gt; PON
  PON --&amp;gt; BW
  BW --&amp;gt; W
  Mix --&amp;gt;|"via_genserver: often attributes client time"| MC
  Mix --&amp;gt;|"in_process: run_tick_pure only"| MC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 10&lt;/strong&gt; reduced redundant messages; &lt;strong&gt;Part 11&lt;/strong&gt; gives you the &lt;strong&gt;bench&lt;/strong&gt;: &lt;code&gt;PipelineWorkload&lt;/code&gt;, Mix profilers, memory snapshots, and explicit env discipline. Treat every optimization as a &lt;strong&gt;hypothesis&lt;/strong&gt; until two traces agree. &lt;strong&gt;Part 12&lt;/strong&gt; zooms out with a series retrospective and a full index of posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mix profiling&lt;/strong&gt; — &lt;code&gt;profile.cprof&lt;/code&gt;, &lt;code&gt;profile.eprof&lt;/code&gt;, &lt;code&gt;profile.fprof&lt;/code&gt;, &lt;code&gt;profile.tprof&lt;/code&gt; — &lt;a href="https://hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html" rel="noopener noreferrer"&gt;HexDocs (cprof)&lt;/a&gt; and sibling tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Armstrong (2003)&lt;/strong&gt; — process isolation and failure domains — &lt;a href="https://www.erlang.org/download/armstrong_thesis_2003.pdf" rel="noopener noreferrer"&gt;thesis PDF&lt;/a&gt; (context for why mailbox/profiler stories matter).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repo&lt;/strong&gt; — &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/profile/pipeline_workload.ex"&gt;&lt;code&gt;pipeline_workload.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../docs/performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../docs/memory-pressure-heuristics.md"&gt;&lt;code&gt;memory-pressure-heuristics.md&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../scripts/run_profile_60s.sh"&gt;&lt;code&gt;run_profile_60s.sh&lt;/code&gt;&lt;/a&gt;; internal articles &lt;a href="//../../docs/artigos/18_arquitetura_runtime_smart_brewery_e_resultados_de_desempenho.md"&gt;18&lt;/a&gt;, &lt;a href="//../../docs/artigos/20_otimizacao_simulacao_hibrida_pon_elixir.md"&gt;20&lt;/a&gt;. Expanded list: &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Published on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;Dev profiling: CPU, memory, and what changed after optimizations&lt;/a&gt; — tracked in &lt;a href="//../../devto_serie_pon_smart_brewery.md"&gt;&lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;Part 10 on dev.to — When notifications explode: message storms, deduplication, and back-pressure in PON&lt;/a&gt; · &lt;a href="//10_when_notifications_explode_message_storms_pon.md"&gt;repo draft&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="//12_retrospective_reactive_rules_engine_elixir.md"&gt;Part 12 — Retrospective: lessons from building a reactive rules engine in Elixir&lt;/a&gt; — &lt;em&gt;end of series&lt;/em&gt; (index + lessons learned).&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>performance</category>
      <category>architecture</category>
      <category>profiling</category>
    </item>
    <item>
      <title>When notifications explode: message storms, deduplication, and back-pressure in PON</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:18:07 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4</link>
      <guid>https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4</guid>
      <description>&lt;p&gt;&lt;em&gt;If this helped you, you can &lt;a href="https://dev.to/matheuscamarques/support-with-a-coffee-2oa0"&gt;support the author with a coffee on dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  When notifications explode: message storms, deduplication, and back-pressure in PON
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 10 of 12&lt;/strong&gt; — &lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;Part 9 on dev.to — ML on the digital twin: export, train pilots, and import predictions back into the app&lt;/a&gt; · &lt;a href="//09_ml_digital_twin_export_train_import_predictions.md"&gt;repo draft&lt;/a&gt; showed how to &lt;strong&gt;export&lt;/strong&gt; history and &lt;strong&gt;import&lt;/strong&gt; model scores. Before adding more consumers, it pays to ask: what happens when the twin’s &lt;strong&gt;tick rate&lt;/strong&gt; rises and every &lt;code&gt;Fato.atualizar/2&lt;/code&gt; fans out to many &lt;code&gt;Regra&lt;/code&gt; processes?&lt;/p&gt;

&lt;p&gt;This post is about &lt;strong&gt;message storms&lt;/strong&gt; on the BEAM: redundant &lt;code&gt;Registry.dispatch&lt;/code&gt; work, rule mailboxes full of duplicate context, and &lt;strong&gt;back-pressure&lt;/strong&gt; at the edges. The patterns below are implemented in &lt;strong&gt;&lt;code&gt;tec0301_pon&lt;/code&gt;&lt;/strong&gt; and exercised by &lt;strong&gt;Smart Brewery&lt;/strong&gt; + &lt;strong&gt;&lt;code&gt;simulacoes_visuais&lt;/code&gt;&lt;/strong&gt;. Deeper Portuguese notes live in &lt;a href="//../../artigos/19_mitigacao_message_storm_pon_elixir_smart_brewery.md"&gt;&lt;code&gt;docs/artigos/19_mitigacao_message_storm_pon_elixir_smart_brewery.md&lt;/code&gt;&lt;/a&gt;; reproducing profiles is covered in &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;docs/performance-dev.md&lt;/code&gt;&lt;/a&gt;. &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;&lt;strong&gt;Part 11 on dev.to&lt;/strong&gt;&lt;/a&gt; zooms in on &lt;strong&gt;profiling&lt;/strong&gt; methodology and before/after numbers. Intuitively, &lt;strong&gt;stable&lt;/strong&gt; queueing systems obey &lt;strong&gt;Little’s Law&lt;/strong&gt; (mean work in system ∝ arrival rate × mean delay—&lt;a href="https://en.wikipedia.org/wiki/Little%27s_law" rel="noopener noreferrer"&gt;Little 1961&lt;/a&gt;); storms push the “work in system” term through the roof unless you cut redundant arrivals or widen the bottleneck.&lt;/p&gt;




&lt;h2&gt;
  
  
  Symptom: CPU spent orchestrating, not deciding
&lt;/h2&gt;

&lt;p&gt;In actor-style PON graphs, one fact update can mean:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;GenServer&lt;/code&gt;&lt;/strong&gt; cast on the fact process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Registry.dispatch&lt;/code&gt;&lt;/strong&gt; to every subscriber.&lt;/li&gt;
&lt;li&gt;One &lt;strong&gt;message per rule&lt;/strong&gt; that watches the fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition evaluation&lt;/strong&gt; and possibly actions—again per message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under Monte Carlo or dense PLC simulation, steps 2–3 multiply into a &lt;strong&gt;storm&lt;/strong&gt;: schedulers stay busy copying terms between processes, even when the &lt;strong&gt;business outcome&lt;/strong&gt; would be the same after coalescing updates.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Deduplicate at the source (&lt;code&gt;Fato&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;If the new value is &lt;strong&gt;strictly equal&lt;/strong&gt; to the current one (&lt;code&gt;===&lt;/code&gt;), skip dispatch entirely and do not bump the fact’s notification statistics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tec0301_pon/pon/fato.ex — handle_cast/2 (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:atualizar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valor_igual?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;novo_estado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;estado&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:estatisticas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ets_put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Tec0301Pon&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;inscritos&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;inscritos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:notificacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_valor&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;novo_estado&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Float note:&lt;/strong&gt; equality is exact. Noisy floats should be &lt;strong&gt;quantized&lt;/strong&gt; upstream (as in the Smart Brewery random walk) if you rely on this filter—adding a global epsilon for all types would surprise rules that compare structured terms.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Coalesce across facts (&lt;code&gt;Fanout&lt;/code&gt; + &lt;code&gt;atualizar_lote/1&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Correlated equipment often updates &lt;strong&gt;several&lt;/strong&gt; facts in the same tick (e.g. filter pressure, clarity, pump speed). Calling &lt;code&gt;atualizar/2&lt;/code&gt; three times means three dispatches and three bursts to shared rules. &lt;strong&gt;&lt;code&gt;Fato.atualizar_lote/1&lt;/code&gt;&lt;/strong&gt; applies &lt;strong&gt;&lt;code&gt;{:atualizar_sem_dispatch, val}&lt;/code&gt;&lt;/strong&gt; per changed fact, then sends &lt;strong&gt;one&lt;/strong&gt; &lt;code&gt;{:notificacoes_lote, map}&lt;/code&gt; per subscriber PID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tec0301_pon/pon/fanout.ex — notify_lote/1 (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;notify_lote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;changed&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;Registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@pubsub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniq&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:notificacoes_lote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules merge only the keys they watch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tec0301_pon/pon/regra.ex — after {:notificacoes_lote, updates}&lt;/span&gt;
&lt;span class="n"&gt;relevante&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;nova_memoria&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relevante&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nova_memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drained&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;drain_notificacoes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nova_memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# then update :estatisticas_notificacoes and call avaliar_apos_memoria/2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Large batches use &lt;strong&gt;&lt;code&gt;Task.async_stream&lt;/code&gt;&lt;/strong&gt; with bounded concurrency inside &lt;code&gt;Fanout.atualizar_lote/1&lt;/code&gt;; very small maps update sequentially—see the module for thresholds.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Drain the rule mailbox before evaluating (&lt;code&gt;drain_notificacoes/3&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Even with fan-out reduced, bursts still happen. After applying the first notification, &lt;strong&gt;&lt;code&gt;Regra&lt;/code&gt;&lt;/strong&gt; empties the mailbox of &lt;strong&gt;more&lt;/strong&gt; &lt;code&gt;{:notificacao, …}&lt;/code&gt; / &lt;code&gt;{:notificacoes_lote, …}&lt;/code&gt; with a zero-timeout &lt;code&gt;receive&lt;/code&gt;, then runs &lt;strong&gt;one&lt;/strong&gt; &lt;code&gt;avaliar_condicao/2&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tec0301_pon/pon/regra.ex — drain (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;drain_notificacoes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;receive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:notificacao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;drain_notificacoes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:notificacoes_lote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fatos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;drain_notificacoes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;after&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;memoria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;debounce-by-draining&lt;/strong&gt;: the rule sees the &lt;strong&gt;latest&lt;/strong&gt; memory state for the burst window, not every intermediate message.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Fast reads without blocking the fact (&lt;code&gt;ETS&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Hot paths that only need the current value (e.g. filling rule memory, Monte Carlo readers) call &lt;strong&gt;&lt;code&gt;Fato.obter/1&lt;/code&gt;&lt;/strong&gt;, which prefers a public &lt;strong&gt;ETS&lt;/strong&gt; table with &lt;code&gt;read_concurrency&lt;/code&gt; and &lt;code&gt;write_concurrency&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/tec0301_pon/pon/fato.ex — obter/1 (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;obter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome_do_fato&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_atom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome_do_fato&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@ets_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nome_do_fato&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;nome_do_fato&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;
    &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome_do_fato&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:obter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Tec0301Pon.Application&lt;/code&gt; calls &lt;strong&gt;&lt;code&gt;Fato.ensure_ets!()&lt;/code&gt;&lt;/strong&gt; before registering the &lt;code&gt;Registry&lt;/code&gt; so lookups are safe during startup races.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Back-pressure outside the engine (Phoenix / Broadway)
&lt;/h2&gt;

&lt;p&gt;The PON core is not the only fan-out. &lt;strong&gt;&lt;code&gt;SmartBreweryFactBroadcaster&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;LiveViewEventBatcher&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;TelemetryProducer&lt;/code&gt;&lt;/strong&gt; (bounded queue with drop-oldest), and &lt;strong&gt;Broadway&lt;/strong&gt; batchers implement &lt;strong&gt;pressure&lt;/strong&gt; so PostgreSQL and LiveView do not amplify storms—see &lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;Part 6 on dev.to&lt;/a&gt; · &lt;a href="//06_phoenix_liveview_operations_ui_rules_engine.md"&gt;repo draft&lt;/a&gt; and &lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;Part 7 on dev.to&lt;/a&gt; · &lt;a href="//07_from_simulation_to_storage_telemetry_broadway_timescaledb.md"&gt;repo draft&lt;/a&gt;. &lt;strong&gt;&lt;code&gt;SmartBreweryMonteCarlo&lt;/code&gt;&lt;/strong&gt; caches &lt;code&gt;Application.get_env&lt;/code&gt; bounds in &lt;strong&gt;&lt;code&gt;init&lt;/code&gt;&lt;/strong&gt; so the tick loop does not hit the application environment on every iteration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Message contract cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Typical producer&lt;/th&gt;
&lt;th&gt;Consumer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{:notificacao, name, value}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Fato&lt;/code&gt; after a real change&lt;/td&gt;
&lt;td&gt;Rules, telemetry bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{:notificacoes_lote, %{atom =&amp;gt; value}}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Fanout.notify_lote/1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same subscribers; rules &lt;code&gt;Map.take/2&lt;/code&gt; relevant keys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Code that only ever calls &lt;strong&gt;&lt;code&gt;atualizar/2&lt;/code&gt;&lt;/strong&gt; keeps the single-notification shape; simulation paths that batch use &lt;strong&gt;&lt;code&gt;atualizar_lote/1&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measuring (preview — full walkthrough in &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;Part 11 on dev.to&lt;/a&gt;)
&lt;/h2&gt;

&lt;p&gt;The repo ships &lt;strong&gt;&lt;code&gt;SimulacoesVisuais.Profile.PipelineWorkload&lt;/code&gt;&lt;/strong&gt; and Mix tasks such as &lt;strong&gt;&lt;code&gt;mix profile.cprof&lt;/code&gt;&lt;/strong&gt; for repeatable runs. Example harness (TSDB off, headless):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;apps/simulacoes_visuais
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tmp/profile
&lt;span class="nv"&gt;PROFILE_PIPELINE_DURATION_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60000 &lt;span class="nv"&gt;LOGGER_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;warning &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;SIMULACOES_TSDB_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;&lt;span class="nv"&gt;SIMULACOES_HEADLESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  mix profile.cprof &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SimulacoesVisuais.Profile.PipelineWorkload.run()"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;tee &lt;/span&gt;tmp/profile/cprof-sample.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat &lt;strong&gt;percent gains&lt;/strong&gt; as &lt;strong&gt;hypotheses&lt;/strong&gt; until you A/B the same workload on two commits; the internal article avoids advertising fixed “30–50%” without paired measurements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flow diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
  subgraph reduce [Reduce fan-out]
    F[Fato atualizar]
    D{Value changed?}
    FD[Registry dispatch]
    L[Fato atualizar_lote]
    FN[Fanout notify_lote]
  end
  subgraph rule [Rule process]
    M[Mailbox]
    DR[drain_notificacoes]
    EV[avaliar_condicao once]
  end
  F --&amp;gt; D
  D --&amp;gt;|no| X[no notify]
  D --&amp;gt;|yes| FD
  FD --&amp;gt; M
  L --&amp;gt; FN
  FN --&amp;gt; M
  M --&amp;gt; DR --&amp;gt; EV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Message storms&lt;/strong&gt; are a property of &lt;strong&gt;graph fan-out&lt;/strong&gt;, not a judgment on PON. This codebase mitigates them with &lt;strong&gt;deduplication&lt;/strong&gt;, &lt;strong&gt;batch notifications&lt;/strong&gt;, &lt;strong&gt;mailbox draining&lt;/strong&gt;, &lt;strong&gt;ETS reads&lt;/strong&gt;, and &lt;strong&gt;bounded&lt;/strong&gt; telemetry pipelines—without changing what a rule &lt;em&gt;means&lt;/em&gt;, only how often it is prodded. &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;&lt;strong&gt;Part 11 on dev.to&lt;/strong&gt;&lt;/a&gt; ties these knobs to &lt;strong&gt;profiles&lt;/strong&gt; and memory heuristics.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Little, J. D. C. (1961)&lt;/strong&gt; — &lt;em&gt;A proof for the queuing formula L = λW&lt;/em&gt; — classic &lt;strong&gt;Little’s Law&lt;/strong&gt;; &lt;a href="https://en.wikipedia.org/wiki/Little%27s_law" rel="noopener noreferrer"&gt;Wikipedia entry&lt;/a&gt; with citation to &lt;em&gt;Operations Research&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Erlang&lt;/strong&gt; — &lt;code&gt;:erlang.process_info/2&lt;/code&gt; (mailbox length, etc.) — &lt;a href="https://www.erlang.org/doc/man/erlang.html" rel="noopener noreferrer"&gt;manual&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elixir &lt;code&gt;Registry&lt;/code&gt;&lt;/strong&gt; — dispatch semantics — &lt;a href="https://hexdocs.pm/elixir/Registry.html" rel="noopener noreferrer"&gt;HexDocs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repo&lt;/strong&gt; — &lt;a href="//../../lib/tec0301_pon/pon/fato.ex"&gt;&lt;code&gt;fato.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../lib/tec0301_pon/pon/fanout.ex"&gt;&lt;code&gt;fanout.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../lib/tec0301_pon/pon/regra.ex"&gt;&lt;code&gt;regra.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../lib/tec0301_pon/application.ex"&gt;&lt;code&gt;application.ex&lt;/code&gt;&lt;/a&gt;; &lt;a href="//../../docs/artigos/19_mitigacao_message_storm_pon_elixir_smart_brewery.md"&gt;&lt;code&gt;artigo 19&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../docs/performance-dev.md"&gt;&lt;code&gt;performance-dev.md&lt;/code&gt;&lt;/a&gt;. Expanded list: &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Published on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;When notifications explode: message storms, deduplication, and back-pressure in PON&lt;/a&gt; — tracked in &lt;a href="//../../devto_serie_pon_smart_brewery.md"&gt;&lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;Part 9 on dev.to — ML on the digital twin: export, train pilots, and import predictions back into the app&lt;/a&gt; · &lt;a href="//09_ml_digital_twin_export_train_import_predictions.md"&gt;repo draft&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/dev-profiling-cpu-memory-and-what-changed-after-optimizations-28hb"&gt;Part 11 on dev.to — Dev profiling: CPU, memory, and what changed after optimizations&lt;/a&gt; · &lt;a href="//11_dev_profiling_cpu_memory_optimizations.md"&gt;repo draft&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>architecture</category>
      <category>performance</category>
      <category>realtime</category>
    </item>
    <item>
      <title>ML on the digital twin: export, train pilots, and import predictions back into the app</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:13:39 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i</link>
      <guid>https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i</guid>
      <description>&lt;p&gt;&lt;em&gt;If this helped you, you can &lt;a href="https://dev.to/matheuscamarques/support-with-a-coffee-2oa0"&gt;support the author with a coffee on dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ML on the digital twin: export, train pilots, and import predictions back into the app
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 9 of 12&lt;/strong&gt; — &lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;Part 8 on dev.to — BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)&lt;/a&gt; · &lt;a href="//08_bi_without_mystery_dimensions_facts_power_bi.md"&gt;repo draft&lt;/a&gt; gave you a &lt;strong&gt;star-shaped&lt;/strong&gt; analytical surface in PostgreSQL/TimescaleDB. Machine learning needs the same history in &lt;strong&gt;files&lt;/strong&gt; or &lt;strong&gt;arrays&lt;/strong&gt;, then a path to bring &lt;strong&gt;scores&lt;/strong&gt; back where operators already look: the Phoenix app.&lt;/p&gt;

&lt;p&gt;This post covers &lt;strong&gt;&lt;code&gt;mix export.ml&lt;/code&gt;&lt;/strong&gt;, the &lt;strong&gt;CSV contract&lt;/strong&gt; documented in &lt;code&gt;MLDatasetExport&lt;/code&gt;, &lt;strong&gt;in-Beam pilot trainers&lt;/strong&gt; (&lt;code&gt;mix simulacoes_visuais.ml_train&lt;/code&gt;), &lt;strong&gt;batch import&lt;/strong&gt; of predictions as JSONL, and the &lt;strong&gt;&lt;code&gt;/smart-brewery/ml-predictions&lt;/code&gt;&lt;/strong&gt; LiveView. &lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;&lt;strong&gt;Part 10 on dev.to&lt;/strong&gt;&lt;/a&gt; shifts to &lt;strong&gt;message storms&lt;/strong&gt; and back-pressure in the PON engine.&lt;/p&gt;

&lt;p&gt;For a longer Portuguese walkthrough (notebooks, Python sketches), see &lt;a href="//../../artigos/27_guia_pratico_treino_ml_smart_brewery.md"&gt;&lt;code&gt;docs/artigos/27_guia_pratico_treino_ml_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closed loop in three hops
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; — Snapshot telemetry, OEE, anomalies, rule events, dimensions, and optional CAGG slices to a directory of CSVs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Train / infer offline&lt;/strong&gt; — Use Elixir pilots for demos, or Python/R/Julia on the same files; produce &lt;strong&gt;JSON Lines&lt;/strong&gt; (one JSON object per row).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import + display&lt;/strong&gt; — &lt;code&gt;mix import.ml.predictions&lt;/code&gt; (alias) bulk-inserts into &lt;strong&gt;&lt;code&gt;ml_predictions&lt;/code&gt;&lt;/strong&gt;; &lt;strong&gt;&lt;code&gt;MlPredictionsLive&lt;/code&gt;&lt;/strong&gt; lists recent rows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Training itself is intentionally &lt;strong&gt;out of band&lt;/strong&gt;: the app does not need GPU drivers to be valuable as the &lt;strong&gt;system of record&lt;/strong&gt; for predictions. For &lt;strong&gt;production readiness&lt;/strong&gt; checklists (monitoring, data validation, CI for models), Google’s &lt;em&gt;ML Test Score&lt;/em&gt; rubric is a compact reference (&lt;a href="https://research.google/pubs/pub46555/" rel="noopener noreferrer"&gt;Breck et al., 2017&lt;/a&gt;); this repo implements only a &lt;strong&gt;thin&lt;/strong&gt; slice—export/import contracts and a LiveView reader—not full MLOps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: &lt;code&gt;mix export.ml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;From &lt;code&gt;apps/simulacoes_visuais&lt;/code&gt;, with &lt;strong&gt;&lt;code&gt;:tsdb_enabled&lt;/code&gt;&lt;/strong&gt; and migrations applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix export.ml &lt;span class="nt"&gt;--out&lt;/span&gt; /tmp/ml_export &lt;span class="nt"&gt;--since-hours&lt;/span&gt; 168
&lt;span class="c"&gt;# Long form: mix simulacoes_visuais.export_ml --out /tmp/ml_export --since-hours 72&lt;/span&gt;
&lt;span class="c"&gt;# Skip some CAGGs if missing: --no-cagg or --no-cagg-1h-1day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The task delegates to &lt;strong&gt;&lt;code&gt;SimulacoesVisuais.MLDatasetExport.export_all/2&lt;/code&gt;&lt;/strong&gt;, which writes UTF-8 CSVs with headers. The module’s moduledoc is the &lt;strong&gt;contract&lt;/strong&gt; for downstream notebooks—example excerpts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.MLDatasetExport @moduledoc (SQL shapes, excerpt)&lt;/span&gt;
&lt;span class="c1"&gt;# Telemetry (multivariate series)&lt;/span&gt;
&lt;span class="c1"&gt;#   SELECT ts, fact_name, value_float, value_int, value_str, ...&lt;/span&gt;
&lt;span class="c1"&gt;#   FROM telemetry_events WHERE ts &amp;gt;= $1 ORDER BY ts ASC;&lt;/span&gt;
&lt;span class="c1"&gt;# OEE (regression target)&lt;/span&gt;
&lt;span class="c1"&gt;#   SELECT ts, oee_pct, availability_pct, performance_pct, quality_pct, ...&lt;/span&gt;
&lt;span class="c1"&gt;#   FROM oee_snapshots WHERE ts &amp;gt;= $1 ORDER BY ts ASC;&lt;/span&gt;
&lt;span class="c1"&gt;# Rule firings (discrete events)&lt;/span&gt;
&lt;span class="c1"&gt;#   SELECT ts, regra_id, case_id, ... FROM rule_events WHERE ts &amp;gt;= $1 ...&lt;/span&gt;
&lt;span class="c1"&gt;# Dimensions + telemetry_events_1min / _1h / _1day — see source for full SQL.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical filenames include &lt;code&gt;telemetry_events.csv&lt;/code&gt;, &lt;code&gt;oee_snapshots.csv&lt;/code&gt;, &lt;code&gt;rule_events.csv&lt;/code&gt;, &lt;code&gt;dim_equipamento_fbe.csv&lt;/code&gt;, and CAGG exports—matching the README’s &lt;strong&gt;headless simulation&lt;/strong&gt; story (Docker Postgres, Monte Carlo, then export).&lt;/p&gt;

&lt;p&gt;The same migration that introduced &lt;strong&gt;&lt;code&gt;ml_predictions&lt;/code&gt;&lt;/strong&gt; also adds &lt;strong&gt;&lt;code&gt;case_id&lt;/code&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;code&gt;rule_events&lt;/code&gt;&lt;/strong&gt;, so exported rule traces can align with &lt;strong&gt;process-mining&lt;/strong&gt; style case identifiers when you join predictions back to operational event logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2a: Elixir pilots (Scholar / Axon)
&lt;/h2&gt;

&lt;p&gt;For reproducible demos without leaving the BEAM, &lt;strong&gt;&lt;code&gt;mix simulacoes_visuais.ml_train&lt;/code&gt;&lt;/strong&gt; reads the same CSV directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix simulacoes_visuais.ml_train &lt;span class="nt"&gt;--dir&lt;/span&gt; /tmp/ml_export &lt;span class="nt"&gt;--pilot&lt;/span&gt; oee
mix simulacoes_visuais.ml_train &lt;span class="nt"&gt;--dir&lt;/span&gt; /tmp/ml_export &lt;span class="nt"&gt;--pilot&lt;/span&gt; fermentation &lt;span class="nt"&gt;--epochs&lt;/span&gt; 40
mix simulacoes_visuais.ml_train &lt;span class="nt"&gt;--dir&lt;/span&gt; /tmp/ml_export &lt;span class="nt"&gt;--pilot&lt;/span&gt; anomaly &lt;span class="nt"&gt;--epochs&lt;/span&gt; 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;oee&lt;/code&gt;&lt;/strong&gt; — Scholar &lt;strong&gt;linear regression&lt;/strong&gt; baseline on exported OEE series.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fermentation&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;Axon MLP&lt;/strong&gt; pilot on fermentation-related signals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;anomaly&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;Axon autoencoder&lt;/strong&gt; focused on FBE_01-style vibration features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task prints &lt;strong&gt;metrics&lt;/strong&gt; to the shell; production workflows usually &lt;strong&gt;emit JSONL&lt;/strong&gt; from a separate inference job and import below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2b: JSONL format for import
&lt;/h2&gt;

&lt;p&gt;Each line is a JSON object. &lt;strong&gt;&lt;code&gt;model_name&lt;/code&gt;&lt;/strong&gt; is required; &lt;strong&gt;&lt;code&gt;ts&lt;/code&gt;&lt;/strong&gt; is optional (defaults to “now” in UTC, normalized to microsecond precision for Ecto).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"model_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"oee_linear_v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2025-03-20T12:00:00.000000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"target_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"oee_pct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"value_float"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;87.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"rmse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"export_window_h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;168&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional fields: &lt;strong&gt;&lt;code&gt;target_name&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;value_float&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;metadata&lt;/code&gt;&lt;/strong&gt; (object, stored as JSON in Postgres).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: persist predictions — schema and context
&lt;/h2&gt;

&lt;p&gt;The migration creates a narrow &lt;strong&gt;fact table&lt;/strong&gt; for batch scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# priv/repo/migrations/20260319120000_add_rule_events_case_id_and_ml_predictions.exs (excerpt)&lt;/span&gt;
&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ml_predictions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:binary_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:target_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:value_float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:float&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:map&lt;/span&gt;
  &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;type:&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ml_predictions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ts&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ml_predictions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ts&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.MlPrediction (schema excerpt)&lt;/span&gt;
&lt;span class="nv"&gt;@primary_key&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:binary_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;autogenerate:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"ml_predictions"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:target_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:value_float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;type:&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bulk insert from decoded JSON maps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.MlPredictions.insert_from_decoded_maps/1 (excerpt)&lt;/span&gt;
&lt;span class="c1"&gt;# Each map must include "model_name"; missing model raises ArgumentError.&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="ss"&gt;ts:&lt;/span&gt; &lt;span class="n"&gt;parse_ts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;model_name:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"model_name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;target_name:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"target_name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;value_float:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"value_float"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;metadata:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;%{},&lt;/span&gt;
      &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MlPrediction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mix task: import from disk
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mix simulacoes_visuais.ml_import_predictions @moduledoc (usage)&lt;/span&gt;
&lt;span class="c1"&gt;#   mix simulacoes_visuais.ml_import_predictions --file /path/to/preds.jsonl&lt;/span&gt;
&lt;span class="c1"&gt;# Alias in mix.exs: mix import.ml.predictions --file /path/to/preds.jsonl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The task streams the file, &lt;strong&gt;&lt;code&gt;Jason.decode!/1&lt;/code&gt;&lt;/strong&gt; per line, and calls &lt;strong&gt;&lt;code&gt;MlPredictions.insert_from_decoded_maps/1&lt;/code&gt;&lt;/strong&gt;. It requires &lt;strong&gt;&lt;code&gt;:tsdb_enabled&lt;/code&gt;&lt;/strong&gt; so &lt;code&gt;Repo&lt;/code&gt; is part of the running app configuration you expect in TSDB workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  LiveView: &lt;code&gt;/smart-brewery/ml-predictions&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuaisWeb.MlPredictionsLive — mount/3 (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;preds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsdb?&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;load_predictions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;predictions:&lt;/span&gt; &lt;span class="n"&gt;preds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tsdb_enabled:&lt;/span&gt; &lt;span class="n"&gt;tsdb?&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;load_predictions&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;tsdb?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:simulacoes_visuais&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tsdb_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;preds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tsdb?&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;MlPredictions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_recent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;preds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsdb?&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Router (already introduced in &lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;Part 6 on dev.to&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="s2"&gt;"/smart-brewery/ml-predictions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MlPredictionsLive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template surfaces &lt;strong&gt;timestamp&lt;/strong&gt;, &lt;strong&gt;model&lt;/strong&gt;, &lt;strong&gt;target&lt;/strong&gt;, and &lt;strong&gt;value&lt;/strong&gt;, plus a &lt;strong&gt;Refresh&lt;/strong&gt; button—enough to validate that your batch job actually landed in the warehouse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Flow diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  subgraph db [TimescaleDB]
    TE[telemetry_events]
    OEE[oee_snapshots]
    RE[rule_events]
    DIM[dimensions]
    MP[ml_predictions]
  end
  subgraph offline [Offline ML]
    CSV[CSV export dir]
    PY[Python / R / etc.]
    JSONL[preds.jsonl]
  end
  subgraph beam [Elixir optional]
    PILOT[ml_train pilots]
  end
  TE --&amp;gt; Export
  OEE --&amp;gt; Export
  RE --&amp;gt; Export
  DIM --&amp;gt; Export
  Export[mix export.ml] --&amp;gt; CSV
  CSV --&amp;gt; PY
  CSV --&amp;gt; PILOT
  PY --&amp;gt; JSONL
  Import[mix import.ml.predictions] --&amp;gt; MP
  JSONL --&amp;gt; Import
  MP --&amp;gt; LV[MlPredictionsLive]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;&lt;strong&gt;Part 7 on dev.to&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;&lt;strong&gt;Part 8 on dev.to&lt;/strong&gt;&lt;/a&gt; built and modeled &lt;strong&gt;historical plant data&lt;/strong&gt;. &lt;strong&gt;This post&lt;/strong&gt; completes the ML ergonomics: &lt;strong&gt;standard CSV exports&lt;/strong&gt;, &lt;strong&gt;documented SQL&lt;/strong&gt;, &lt;strong&gt;optional BEAM-native pilots&lt;/strong&gt;, a &lt;strong&gt;simple import contract&lt;/strong&gt;, and a &lt;strong&gt;LiveView&lt;/strong&gt; to read &lt;code&gt;ml_predictions&lt;/code&gt;. Next: keeping the &lt;strong&gt;notification graph&lt;/strong&gt; healthy when rates spike (&lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;&lt;strong&gt;Part 10 on dev.to&lt;/strong&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Breck et al. (2017)&lt;/strong&gt; — &lt;em&gt;The ML Test Score&lt;/em&gt; — &lt;a href="https://research.google/pubs/pub46555/" rel="noopener noreferrer"&gt;Google Research&lt;/a&gt; (production-readiness rubric; not a tutorial).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axon / Scholar&lt;/strong&gt; — neural and classical ML in Elixir — &lt;a href="https://hexdocs.pm/axon" rel="noopener noreferrer"&gt;hexdocs.pm/axon&lt;/a&gt;, &lt;a href="https://hexdocs.pm/scholar" rel="noopener noreferrer"&gt;hexdocs.pm/scholar&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repo (PT)&lt;/strong&gt; — &lt;a href="//../../artigos/27_guia_pratico_treino_ml_smart_brewery.md"&gt;&lt;code&gt;docs/artigos/27_guia_pratico_treino_ml_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repo (code)&lt;/strong&gt; — &lt;a href="//../../apps/simulacoes_visuais/mix/tasks/simulacoes_visuais.export_ml.ex"&gt;&lt;code&gt;export_ml&lt;/code&gt; task&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/ml_dataset_export.ex"&gt;&lt;code&gt;ml_dataset_export.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/mix/tasks/simulacoes_visuais.ml_train.ex"&gt;&lt;code&gt;ml_train&lt;/code&gt; task&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/mix/tasks/simulacoes_visuais.ml_import_predictions.ex"&gt;&lt;code&gt;ml_import_predictions&lt;/code&gt; task&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/ml_predictions.ex"&gt;&lt;code&gt;ml_predictions.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais_web/live/ml_predictions_live.ex"&gt;&lt;code&gt;ml_predictions_live.ex&lt;/code&gt;&lt;/a&gt;. Expanded list: &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Published on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;ML on the digital twin: export, train pilots, and import predictions back into the app&lt;/a&gt; — tracked in &lt;a href="//../../devto_serie_pon_smart_brewery.md"&gt;&lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;Part 8 on dev.to — BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)&lt;/a&gt; · &lt;a href="//08_bi_without_mystery_dimensions_facts_power_bi.md"&gt;repo draft&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/when-notifications-explode-message-storms-deduplication-and-back-pressure-in-pon-34p4"&gt;Part 10 on dev.to — When notifications explode: message storms, deduplication, and back-pressure in PON&lt;/a&gt; · &lt;a href="//10_when_notifications_explode_message_storms_pon.md"&gt;repo draft&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>machinelearning</category>
      <category>timescaledb</category>
      <category>architecture</category>
    </item>
    <item>
      <title>BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:09:28 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj</link>
      <guid>https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj</guid>
      <description>&lt;p&gt;&lt;em&gt;If this helped you, you can &lt;a href="https://dev.to/matheuscamarques/support-with-a-coffee-2oa0"&gt;support the author with a coffee on dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 8 of 12&lt;/strong&gt; — &lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;Part 7 on dev.to — From simulation to storage: telemetry, Broadway/GenStage, and TimescaleDB&lt;/a&gt; · &lt;a href="//07_from_simulation_to_storage_telemetry_broadway_timescaledb.md"&gt;repo draft&lt;/a&gt; landed &lt;strong&gt;raw&lt;/strong&gt; rows in &lt;code&gt;telemetry_events&lt;/code&gt; and sibling tables for OEE, anomalies, and rule firings. Analysts rarely want fifty-seven wide columns per millisecond; they want a &lt;strong&gt;model&lt;/strong&gt; they can relate in a semantic layer and refresh on a schedule—or query live with guardrails.&lt;/p&gt;

&lt;p&gt;This post describes the &lt;strong&gt;star-style&lt;/strong&gt; objects in the Smart Brewery Postgres/Timescale database, how &lt;strong&gt;&lt;code&gt;SimulacoesVisuais.SmartBreweryBI&lt;/code&gt;&lt;/strong&gt; mirrors those queries for the in-app &lt;strong&gt;BI&lt;/strong&gt; tab, and how &lt;strong&gt;Power BI&lt;/strong&gt; (or any SQL BI tool) can connect safely. &lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;&lt;strong&gt;Part 9 on dev.to&lt;/strong&gt;&lt;/a&gt; picks up &lt;strong&gt;ML exports and prediction round-trips&lt;/strong&gt;. The dimensional vocabulary—&lt;strong&gt;facts&lt;/strong&gt; vs &lt;strong&gt;dimensions&lt;/strong&gt;, conformed keys, slowly changing attributes—follows the Kimball-style methodology (&lt;em&gt;The Data Warehouse Toolkit&lt;/em&gt;, 3rd ed., Wiley); our schema is a pragmatic subset for a twin demo, not a full enterprise bus matrix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not query only &lt;code&gt;telemetry_events&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;The hypertable is the source of truth for &lt;strong&gt;high-volume&lt;/strong&gt; numeric telemetry. For dashboards you usually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bucket&lt;/strong&gt; time with &lt;code&gt;time_bucket&lt;/code&gt; (TimescaleDB).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate&lt;/strong&gt; (&lt;code&gt;avg&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;) per bucket and signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join&lt;/strong&gt; to &lt;strong&gt;dimensions&lt;/strong&gt; so charts show “Fermentador A” instead of &lt;code&gt;fbe_06_internal_temp&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Continuous aggregates (&lt;code&gt;telemetry_events_1min&lt;/code&gt;, &lt;code&gt;_1h&lt;/code&gt;, &lt;code&gt;_1day&lt;/code&gt;) precompute that roll-up. Views on top expose a &lt;strong&gt;fact&lt;/strong&gt; shape with &lt;code&gt;fbe_id&lt;/code&gt; and &lt;code&gt;fact_name&lt;/code&gt; ready for foreign-key-style relationships in Power BI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dimension tables: equipment and variables
&lt;/h2&gt;

&lt;p&gt;The migration seeds two conformed dimensions used across reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# priv/repo/migrations/20250319200000_add_star_schema_dimensions_and_fact_views.exs (excerpt)&lt;/span&gt;
&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dim_equipamento_fbe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:fbe_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:fase_operacional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# INSERT … FBE_01 … FBE_11 with Portuguese operational labels&lt;/span&gt;

&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:dim_variaveis_mapeamento&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:fact_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:descricao&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:unidade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# INSERT … maps each fbe_XX_* fact atom string to human description + unit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A later migration adds &lt;strong&gt;&lt;code&gt;descricao_longa&lt;/code&gt;&lt;/strong&gt; (aligned with &lt;code&gt;FatoDescriptions&lt;/code&gt;) and &lt;strong&gt;&lt;code&gt;dim_regras&lt;/code&gt;&lt;/strong&gt;: metadata for PON rules (&lt;code&gt;r_01&lt;/code&gt; … &lt;code&gt;r_12&lt;/code&gt;) so &lt;code&gt;rule_events&lt;/code&gt; can be explained in plain language—see &lt;a href="//../../apps/simulacoes_visuais/priv/repo/migrations/20260320140000_power_bi_dim_context.exs"&gt;&lt;code&gt;20260320140000_power_bi_dim_context.exs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Calendar dimension and fact views
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dim_calendario&lt;/code&gt; is built from &lt;strong&gt;distinct buckets&lt;/strong&gt; of the 1-minute continuous aggregate—useful for time-intelligence style filters without scanning the full hypertable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- From the same migration (conceptual shape)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;dim_calendario&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt;
  &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ts_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;YEAR&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MONTH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;telemetry_events_1min&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fact views project CAGG columns into a stable star join key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;fact_telemetria_agregada_1min&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ts_bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;UPPER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUBSTRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fact_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;fbe_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;fact_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;value_float_avg&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;value_float_min&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;value_float_max&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;telemetry_events_1min&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parallel views exist for &lt;strong&gt;&lt;code&gt;_1h&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;_1day&lt;/code&gt;&lt;/strong&gt; grains. In Power BI, typical relationships are: fact → &lt;code&gt;dim_equipamento_fbe&lt;/code&gt; on &lt;code&gt;fbe_id&lt;/code&gt;, fact → &lt;code&gt;dim_variaveis_mapeamento&lt;/code&gt; on &lt;code&gt;fact_name&lt;/code&gt;, fact → &lt;code&gt;dim_calendario&lt;/code&gt; on &lt;code&gt;ts_bucket&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAGG latency vs freshness:&lt;/strong&gt; Continuous aggregates refresh on a policy schedule (see migrations under &lt;code&gt;priv/repo/migrations&lt;/code&gt; for &lt;code&gt;telemetry_events_1min&lt;/code&gt; and friends). That is ideal for &lt;strong&gt;import&lt;/strong&gt; or &lt;strong&gt;DirectQuery&lt;/strong&gt; models that scan fewer rows per question. When operators need charts that track the last minutes without waiting for the next rollup refresh, &lt;code&gt;SmartBreweryBI&lt;/code&gt; hits &lt;strong&gt;&lt;code&gt;telemetry_events&lt;/code&gt;&lt;/strong&gt; directly with &lt;code&gt;time_bucket&lt;/code&gt;—same SQL idioms, different freshness/cost trade-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical note:&lt;/strong&gt; &lt;code&gt;rule_events.regra_id&lt;/code&gt; is persisted as the string form of the numeric rule id (e.g. &lt;code&gt;"1"&lt;/code&gt;). &lt;code&gt;dim_regras&lt;/code&gt; uses keys like &lt;code&gt;"r_01"&lt;/code&gt;. For joins you may add a calculated column or a small bridge in SQL—worth standardizing in one place for your deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline analytics:&lt;/strong&gt; &lt;code&gt;mix export.ml&lt;/code&gt; (with TSDB enabled) emits CSV slices of telemetry, OEE, anomalies, rules, and dimension tables—useful for Python/R notebooks or training pipelines before &lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;&lt;strong&gt;Part 9 on dev.to&lt;/strong&gt;&lt;/a&gt;’s prediction import path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Event facts beside telemetry
&lt;/h2&gt;

&lt;p&gt;Telemetry is only one analytical slice. The same database holds &lt;strong&gt;&lt;code&gt;oee_snapshots&lt;/code&gt;&lt;/strong&gt; (availability / performance / quality components over time), &lt;strong&gt;&lt;code&gt;anomaly_events&lt;/code&gt;&lt;/strong&gt; (EMA-driven highlights aligned with the operator UI), and &lt;strong&gt;&lt;code&gt;rule_events&lt;/code&gt;&lt;/strong&gt; (which PON rule fired, with optional &lt;strong&gt;&lt;code&gt;case_id&lt;/code&gt;&lt;/strong&gt; for scenario tracking). None of these replace the star views—they &lt;strong&gt;complement&lt;/strong&gt; them: a single Power BI report can place OEE line charts next to aggregated temperature trends and a table of recent rule firings, all filtered by the same time slicer on &lt;code&gt;ts&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Native BI in Elixir: &lt;code&gt;SmartBreweryBI&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Phoenix app does not require Power BI for demos. &lt;strong&gt;&lt;code&gt;SmartBreweryBI&lt;/code&gt;&lt;/strong&gt; centralizes parameterized SQL: OEE cards, telemetry trends with &lt;code&gt;time_bucket&lt;/code&gt;, correlation pivots, synoptic averages by FBE, anomaly Pareto, and rule counts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.SmartBreweryBI (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;default_filters&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="s2"&gt;"window"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"24h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"granularity"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"1h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"fbe_id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"fact_name"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;dashboard_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window_to_hours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"window"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="ss"&gt;oee_cards:&lt;/span&gt; &lt;span class="n"&gt;oee_cards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;oee_trend:&lt;/span&gt; &lt;span class="n"&gt;oee_trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"granularity"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="ss"&gt;telemetry_trend:&lt;/span&gt; &lt;span class="n"&gt;telemetry_trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;cep_chart:&lt;/span&gt; &lt;span class="n"&gt;cep_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;correlation_points:&lt;/span&gt; &lt;span class="n"&gt;correlation_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;synoptic_status:&lt;/span&gt; &lt;span class="n"&gt;synoptic_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;anomaly_pareto:&lt;/span&gt; &lt;span class="n"&gt;anomaly_pareto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;rule_top:&lt;/span&gt; &lt;span class="n"&gt;rule_top&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;totals:&lt;/span&gt; &lt;span class="n"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;BI&lt;/strong&gt; view mode in &lt;code&gt;SmartBreweryLive&lt;/code&gt; (&lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;Part 6 on dev.to&lt;/a&gt;) loads this map when &lt;code&gt;:tsdb_enabled&lt;/code&gt; is true—same warehouse, two consumers (LiveView vs external BI).&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;near-real-time&lt;/strong&gt; trends, the module prefers querying &lt;strong&gt;&lt;code&gt;telemetry_events&lt;/code&gt;&lt;/strong&gt; with &lt;code&gt;time_bucket&lt;/code&gt; for selected panels so results are not delayed by CAGG refresh policies; aggregated &lt;strong&gt;views&lt;/strong&gt; remain the sweet spot for heavier report workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Governance: read-only database role
&lt;/h2&gt;

&lt;p&gt;The migration &lt;strong&gt;&lt;code&gt;AddPowerbiAnalyticsReadonlyRole&lt;/code&gt;&lt;/strong&gt; creates &lt;strong&gt;&lt;code&gt;powerbi_analytics&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;LOGIN&lt;/code&gt;, &lt;code&gt;CONNECT&lt;/code&gt; to the database, &lt;code&gt;USAGE&lt;/code&gt; on &lt;code&gt;public&lt;/code&gt;, &lt;strong&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/strong&gt; on tables and default privileges for future tables. The BI connector should &lt;strong&gt;not&lt;/strong&gt; use the application superuser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Excerpt from 20250319400000_add_powerbi_analytics_readonly_role.exs&lt;/span&gt;
&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CREATE ROLE powerbi_analytics WITH LOGIN PASSWORD %L'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;escaped&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"GRANT USAGE ON SCHEMA public TO powerbi_analytics;"&lt;/span&gt;
&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"GRANT SELECT ON ALL TABLES IN SCHEMA public TO powerbi_analytics;"&lt;/span&gt;
&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO powerbi_analytics;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;strong&gt;&lt;code&gt;POWERBI_ANALYTICS_PASSWORD&lt;/code&gt;&lt;/strong&gt; before migrating in real environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optional: push rows to Power BI REST
&lt;/h2&gt;

&lt;p&gt;When Import/DirectQuery is not enough for a “live tile” experiment, &lt;strong&gt;&lt;code&gt;PowerBIPushSink&lt;/code&gt;&lt;/strong&gt; can enqueue rows &lt;strong&gt;after&lt;/strong&gt; they are persisted, throttle HTTP calls, and POST to the &lt;a href="https://learn.microsoft.com/en-us/rest/api/power-bi/push-datasets" rel="noopener noreferrer"&gt;Push datasets API&lt;/a&gt;. It is &lt;strong&gt;off by default&lt;/strong&gt; (&lt;code&gt;:power_bi_push&lt;/code&gt; → &lt;code&gt;enabled: false&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.SmartBrewery.PowerBIPushSink — encode_row/2 (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;encode_row&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;ts:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;fact_name:&lt;/span&gt; &lt;span class="n"&gt;fact_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value_float:&lt;/span&gt; &lt;span class="n"&gt;vf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value_int:&lt;/span&gt; &lt;span class="n"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value_str:&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;fact_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fact_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="s2"&gt;"ts"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime_to_iso8601&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;"fact_name"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fact_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"value_float"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"value_int"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"value_str"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:include_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;FatoDescriptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descricao_bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fact_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"descricao"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operational guidance (latency vs license, DirectQuery vs push) lives in &lt;strong&gt;&lt;a href="//../../docs/power-bi-realtime.md"&gt;&lt;code&gt;docs/power-bi-realtime.md&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; at the repo root.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verify the model: &lt;code&gt;mix verify.bi&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Mix task runs &lt;strong&gt;&lt;code&gt;SmartBreweryBI.run_query_diagnostics/0&lt;/code&gt;&lt;/strong&gt;: each diagnostic query returns &lt;code&gt;:ok&lt;/code&gt; with a small &lt;code&gt;LIMIT&lt;/code&gt; sample, or an error you can fix before publishing reports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From apps/simulacoes_visuais (TSDB enabled)&lt;/span&gt;
mix verify.bi
&lt;span class="c"&gt;# alias: mix simulacoes_visuais.verify_bi_queries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero rows in a sample is still &lt;strong&gt;success&lt;/strong&gt; if your simulation was off—the README stresses distinguishing “query broken” from “empty time window”.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture sketch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  subgraph warehouse [Postgres / TimescaleDB]
    TE[telemetry_events]
    CAGG[telemetry_events_1min / 1h / 1day]
    F1[fact_telemetria_agregada_*]
    D1[dim_equipamento_fbe]
    D2[dim_variaveis_mapeamento]
    DC[dim_calendario]
    DR[dim_regras]
    OEE[oee_snapshots]
    RE[rule_events]
  end
  subgraph consumers [Consumers]
    LV[SmartBrewery BI tab]
    PBI[Power BI DirectQuery / Import]
    PUSH[Power BI Push optional]
  end
  TE --&amp;gt; CAGG
  CAGG --&amp;gt; F1
  F1 --&amp;gt; D1
  F1 --&amp;gt; D2
  F1 --&amp;gt; DC
  RE --&amp;gt; DR
  LV --&amp;gt; SmartBreweryBI
  SmartBreweryBI --&amp;gt; TE
  SmartBreweryBI --&amp;gt; OEE
  PBI --&amp;gt; F1
  PBI --&amp;gt; D1
  TE --&amp;gt; PUSH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;&lt;strong&gt;Part 7 on dev.to&lt;/strong&gt;&lt;/a&gt; wrote fast &lt;strong&gt;facts&lt;/strong&gt;; &lt;strong&gt;this post&lt;/strong&gt; makes them &lt;strong&gt;legible&lt;/strong&gt;: conformed dimensions, CAGG-backed fact views, a read-only role, optional push, and a single Elixir module that encodes the same SQL the UI and &lt;code&gt;mix verify.bi&lt;/code&gt; rely on. Next, we close the loop with &lt;strong&gt;ML&lt;/strong&gt; on top of these exports (&lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;&lt;strong&gt;Part 9 on dev.to&lt;/strong&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kimball, R.; Ross, M.&lt;/strong&gt; — &lt;em&gt;The Data Warehouse Toolkit&lt;/em&gt; (3rd ed.) — dimensional modeling, star schema, fact/grain discipline (Wiley).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TimescaleDB&lt;/strong&gt; — continuous aggregates as rollups — &lt;a href="https://docs.timescale.com/use-timescale/latest/continuous-aggregates/" rel="noopener noreferrer"&gt;docs.timescale.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Learn&lt;/strong&gt; — Power BI REST / &lt;a href="https://learn.microsoft.com/en-us/rest/api/power-bi/push-datasets" rel="noopener noreferrer"&gt;push datasets&lt;/a&gt; (see also in-repo &lt;a href="//../../docs/power-bi-realtime.md"&gt;&lt;code&gt;power-bi-realtime.md&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; — roles, &lt;code&gt;GRANT&lt;/code&gt;, least privilege — &lt;a href="https://www.postgresql.org/docs/current/sql-grant.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repo&lt;/strong&gt; — &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/smart_brewery_bi.ex"&gt;&lt;code&gt;smart_brewery_bi.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/smart_brewery/power_bi_push_sink.ex"&gt;&lt;code&gt;power_bi_push_sink.ex&lt;/code&gt;&lt;/a&gt;, migrations &lt;a href="//../../apps/simulacoes_visuais/priv/repo/migrations/20250319200000_add_star_schema_dimensions_and_fact_views.exs"&gt;&lt;code&gt;20250319200000_add_star_schema_dimensions_and_fact_views.exs&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/priv/repo/migrations/20250319400000_add_powerbi_analytics_readonly_role.exs"&gt;&lt;code&gt;20250319400000_add_powerbi_analytics_readonly_role.exs&lt;/code&gt;&lt;/a&gt;. Expanded list: &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Published on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)&lt;/a&gt; — tracked in &lt;a href="//../../devto_serie_pon_smart_brewery.md"&gt;&lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;Part 7 on dev.to — From simulation to storage: telemetry, Broadway/GenStage, and TimescaleDB&lt;/a&gt; · &lt;a href="//07_from_simulation_to_storage_telemetry_broadway_timescaledb.md"&gt;repo draft&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/ml-on-the-digital-twin-export-train-pilots-and-import-predictions-back-into-the-app-207i"&gt;Part 9 on dev.to — ML on the digital twin: export, train pilots, and import predictions back into the app&lt;/a&gt; · &lt;a href="//09_ml_digital_twin_export_train_import_predictions.md"&gt;repo draft&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>timescaledb</category>
      <category>analytics</category>
      <category>architecture</category>
    </item>
    <item>
      <title>From simulation to storage: telemetry, Broadway/GenStage, and TimescaleDB</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:06:08 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762</link>
      <guid>https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762</guid>
      <description>&lt;p&gt;&lt;em&gt;If this helped you, you can &lt;a href="https://dev.to/matheuscamarques/support-with-a-coffee-2oa0"&gt;support the author with a coffee on dev.to&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  From simulation to storage: telemetry, Broadway/GenStage, and TimescaleDB
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Part 7 of 12&lt;/strong&gt; — &lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;Part 6 on dev.to — Phoenix LiveView in real time: an operations UI on top of a rules engine&lt;/a&gt; · &lt;a href="//06_phoenix_liveview_operations_ui_rules_engine.md"&gt;repo draft&lt;/a&gt; showed how &lt;strong&gt;LiveView&lt;/strong&gt; stays responsive: batched PubSub messages and a second throttle before touching assigns. This post goes &lt;strong&gt;one layer deeper&lt;/strong&gt;: what happens when those same &lt;code&gt;Fato.atualizar/2&lt;/code&gt; notifications must also become &lt;strong&gt;durable rows&lt;/strong&gt; in PostgreSQL with the &lt;strong&gt;TimescaleDB&lt;/strong&gt; extension—without blocking the rule processes or the UI.&lt;/p&gt;

&lt;p&gt;We focus on the &lt;strong&gt;&lt;code&gt;simulacoes_visuais&lt;/code&gt;&lt;/strong&gt; Phoenix app: a &lt;strong&gt;GenStage&lt;/strong&gt; producer, a &lt;strong&gt;Broadway&lt;/strong&gt; pipeline with configurable batching, an asynchronous &lt;strong&gt;GenServer&lt;/strong&gt; writer, and &lt;strong&gt;hypertables&lt;/strong&gt; with retention. &lt;strong&gt;Dimensional BI and Power BI&lt;/strong&gt; consumption are the subject of &lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;&lt;strong&gt;Part 8 on dev.to&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a separate persistence path?
&lt;/h2&gt;

&lt;p&gt;The PON engine (&lt;code&gt;tec0301_pon&lt;/code&gt;) is optimized for &lt;strong&gt;reactive evaluation&lt;/strong&gt;: &lt;code&gt;Registry&lt;/code&gt;, &lt;code&gt;Fato&lt;/code&gt; processes, and rule notifications. Long-running &lt;code&gt;Repo.insert_all/3&lt;/code&gt; calls do not belong on that hot path. The Phoenix application therefore acts as a &lt;strong&gt;sink&lt;/strong&gt;, using the same back-pressure vocabulary as &lt;strong&gt;GenStage&lt;/strong&gt; (demand-driven stages) and &lt;strong&gt;Broadway&lt;/strong&gt; (batched consumers)—see the &lt;a href="https://hexdocs.pm/gen_stage" rel="noopener noreferrer"&gt;GenStage&lt;/a&gt; and &lt;a href="https://hexdocs.pm/broadway" rel="noopener noreferrer"&gt;Broadway&lt;/a&gt; documentation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Subscribe&lt;/strong&gt; to the engine’s notification bus (via &lt;code&gt;SmartBreweryFactBroadcaster&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shape&lt;/strong&gt; traffic with back-pressure-friendly stages (GenStage + Broadway, with a GenServer fallback batcher).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; merged batches to PubSub for subscribers (EMA, OEE, downstream services).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persist&lt;/strong&gt; only when &lt;code&gt;:tsdb_enabled&lt;/code&gt; is true, through a &lt;strong&gt;queued async writer&lt;/strong&gt; so database latency does not stall the pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Running &lt;code&gt;mix run examples/smart_brewery_simulacao.exs&lt;/code&gt; at the monorepo root still exercises &lt;strong&gt;only&lt;/strong&gt; &lt;code&gt;:tec0301_pon&lt;/code&gt;; it does &lt;strong&gt;not&lt;/strong&gt; fill &lt;code&gt;telemetry_events&lt;/code&gt;. Only this app, with TSDB enabled, owns the warehouse path—see &lt;a href="//../../apps/simulacoes_visuais/README.md"&gt;&lt;code&gt;apps/simulacoes_visuais/README.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning the warehouse on: supervision and environment
&lt;/h2&gt;

&lt;p&gt;At startup, &lt;code&gt;SimulacoesVisuais.Application&lt;/code&gt; reads &lt;code&gt;SIMULACOES_TSDB_ENABLED&lt;/code&gt; from the OS environment (falling back to &lt;code&gt;Application.get_env(:simulacoes_visuais, :tsdb_enabled, false)&lt;/code&gt;). When enabled, it starts &lt;strong&gt;&lt;code&gt;Repo&lt;/code&gt;&lt;/strong&gt; plus dedicated writers &lt;strong&gt;before&lt;/strong&gt; the HTTP endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# apps/simulacoes_visuais/lib/simulacoes_visuais/application.ex (excerpt)&lt;/span&gt;
&lt;span class="n"&gt;tsdb_children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tsdb_enabled&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TelemetryAsyncWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;OeeSnapshotWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AnomalyEventWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RuleEventWriter&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# … PubSub, bridge, TelemetryPipeline, batchers, Monte Carlo, …&lt;/span&gt;
    &lt;span class="n"&gt;tsdb_children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;SimulacoesVisuaisWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Endpoint&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fact telemetry&lt;/strong&gt; flows through &lt;strong&gt;&lt;code&gt;TelemetryPipeline&lt;/code&gt;&lt;/strong&gt; (always started) and reaches &lt;code&gt;TelemetryAsyncWriter&lt;/code&gt; only when the flag is on. &lt;strong&gt;OEE, anomalies, and rule firings&lt;/strong&gt; use separate PubSub topics and writers that also depend on &lt;code&gt;Repo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Convenience in dev: &lt;code&gt;mix dev.tsdb&lt;/code&gt; / &lt;code&gt;iex -S mix dev.tsdb&lt;/code&gt; aligns with the README so Postgres/TimescaleDB and Monte Carlo can run together for local demos.&lt;/p&gt;




&lt;h2&gt;
  
  
  GenStage producer: demand-driven ingest
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SmartBreweryFactBroadcaster&lt;/code&gt; resolves the Broadway producer and sends &lt;code&gt;GenStage.cast(producer_pid, {:event, nome_fato, novo_valor})&lt;/code&gt;. The producer keeps a bounded &lt;strong&gt;queue&lt;/strong&gt;; when the queue is full, the oldest event is dropped—trading completeness for &lt;strong&gt;bounded memory&lt;/strong&gt; under burst load.&lt;/p&gt;

&lt;p&gt;Critically, when Broadway has already requested demand with an empty queue, &lt;code&gt;handle_cast/2&lt;/code&gt; &lt;strong&gt;drains immediately&lt;/strong&gt; so events are not stuck until the next &lt;code&gt;handle_demand&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.SmartBrewery.TelemetryProducer (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nome_fato&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max_queue:&lt;/span&gt; &lt;span class="n"&gt;max_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;pending_demand:&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;new_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ss"&gt;:queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;max_queue&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="ss"&gt;:value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_dropped&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="ss"&gt;:queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;nome_fato&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="ss"&gt;:queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;nome_fato&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drained_queue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;take_from_queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new_pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="n"&gt;drained_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;pending_demand:&lt;/span&gt; &lt;span class="n"&gt;new_pending&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="n"&gt;new_queue&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the producer PID cannot be resolved, the broadcaster falls back to &lt;strong&gt;&lt;code&gt;SmartBreweryTelemetryBatcher&lt;/code&gt;&lt;/strong&gt;; that path must still call &lt;code&gt;TelemetryAsyncWriter.cast_batch/1&lt;/code&gt; on flush so TSDB does not go silent while PubSub keeps working—a parity fix called out in the batcher’s moduledoc.&lt;/p&gt;




&lt;h2&gt;
  
  
  Broadway: processors, batchers, and one merged batch
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TelemetryPipeline&lt;/code&gt; uses Broadway’s &lt;strong&gt;batcher&lt;/strong&gt; stage to merge many &lt;code&gt;{fact, value}&lt;/code&gt; messages into a &lt;strong&gt;single map&lt;/strong&gt; per batch (last write wins per key), then fan-out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.SmartBrewery.TelemetryPipeline (excerpt)&lt;/span&gt;
&lt;span class="no"&gt;Broadway&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;producer:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="ss"&gt;module:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;TelemetryProducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:smart_brewery_telemetry_producer&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="ss"&gt;transformer:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;processors:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;concurrency:&lt;/span&gt; &lt;span class="n"&gt;processor_concurrency&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
  &lt;span class="ss"&gt;batchers:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="ss"&gt;concurrency:&lt;/span&gt; &lt;span class="n"&gt;batcher_concurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;batch_size:&lt;/span&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;batch_timeout:&lt;/span&gt; &lt;span class="n"&gt;batch_timeout&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_batch_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;merge_message_data&lt;/span&gt;&lt;span class="p"&gt;(%{})&lt;/span&gt;
    &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="ss"&gt;:telemetry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:simulacoes_visuais&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:smart_brewery_telemetry_batcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:flush&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;updates_count:&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;buffer_size_before:&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
      &lt;span class="p"&gt;%{}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"smart_brewery:fatos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;push_ema_numeric_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:simulacoes_visuais&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tsdb_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TelemetryAsyncWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So each batch: &lt;strong&gt;metrics&lt;/strong&gt; (LiveDashboard can chart flush sizes), &lt;strong&gt;PubSub&lt;/strong&gt; on &lt;code&gt;smart_brewery:fatos&lt;/code&gt;, &lt;strong&gt;EMA&lt;/strong&gt; updates for anomaly heuristics, and optionally &lt;strong&gt;async persist&lt;/strong&gt;. &lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;Part 6 on dev.to&lt;/a&gt;’s LiveView listens on a &lt;strong&gt;different&lt;/strong&gt; topic (&lt;code&gt;smart_brewery:liveview_batch&lt;/code&gt;) fed by &lt;code&gt;LiveViewEventBatcher&lt;/code&gt;—same simulation, two consumption speeds.&lt;/p&gt;




&lt;h2&gt;
  
  
  TelemetryAsyncWriter: decouple DB latency from Broadway
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cast_batch/1&lt;/code&gt; enqueues whole batches; a bounded queue drops the &lt;strong&gt;oldest&lt;/strong&gt; batch when overloaded, keeping &lt;strong&gt;recent&lt;/strong&gt; telemetry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.SmartBrewery.TelemetryAsyncWriter (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;cast_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:flush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="ss"&gt;:flush&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TelemetryEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changesets_from_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TelemetryEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PowerBIPushSink&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Broadway never waits on PostgreSQL; the writer serializes inserts and can hook optional push sinks for external analytics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Row shape: from Elixir values to columns
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TelemetryEvent.changesets_from_batch/2&lt;/code&gt; maps &lt;code&gt;{atom_name, value}&lt;/code&gt; tuples into flat maps suitable for &lt;code&gt;insert_all/3&lt;/code&gt;—numbers as &lt;code&gt;value_float&lt;/code&gt;, booleans split across &lt;code&gt;value_int&lt;/code&gt; / &lt;code&gt;value_str&lt;/code&gt;, other atoms as strings—so continuous aggregates and ML exports can choose the column that fits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.TelemetryEvent (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;to_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;%{&lt;/span&gt;
    &lt;span class="ss"&gt;ts:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;fact_name:&lt;/span&gt; &lt;span class="no"&gt;Atom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;value_float:&lt;/span&gt; &lt;span class="n"&gt;to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;value_int:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;value_str:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;inserted_at:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;updated_at:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Rule events: same bus, different writer
&lt;/h2&gt;

&lt;p&gt;Rule firings are published to &lt;code&gt;smart_brewery:regras&lt;/code&gt; (for LiveView and others). &lt;strong&gt;&lt;code&gt;RuleEventWriter&lt;/code&gt;&lt;/strong&gt; subscribes, buffers up to a max pending count, and &lt;strong&gt;&lt;code&gt;insert_all&lt;/code&gt;&lt;/strong&gt; into &lt;code&gt;rule_events&lt;/code&gt; in chunks—again isolating database work from the rule processes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SimulacoesVisuais.SmartBrewery.RuleEventWriter (excerpt)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_info&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:regra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regra_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:regra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regra_id&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="c1"&gt;# … trim to max_pending …&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="ss"&gt;:drain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="ss"&gt;pending:&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;draining:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OEE snapshots and anomaly rows follow the same pattern (dedicated modules in &lt;code&gt;SmartBrewery.*&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  TimescaleDB: hypertable and retention
&lt;/h2&gt;

&lt;p&gt;The initial migration enables the extension, creates &lt;code&gt;telemetry_events&lt;/code&gt;, and promotes the table to a &lt;strong&gt;hypertable&lt;/strong&gt; partitioned on &lt;code&gt;ts&lt;/code&gt;, with a &lt;strong&gt;7-day retention policy&lt;/strong&gt; on raw rows (adjustable in your deployment):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# priv/repo/migrations/20250318000000_create_telemetry_events.exs (excerpt)&lt;/span&gt;
&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:telemetry_events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:fact_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:value_float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:float&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:value_int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:value_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;type:&lt;/span&gt; &lt;span class="ss"&gt;:utc_datetime_usec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"SELECT create_hypertable('telemetry_events', 'ts', if_not_exists =&amp;gt; true);"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"SELECT add_retention_policy('telemetry_events', INTERVAL '7 days');"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later migrations add &lt;strong&gt;continuous aggregates&lt;/strong&gt; (for example &lt;code&gt;telemetry_events_1min&lt;/code&gt;, hourly and daily rollups) and &lt;strong&gt;compression&lt;/strong&gt; policies on the hypertable. Those objects sit entirely in the database: the Elixir app keeps writing &lt;strong&gt;raw&lt;/strong&gt; &lt;code&gt;telemetry_events&lt;/code&gt; rows; BI queries and &lt;code&gt;mix export.ml&lt;/code&gt; can target either the base table or the CAGGs depending on grain and export flags (&lt;code&gt;--no-cagg&lt;/code&gt;, &lt;code&gt;--no-cagg-1h-1day&lt;/code&gt; in the Mix task help).&lt;/p&gt;




&lt;h2&gt;
  
  
  Fallback batcher and TSDB parity
&lt;/h2&gt;

&lt;p&gt;When the Broadway producer is unavailable, &lt;code&gt;SmartBreweryTelemetryBatcher&lt;/code&gt; still merges updates and broadcasts &lt;code&gt;{:batch, list}&lt;/code&gt; to &lt;code&gt;smart_brewery:fatos&lt;/code&gt;. The flush handler mirrors the pipeline’s TSDB branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:simulacoes_visuais&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tsdb_enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;SimulacoesVisuais&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SmartBrewery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TelemetryAsyncWriter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cast_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, operators would still see PubSub-driven UI signals while &lt;strong&gt;&lt;code&gt;telemetry_events&lt;/code&gt; stayed flat&lt;/strong&gt;—a classic split-brain symptom when debugging ingest.&lt;/p&gt;




&lt;h2&gt;
  
  
  End-to-end picture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
  subgraph engine [tec0301_pon]
    F[Fato]
  end
  subgraph ingest [simulacoes_visuais ingest]
    FB[SmartBreweryFactBroadcaster]
    P[TelemetryProducer GenStage]
    BW[TelemetryPipeline Broadway]
  end
  subgraph fanout [Fan-out]
    PS[PubSub smart_brewery:fatos]
    EMA[EMA / OEE inputs]
    W[TelemetryAsyncWriter]
  end
  subgraph db [PostgreSQL + TimescaleDB]
    T[(telemetry_events hypertable)]
    R[(rule_events etc.)]
  end
  F --&amp;gt; FB
  FB --&amp;gt;|GenStage.cast| P
  P --&amp;gt; BW
  BW --&amp;gt; PS
  BW --&amp;gt; EMA
  BW --&amp;gt;|cast_batch when tsdb_enabled| W
  W --&amp;gt; T
  RN[RegraNotifier] --&amp;gt;|PubSub regras| RW[RuleEventWriter]
  RW --&amp;gt; R
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Operating and verifying
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mix verify.tsdb&lt;/code&gt;&lt;/strong&gt; — checks extension, row counts, recent timestamps, and whether the Broadway producer and writers are alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless simulation&lt;/strong&gt; — &lt;code&gt;SIMULACOES_TSDB_ENABLED=true AUTO_START_MONTE_CARLO=true mix phx.server&lt;/code&gt; (from the app directory) populates tables for ML export without opening the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention&lt;/strong&gt; — &lt;code&gt;mix simulacoes_visuais.retention --days N&lt;/code&gt; (from the &lt;code&gt;apps/simulacoes_visuais&lt;/code&gt; directory, TSDB enabled) replaces the hypertable retention policy without editing migrations—the default migration still installs a 7-day window.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;&lt;strong&gt;Part 6 on dev.to&lt;/strong&gt;&lt;/a&gt; optimized &lt;strong&gt;perception&lt;/strong&gt; (UI). &lt;strong&gt;This post&lt;/strong&gt; optimizes &lt;strong&gt;durability&lt;/strong&gt;: GenStage demand, Broadway batching, bounded queues, async &lt;code&gt;insert_all&lt;/code&gt;, and a time-series physical model. The engine stays reactive; the warehouse absorbs load on its own terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broadway&lt;/strong&gt; — batching, processors — &lt;a href="https://hexdocs.pm/broadway" rel="noopener noreferrer"&gt;hexdocs.pm/broadway&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GenStage&lt;/strong&gt; — producers/consumers, demand — &lt;a href="https://hexdocs.pm/gen_stage" rel="noopener noreferrer"&gt;hexdocs.pm/gen_stage&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TimescaleDB&lt;/strong&gt; — hypertables, continuous aggregates, retention — &lt;a href="https://docs.timescale.com/" rel="noopener noreferrer"&gt;docs.timescale.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry&lt;/strong&gt; — &lt;code&gt;telemetry&lt;/code&gt; events in BEAM apps — &lt;a href="https://hexdocs.pm/telemetry" rel="noopener noreferrer"&gt;hexdocs.pm/telemetry&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this repo&lt;/strong&gt; — &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/application.ex"&gt;&lt;code&gt;application.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/smart_brewery/telemetry_pipeline.ex"&gt;&lt;code&gt;telemetry_pipeline.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/smart_brewery/telemetry_producer.ex"&gt;&lt;code&gt;telemetry_producer.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/smart_brewery/telemetry_async_writer.ex"&gt;&lt;code&gt;telemetry_async_writer.ex&lt;/code&gt;&lt;/a&gt;, &lt;a href="//../../apps/simulacoes_visuais/lib/simulacoes_visuais/smart_brewery_telemetry_batcher.ex"&gt;&lt;code&gt;smart_brewery_telemetry_batcher.ex&lt;/code&gt;&lt;/a&gt;, migration &lt;a href="//../../apps/simulacoes_visuais/priv/repo/migrations/20250318000000_create_telemetry_events.exs"&gt;&lt;code&gt;20250318000000_create_telemetry_events.exs&lt;/code&gt;&lt;/a&gt;; &lt;a href="//../../performance-dev.md"&gt;&lt;code&gt;docs/performance-dev.md&lt;/code&gt;&lt;/a&gt;. Expanded list: &lt;a href="https://dev.to/matheuscamarques/bibliography-pon-smart-brewery-devto-series-en-drafts-58a9"&gt;Bibliography on dev.to — PON + Smart Brewery series (EN drafts)&lt;/a&gt; · &lt;a href="//../BIBLIOGRAPHY_PON_SERIES.md"&gt;repo draft&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Published on dev.to:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/from-simulation-to-storage-telemetry-broadwaygenstage-and-timescaledb-762"&gt;From simulation to storage: telemetry, Broadway/GenStage, and TimescaleDB&lt;/a&gt; — tracked in &lt;a href="//../../devto_serie_pon_smart_brewery.md"&gt;&lt;code&gt;docs/devto_serie_pon_smart_brewery.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/phoenix-liveview-in-real-time-an-operations-ui-on-top-of-a-rules-engine-17ci"&gt;Part 6 on dev.to — Phoenix LiveView in real time: an operations UI on top of a rules engine&lt;/a&gt; · &lt;a href="//06_phoenix_liveview_operations_ui_rules_engine.md"&gt;repo draft&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to/matheuscamarques/bi-without-mystery-dimensions-facts-and-consuming-the-data-eg-power-bi-54aj"&gt;Part 8 on dev.to — BI without mystery: dimensions, facts, and consuming the data (e.g. Power BI)&lt;/a&gt; · &lt;a href="//08_bi_without_mystery_dimensions_facts_power_bi.md"&gt;repo draft&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>otp</category>
      <category>timescaledb</category>
      <category>broadway</category>
    </item>
  </channel>
</rss>
