Part 3 · 1 chapters · ~8 min

Processes, OTP and Supervision

spawn, send and receive, links and monitors, "let it crash", GenServer for stateful processes, Supervisor strategies (one_for_one, rest_for_one, one_for_all), Applications and supervision trees, Registry and DynamicSupervisor, Task and Task.Supervisor, Agent, timeouts and backpressure, and measured process and message costs.

4

Measured, then structured

code
{us, pids} = :timer.tc(fn -> for _ <- 1..1_000_000, do: spawn(fn -> receive do :stop -> :ok end end) end)
# 1,000,000 processes in 3,806 ms · ~2,648 bytes each
# ping-pong over 100,000 round trips: 1,153 ns each

defmodule PayoutTracker do
  use GenServer                                     # one process per in-flight payout
  def start_link(id), do: GenServer.start_link(__MODULE__, id, name: via(id))
  def init(id), do: {:ok, %{id: id, state: :submitted, polls: 0}, {:continue, :poll}}
  def handle_continue(:poll, s), do: poll(s)
  def handle_info(:poll, s), do: poll(s)
  defp poll(s) do
    case Rails.status(s.id) do
      {:ok, :settled} -> Ledger.settle(s.id); {:stop, :normal, s}
      {:ok, :pending} -> Process.send_after(self(), :poll, backoff(s.polls)); {:noreply, %{s | polls: s.polls + 1}}
      {:error, _} -> raise "rail unavailable"       # crash: the supervisor restarts with fresh state
    end
  end
end

children = [{Registry, keys: :unique, name: Payouts.Registry}, {DynamicSupervisor, name: Payouts.Sup, strategy: :one_for_one}]
Supervisor.start_link(children, strategy: :one_for_one)
DynamicSupervisor.start_child(Payouts.Sup, {PayoutTracker, "po_81"})

Let it crash does not mean ignoring errors: it means writing the happy path clearly, letting unexpected failures kill the process, and relying on supervisors to restart from a known good state, while durable facts live in the database (the ledger), never only in process memory.

BEAM PROCESSES, MEASURED
Elixir 1.19.5 on OTP 29, Apple M3 Pro, 12 schedulers
spawn one process3.8 µsmessage round trip (ping-pong)1.15 µsmemory per idle process~2.6 KB
swipe the figure sideways, or tap expand for full screen
1/4
cheap processes
Spawning 1,000,000 processes that wait for a message took 3.8 seconds, 3.8 µs each, and used about 2.6 KB per process (2.5 GiB total).
a million processes3.8 µs, ~2.6 KB each