Slide 1

Slide 1 text

scalable dist-sys from the grounds up in Elixir

Slide 2

Slide 2 text

whoami udit @ nilenso

Slide 3

Slide 3 text

agenda why elixir/erlang under the hood build you a live game for great good build you a better live game for greater good

Slide 4

Slide 4 text

why elixir / erlang

Slide 5

Slide 5 text

why dist-sys are hard? state computation reliability order . . .

Slide 6

Slide 6 text

why elixir/erlang asynchronous message passing no sharing fault tolerance

Slide 7

Slide 7 text

why elixir/erlang distributed out of the box primitives for concurrency

Slide 8

Slide 8 text

under the hood

Slide 9

Slide 9 text

beam Bjorn’s erlang abstract machine bytecode ~ erlang / Elixir / Gleam / LFE etc

Slide 10

Slide 10 text

process light weight - green thread communicate via message passing single threaded

Slide 11

Slide 11 text

process process control board heap stack

Slide 12

Slide 12 text

process garbage collection when: heap meets stack runs on process schedule compaction vs full copy

Slide 13

Slide 13 text

process schedulers process queues soft pre-emptive

Slide 14

Slide 14 text

process defmodule RcDemo.Echo do def start() do receive do :exit -> IO.puts("Shutting down") x -> IO.inspect(x, label: "Received Message on #{inspect(self())}: ") start() end end end

Slide 15

Slide 15 text

process registration noname :local :global pg2 swarm Registry (elixir)

Slide 16

Slide 16 text

supervisor reliability monitor other process

Slide 17

Slide 17 text

gen_server generic server better abstraction over state still a process defmodule RcDemo.EchoGenServer do use GenServer def start(), do: GenServer.start(__MODULE__, nil) def init(nil), do: {:ok, %{}} def handle_cast(message, state) do IO.inspect(message, label: "Cast:") {:noreply, state} end def handle_call(message, from, state) do IO.inspect(message, label: "Call:") :timer.sleep(2000) {:reply, :called, state} end end

Slide 18

Slide 18 text

gen_server call GenServer.call(pid, :hi, 1000) cast GenServer.cast(pid, :hello) info send(pid, :info)

Slide 19

Slide 19 text

distributed nodes fully connected mesh network heartbeat

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

a live game for great good

Slide 22

Slide 22 text

listener def receive_message(socket, receive_callback) do case :gen_tcp.recv(socket, 0) do {:ok, message} -> :gen_tcp.send(socket, "Message received\n") {m, f, a} = receive_callback apply(m, f, a ++ [message]) receive_message(socket, receive_callback) _otherwise -> IO.inspect("Shutting down the socket") end end def listen(port, accept_callback) do {:ok, socket} = :gen_tcp.listen(port, [:binary, reuseaddr: true]) accept_connection(socket, accept_callback) end def accept_connection(listen_socket, accept_callback) do {:ok, accept_socket} = :gen_tcp.accept(listen_socket) spawn(fn -> {m, f, a} = accept_callback receive_callback = apply(m, f, a ++ [accept_socket]) receive_message(accept_socket, receive_callback) end) accept_connection(listen_socket, accept_callback) end

Slide 23

Slide 23 text

one for all defmodule RcDemo.Game.OneToAll.SingleActor do def start (port), do: GenServer.start_link( __MODULE__, port, name: {:global, Single}) def init(port) do spawn(fn -> TcpListner.listen(port, {__MODULE__, :noop, []}) end) {:ok, %{}} end def noop(_), do: {__MODULE__, :incoming, []} def incoming(message), do: GenServer.cast(Single, {:incoming, message}) def handle_cast({:incoming, message}, state) do IO.inspect(message, label: "Received in GenServer with pid #{inspect self()}") {:noreply, state} end

Slide 24

Slide 24 text

one for all

Slide 25

Slide 25 text

one for all 1 : n single thread of execution for all incoming message message queue build up no fault tolerance

Slide 26

Slide 26 text

one for one defmodule RcDemo.Game.OneToOne.Master do def start(port), do: GenServer.start_link(__MODULE__, port, name: Master) def init(port) do spawn(fn -> TcpListner.listen(port, {__MODULE__, :new_connection, []}) end) {:ok, %{}} end def new_connection(socket) do {:ok, pid} = Worker.start(socket) {Worker, :incoming, [pid]} end end defmodule RcDemo.Game.OneToOne.Worker do def start(_socket), do: GenServer.start_link(__MODULE__, []) def init(_), do: {:ok, %{}} def incoming(pid, message), do: GenServer.cast(pid, {:incoming, message}) def handle_cast({:incoming, message}, state) do IO.inspect(message, label: "Received in Worker with pid: #{inspect self()}") {:noreply, state} end end

Slide 27

Slide 27 text

one for one 1 : 1 can process responses from players faster large process queue on schedulers

Slide 28

Slide 28 text

n to m defmodule RcDemo.Game.Balanced.Master do def init(port) do worker_pids = Enum.reduce(1..2, [], fn _x, acc -> {:ok, pid} = Worker.start() [pid] ++ acc end) spawn(fn -> TcpListner.listen(port,{__MODULE__, :new_connection, [[worker_pids]]}) end) {:ok, %{}} end def new_connection([worker_pids], socket) do worker_index = hash_socket(socket) worker_pid = Enum.at(worker_pids, worker_index) {Worker, :incoming, [worker_pid]} end defp hash_socket(_socket), do: :rand.uniform(2) – 1 end defmodule RcDemo.Game.Balanced.Worker do def init(_params), do: {:ok, %{}} def incoming(pid, message), do: GenServer.cast(pid, {:incoming, message}) def handle_cast({:incoming, message}, state) do IO.inspect(message, label: "Received in Worker with pid: #{inspect self()}") {:noreply, state} end end

Slide 29

Slide 29 text

n to m n : m shard player

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

a live game for greater good

Slide 32

Slide 32 text

addendum supervision

Slide 33

Slide 33 text

addendum differentiate processes

Slide 34

Slide 34 text

addendum distributed processes

Slide 35

Slide 35 text

addendum islands

Slide 36

Slide 36 text

fin

Slide 37

Slide 37 text

references Erlang Garbage Collection Details and Why It Matters The beam book Discord Blog Whatsapp’s island architecture Elixir School Elixir Official Documentation Demo code repo Slides

Slide 38

Slide 38 text

thank you @yudistrange