Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scalable dist-sys from grounds up

204b5d16fa16275357b4a58d2ef62d02?s=47 udit
April 28, 2019

Scalable dist-sys from grounds up

204b5d16fa16275357b4a58d2ef62d02?s=128

udit

April 28, 2019
Tweet

More Decks by udit

Other Decks in Technology

Transcript

  1. scalable dist-sys from the grounds up in Elixir

  2. whoami udit @ nilenso

  3. agenda why elixir/erlang under the hood build you a live

    game for great good build you a better live game for greater good
  4. why elixir / erlang

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

    .
  6. why elixir/erlang asynchronous message passing no sharing fault tolerance

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

  8. under the hood

  9. beam Bjorn’s erlang abstract machine bytecode ~ erlang / Elixir

    / Gleam / LFE etc
  10. process light weight - green thread communicate via message passing

    single threaded
  11. process process control board heap stack

  12. process garbage collection when: heap meets stack runs on process

    schedule compaction vs full copy
  13. process schedulers process queues soft pre-emptive

  14. 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
  15. process registration noname :local :global pg2 swarm Registry (elixir)

  16. supervisor reliability monitor other process

  17. 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
  18. gen_server call GenServer.call(pid, :hi, 1000) cast GenServer.cast(pid, :hello) info send(pid,

    :info)
  19. distributed nodes fully connected mesh network heartbeat

  20. None
  21. a live game for great good

  22. 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
  23. 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
  24. one for all

  25. one for all 1 : n single thread of execution

    for all incoming message message queue build up no fault tolerance
  26. 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
  27. one for one 1 : 1 can process responses from

    players faster large process queue on schedulers
  28. 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
  29. n to m n : m shard player

  30. None
  31. a live game for greater good

  32. addendum supervision

  33. addendum differentiate processes

  34. addendum distributed processes

  35. addendum islands

  36. fin

  37. 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
  38. thank you @yudistrange