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

Introducing Elixir: Self-healing applications at ZOMG scale

Introducing Elixir: Self-healing applications at ZOMG scale

Elixir is a new functional language built on the Erlang VM. We’ll tour the Elixir language and it’s most important frameworks to discover what makes Elixir a great choice for building systems at ZOMG scale and how those systems stay up for so long with Wolverine level self-healing.

Elixir’s secret is in its Erlang heritage. When was the last time your telephone network went down? Never, right? That’s because most of the telephone network is written in Erlang. How does WhatsApp manage to support 1.2 billion users and deliver 42 billion messages a day with only around 50 engineers? Yes, Erlang.

Elixir takes the power and stability offered by Erlang and makes it more approachable, with a Ruby inspired syntax, simpler abstractions and some powerful tools which will enable you to build concurrent, fault-tolerant and distributed systems. We'll find out how this functional language is the most Object Oriented of all languages and how microservice architecture is built right in.

Challenge yourself to break out of your comfort zone and look at how other languages solve problems. You’ll become a better developer if you do.

196ab25f16dcfd37518a41ceb15e0da0?s=128

Andy Pike

June 10, 2017
Tweet

Transcript

  1. Introducing Elixir Self-healing applications at ZOMG scale @andypike

  2. Allow myself to introduce… …myself

  3. @andypike

  4. Freelancer for hire!

  5. None
  6. None
  7. None
  8. Background

  9. None
  10. Hello Mike Hello Robert Hello Joe Erlang The Movie https://youtu.be/xrIjfIjssLE

  11. 2 million connections per server 1.2 billion users 42 billion

    messages per day
  12. Operating System BEAM VM Erlang Bytecode Elixir LFE

  13. None
  14. Elixir Basics

  15. Functional Immutable Concurrent Fault Tolerant Ruby Inspired Syntax Distributed

  16. $ iex iex(1)> $ iex hello.exs iex(1)> Hello.world() "Hello World"

    iex(2)> h String.upcase Converts all characters in the given string to uppercase. ## Examples iex> String.upcase("abcd") "ABCD"
  17. Basic Types

  18. $ iex iex(1)> 1 + 1 2 iex(2)> 2 *

    3 6 iex(3)> 1.2 - 0.2 1.0 iex(4)> 1..10 1..10 iex(5)> "Hello World" "Hello World" iex(6)> :andy :andy
  19. iex(1)> { :ok, 42, "next" } { :ok, 42, "next"

    } iex(2)> [1, 2, 3] [1, 2, 3] iex(3)> [1, 2, 3] ++ [4, 5, 6] [1, 2, 3, 4, 5, 6] iex(4)> info = %{ :name => "Andy", :town => "Woking" } %{name: "Andy", town: "Woking"} iex(5)> info[:name] "Andy" iex(6)> info.name "Andy"
  20. person.exs defmodule Person do
 defstruct name: "", age: 0, email:

    "" end $ iex person.exs iex(1)> p1 = %Person{ name: "Andy", age: 25 } %Person{age: 25, email: "", name: "Andy"} iex(2)> p1.name "Andy" iex(3)> p2 = %Person{ p1 | email: "me@here.com" } %Person{age: 25, email: "me@here.com", name: "Andy"} iex(4)> p1.email ""
  21. Pattern Matching

  22. iex(1)> a = 1 1 iex(2)> a + 2 3

    iex(3)> a = 2 2 iex(4)> 2 = a 2 iex(5)> 3 = a ** (MatchError) no match of right hand side value: 2 iex(6)> ^a = 3 ** (MatchError) no match of right hand side value: 3
  23. iex(1)> list = [1, 2, 3] [1, 2, 3] iex(2)>

    [x, y, z] = list [1, 2, 3] iex(3)> y 2 iex(4)> [1, b, c] = list [1, 2, 3] iex(5)> b 2 iex(6)> [3, t, 5] = list ** (MatchError) no match of right hand side value: [1, 2, 3]
  24. iex(1)> success = { :ok, 42 } { :ok, 42

    } iex(2)> failure = { :error, "Oops" } { :error, "Oops" } iex(3)> { :ok, result } = success { :ok, 42 } iex(4)> result 42 iex(5)> { :ok, result } = failure ** (MatchError) no match of right hand side value: {:error, "Oops"} iex(6)> { _, result } = failure { :error, "Oops" }
  25. iex(1)> list = [1, 2, 3] [1, 2, 3] iex(2)>

    [head|tail] = list [1, 2, 3] iex(3)> head 1 iex(4)> tail [2, 3] iex(5)> [head_2|tail_2] = tail [2, 3] iex(6)> head_2 2 iex(7)> tail_2 [3]
  26. Functions

  27. defmodule Hello do def world do "Hello World" end def

    world(name) do "Hello #{name}" end end iex(1)> Hello.world "Hello World" iex(2)> Hello.world("Andy") "Hello Andy"
  28. defmodule Hello do def world, do: greeting("World") def world(name), do:

    greeting("Hi", name) defp greeting(salutation \\ "Hello", name) do "#{salutation} #{name}" end end iex(1)> Hello.world "Hello World" iex(2)> Hello.world("Andy") "Hi Andy" iex(3)> Hello.greeting("Andy") ** (UndefinedFunctionError) function Hello.greeting/1 is undefined or private
  29. defmodule Maths do def arithmetic(:add, a, b), do: a +

    b def arithmetic(:subtract, a, b), do: a - b end iex(1)> Maths.arithmetic(:add, 1, 2) 3 iex(2)> Maths.arithmetic(:subtract, 10, 2) 8 iex(2)> Maths.arithmetic(:multiply, 3, 3) ** (FunctionClauseError) no function clause matching…
  30. defmodule MyList do def count([]), do: 0 def count([_ |

    tail]) do 1 + count(tail) end end iex(1)> MyList.count([]) 0 iex(2)> MyList.count([1, 2, 3]) 3
  31. defmodule MyList do def count(list, total \\ 0) def count([],

    total), do: total def count([_ | tail], total) do count(tail, total + 1) end end iex(1)> MyList.count([]) 0 iex(2)> MyList.count([1, 2, 3]) 3
  32. defmodule Guard do def what_is(x) when is_atom(x) do IO.puts "#{x}

    is an atom" end def what_is(x) when is_number(x) and x > 0 do IO.puts "#{x} is a positive number" end def what_is(_), do: IO.puts "Unknown" end iex(1)> Guard.what_is(10) 10 is a positive number iex(2)> Guard.what_is("Andy") Unknown
  33. iex(1)> String.downcase("ANDY") "andy" iex(2)> String.ends_with?("Andy", "y") true iex(3)> Enum.map([1, 2,

    3], fn(x) -> x * x end) [1, 4, 9] iex(4)> Enum.map([1, 2, 3], &(&1 * &1)) [1, 4, 9] iex(5)> Map.has_key?(%{ name: "Andy" }, :name) true
  34. Pipe Operator

  35. text = " Hi Andy " text = String.downcase(text) text

    = String.strip(text) text = String.reverse(text) => "ydna ih" String.reverse(String.strip(String.downcase(text))) => "ydna ih"
  36. text = " Hi Andy " text |> String.downcase |>

    String.strip |> String.reverse => "ydna ih"
  37. Doctests

  38. defmodule MyString do @doc ~S""" Converts a string to uppercase

    ## Examples iex> MyString.upcase("andy") "ANDY" """ def upcase(string) do String.upcase(string) end end
  39. defmodule MyStringTest do use ExUnit.Case doctest MyString end $ mix

    test 1 test, 0 failures iex(1)> h MyString.upcase Converts a string to uppercase ## Examples iex> MyString.upcase("andy") "ANDY"
  40. Concurrent

  41. Processes

  42. iex(1)> pid = spawn(fn -> IO.puts "Hello World" end) Hello

    World #PID<0.82.0> iex(2)> Process.alive?(pid) false
  43. defmodule Chat do def greet do IO.puts "Hello World" end

    end iex(1)> pid = spawn(Chat, :greet, []) Hello World #PID<0.82.0>
  44. Sending Messages

  45. Memory Function fn -> IO.puts "Hello World" end Mailbox Message

    1 Message 2 Message 3
  46. defmodule ChatWorker do def loop do receive do { :greet,

    name } -> IO.puts "Hi #{name}!" loop() end end end iex(1)> pid = spawn(ChatWorker, :loop, []) #PID<0.118.0> iex(2)> Process.alive?(pid) true
  47. defmodule ChatWorker do def loop do receive do { :greet,

    name } -> IO.puts "Hi #{name}!" loop() end end end iex(3)> send pid, { :greet, "Andy" } Hi Andy! iex(4)> Process.alive?(pid) true
  48. defmodule NothingWorker do def loop do receive do { :exit

    } -> IO.puts "Exiting…" _ -> loop() end end end iex(1)> pid = spawn(NothingWorker, :loop, []) iex(2)> send pid, { :covfefe } iex(3)> Process.alive?(pid) true
  49. defmodule NothingWorker do def loop do receive do { :exit

    } -> IO.puts "Exiting…" _ -> loop() end end end iex(4)> send pid, { :exit } Exiting… iex(3)> Process.alive?(pid) false
  50. State

  51. defmodule Counter do def init(count \\ 0) do loop(count) end

    def loop(count) do receive do { :add, number } -> count = count + number loop(count) { :current, sender } -> send(sender, { :ok, count }) loop(count) end end end
  52. iex(1)> pid = spawn(Counter, :init, []) #PID<0.88.0> iex(2)> send(pid, {:add,

    1}) {:add, 1} iex(3)> send(pid, {:add, 2}) {:add, 2} iex(4)> send(pid, {:current, self()}) {:current, #PID<0.80.0>} iex(5)> flush() {:ok, 3} :ok
  53. IEX <0.80.0> Counter.loop <0.88.0> {:add, 1} {:add, 2} {:current, <0.80.0>}

    {:ok, 3}
  54. spawn init loop exit Message Message Message State

  55. OTP

  56. use GenServer GenServer.start_link MyModule.init GenServer.call MyModule.handle_call GenServer.cast MyModule.handle_cast

  57. defmodule OTPCounter do use GenServer # Public API def start_link

    do GenServer.start_link(__MODULE__, :ok, []) end # Callbacks def init(:ok) do {:ok, 0} end end initial state
  58. defmodule OTPCounter do # snip… # Public API def current(pid)

    do GenServer.call(pid, :current) end # Callbacks def handle_call(:current, from_pid, state) do {:reply, state, state} end end
  59. defmodule OTPCounter do # snip… # Client API def add(pid,

    number) do GenServer.cast(pid, {:add, number}) end # Callbacks def handle_cast({:add, number}, state) do {:noreply, state + number} end end
  60. # Public API def stop(pid) do GenServer.cast(pid, :stop) end #

    Callbacks def handle_cast(:stop, state) do {:stop, :normal, state} end def terminate(reason, state) do IO.puts "Server stopping with total #{state}" :ok end
  61. iex(1)> {:ok, pid} = OTPCounter.start_link {:ok, #PID<0.94.0>} iex(2)> OTPCounter.current(pid) 0

    iex(3)> OTPCounter.add(pid, 1) :ok iex(4)> OTPCounter.add(pid, 1) :ok iex(5)> OTPCounter.current(pid) 2 iex(6)> OTPCounter.stop(pid) Server stopping with total 2 iex(7)> Process.alive?(pid) false
  62. None
  63. The Scheduler

  64. Process 1 Process 2 Process 3 Process 4 Run Queue

    Current Process Until empty mailbox or 2000 reductions
  65. Core 1 Core 2 Core 3 Core 4

  66. Fault-Tolerant

  67. What’s the one universal way to fix any computer problem?

  68. "Have you tried turning it off and back on again?"

  69. Supervision Trees

  70. Worker Worker Supervisor Supervisor Worker Worker

  71. defmodule OTPCounter do # snip… def crash(pid) do GenServer.cast(pid, :crash)

    end def handle_cast(:crash, state) do 1/0 end end
  72. iex> self() #PID<0.80.0> iex> import Supervisor.Spec Supervisor.Spec iex> c =

    [worker(OTPCounter, [], [])] … iex> {_, sup} = Supervisor.start_link(c, strategy: :one_for_one) {:ok, #PID<0.90.0>} iex> {_, [_, counter]} = Process.info(sup, :links) {:links, [#PID<0.80.0>, #PID<0.91.0>]}
  73. iex> OTPCounter.add(counter, 1) :ok iex> OTPCounter.current(counter) 1 iex> OTPCounter.crash(counter) [error]

    GenServer #PID<0.91.0> terminating… iex> {_, [_, counter]} = Process.info(sup, :links) {:links, [#PID<0.80.0>, #PID<0.95.0>]} iex> OTPCounter.current(counter) 0
  74. Let it crash

  75. Distributed

  76. Node :foo Core 1 Core n . . . Core

    1 Core n . . . Node :bar msg
  77. $ iex --name foo@192.168.0.100 --cookie s3cr3t iex(foo@192.168.0.100)1> Node.connect(:"bar@192.168.0.101") true iex(foo@192.168.0.100)2>

    Node.list [:"bar@192.168.0.101"] iex(foo@192.168.0.100)3> c "chat_worker.exs" [ChatWorker] $ iex --name bar@192.168.0.101 --cookie s3cr3t iex(bar@192.168.0.101)1> Node.list [:"foo@192.168.0.100"] iex(bar@192.168.0.101)2> ChatWorker.loop ** (UndefinedFunctionError) function ChatWorker.loop/0 is undefined
  78. $ iex --name foo@192.168.0.100 --cookie s3cr3t iex(foo@192.168.0.100)1> Node.connect(:"bar@192.168.0.101") true iex(foo@192.168.0.100)2>

    Node.list [:"bar@192.168.0.101"] iex(foo@192.168.0.100)3> c "chat_worker.exs" [ChatWorker] iex(bar@192.168.0.101)3> pid = Node.spawn_link :"foo@192.168.0.100", ChatWorker, :loop, [] #PID<9137.108.0> iex(bar@192.168.0.101)4> send(pid, {:greet, "Andy"}) Hi Andy!
  79. None
  80. Frameworks, Libraries & Tools

  81. None
  82. Framework Throughput (req/s) Avg Latency (ms) Phoenix 31,417 3.52 Express

    9,477 10.56 Sinatra 8,334 7.46 Rails 3,452 17.96 https://github.com/mroth/phoenix-showdown/blob/master/RESULTS_v3.md
  83. http://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections

  84. None
  85. hex mnesia ETS Hot code upgrades mix observer

  86. Resources

  87. elixir-lang.org exercism.io elixirschool.com learnelixir.tv learnphoenix.tv elixirfountain.com codeschool.com redfour.io

  88. None
  89. Final Thoughts

  90. iMac Pro 18-cores Intel X-Series 18-cores Apple Watch Series 2

    Dual-core Raspberry PI Quad-core
  91. "I thought of objects being like biological cells or individual

    computers on a network, only able to communicate with messages." - Alan Kay
  92. None
  93. None
  94. Thank you! @andypike

  95. Sponsors