Slide 1

Slide 1 text

Пропаганда Elixir для новых адептов

Slide 2

Slide 2 text

Никита Соболев Руководитель web-разработки

Slide 3

Slide 3 text

План доклада • Быстро пройдемся по базовым вещам • Посмотрим, чем Elixir отличается от других языков • А потом поговорим об инфраструктуре вокруг языка

Slide 4

Slide 4 text

Серия слайдов про базовые вещи (но не слишком подробно)

Slide 5

Slide 5 text

Elixir • Написан Jose Valim (Rails Core Dev) в 2012 году • Основывается на BEAM

Slide 6

Slide 6 text

Типы данных iex> 1 # integer iex> 1.0 # float iex> true # boolean iex> :atom # atom iex> "elixir" # string (unicode!) iex> [1, 2, 3] # list iex> {1, 2, 3} # tuple iex> %{a: 1} # map iex> [key: v] # keyword list

Slide 7

Slide 7 text

Приятные фичи • Pattern matching [1 | tail] = [1, 2, 3] #=> [1, 2, 3] tail #=> [2, 3] • Pipes "Elixir rocks" |> String.upcase |> String.split #=> ["ELIXIR", "ROCKS"] • Макросы

Slide 8

Slide 8 text

Чем Elixir - другой?

Slide 9

Slide 9 text

Actor Model

Slide 10

Slide 10 text

parent = self() #=> PID<0.80.0> spawn fn -> send(parent, {:hello, self()}) end #=> PID<0.83.0> receive do {:hello, pid} -> "Got hello from #{inspect pid}" end #=> "Got hello from #PID<0.83.0>" Процессы - изолированные, параллельные, легковесные

Slide 11

Slide 11 text

Уже готовые обертки • Agent - простая обертка над процессом с состоянием • GenServer - "generic process", инкапсулирует работу с состоянием, добавляют возможность использовать sync и async методы • GenEvent - позволяет работать с событиями • Task - позволяет запустить процесс, а потом получить его результат

Slide 12

Slide 12 text

Обработка ошибок try do raise "Big Bad Error" rescue e -> IO.inspect(e) end #=> %RuntimeError{message: "Big Bad Error"} try do throw "Big Bad Throwable Error" catch e -> IO.inspect(e) end #=> "Big Bad Throwable Error" А еще все умрет, если умрет связанный (linked) процесс...

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Supervisor Tree

Slide 15

Slide 15 text

defmodule Stack do use GenServer def start_link(state, opts \\ []) do GenServer.start_link(__MODULE__, state, opts) end def handle_call(:pop, _from, [h | t]) do {:reply, h, t} end def handle_cast({:push, h}, t) do {:noreply, [h | t]} end end Наше приложение

Slide 16

Slide 16 text

Строим supervisor tree # Import helpers for defining supervisors import Supervisor.Spec # Supervise the Stack server which will be started with # a single argument [:hello] and the default registered # name of MyStack. children = [ worker(Stack, [[:hello], [name: MyStack]]) ] # Start the supervisor with our child {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

Slide 17

Slide 17 text

Используем приложение GenServer.call(MyStack, :pop) #=> :hello GenServer.cast(MyStack, {:push, :world}) #=> :ok GenServer.call(MyStack, :pop) #=> :world GenServer.call(MyStack, :pop) #=> ??? #=> BOOM!!!

Slide 18

Slide 18 text

Проблема Иногда код на Elixir медленнее, чем мы того ожидаем

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Решение Перестраиваем мышление

Slide 21

Slide 21 text

source = File.read!("source.txt") #=> "roses are red\nviolets are blue\n\n" source |> String.split("\n") |> Enum.flat_map(&String.split/1) |> Enum.reduce(%{}, fn(word, map) -> Map.update(map, word, 1, &(&1 + 1)) end) Жадный подход

Slide 22

Slide 22 text

Ленивый подход File.stream!("source.txt", [], :line) |> Stream.flat_map(&String.split/1) |> Enum.reduce(%{}, fn(word, map) -> Map.update(map, word, 1, &(&1 + 1)) end)

Slide 23

Slide 23 text

Асинхронный подход File.stream!("source.txt", [], :line) |> Flow.from_enumerable() |> Flow.flat_map(&String.split/1) |> Flow.partition() # the magic happens here |> Flow.reduce(fn -> %{} end, fn(word, map) -> Map.update(map, word, 1, &(&1 + 1)) end) |> Enum.into(%{})

Slide 24

Slide 24 text

Результаты • На маленьком объеме данных побеждает "жадный подход" (более чем в 19 раз) • На средних и больших побеждает Flow (от 3 раз) • https://github.com/sobolevn/fpconf_elixir

Slide 25

Slide 25 text

Инфраструктура вокруг языка

Slide 26

Slide 26 text

mix • Встроенный build-tool, который выполняет все необходимые действия • Управляет зависимостями (как bundler) • Запускает задачи (как manage.py, rake) • Управляет конфигурацией

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Phoenix Framework

Slide 30

Slide 30 text

4.0GHZ Core i7 (quad core), 32GB RAM Framework Throughput (req/ s) Latency (ms) Consistency (σ ms) Gin 59001.07 1.84 1.35 Phoenix 31417.81 3.52 3.50 Express Cluster 26244.35 3.92 3.25 Sinatra 8334.84 7.46 3.38 Express 9477.14 10.56 1.39 Rails 3452.58 17.96 7.73

Slide 31

Slide 31 text

Сколько открытых вебсоккетов может держать Phoenix? 128GB RAM, 40cores *

Slide 32

Slide 32 text

2 миллиона http://www.elixirconf.eu/elixirconf2016/gary-rennie

Slide 33

Slide 33 text

Ecto defmodule Weather do use Ecto.Schema import Ecto.Query schema "weather" do field :city, :string field :temp_lo, :integer field :temp_hi, :integer end def keyword_query do query = from w in Weather, where: w.city == "Moscow" and w.temp_lo > 0, select: w Repo.all(query) end end

Slide 34

Slide 34 text

Когда использовать? • У вас высоконагруженное приложение • У вас есть задачи, которые нужно выполнять асинхронно / параллельно • Вам крайне важна отказоустойчивость

Slide 35

Slide 35 text

8 декабря, Rambler http://elixir-lang.moscow

Slide 36

Slide 36 text

Материалы • Keynote by Jose Valim: https://www.youtube.com/watch?v=srtMWzyqdp8 • The Road to 2 Million Websocket Connections in Phoenix: https:// www.youtube.com/watch?v=c6JcVwbOGXc • Processing 2.7 million images with Elixir: https://www.youtube.com/watch? v=xoNRtWl4fZU • Elixir on the Web: Raising Phoenix: https://www.youtube.com/watch? v=h4z7WnMLXKI • benchee: https://github.com/PragTob/benchee • Phoenix benchmarks: https://github.com/mroth/phoenix-showdown • Код из презентации: https://github.com/sobolevn/fpconf_elixir

Slide 37

Slide 37 text

github.com/sobolevn