Наш опыт переезда на Elixir
Черепанов Сергей
Размахнин Никита
Fullstack Development
У нас курьерское приложение и много асинхронной логики во время выполнения заказа
Аналог Uber, только по доставке не людей
3к
курьеров
400+
заказов в день
2–3мин
поиск курьера
REST API на Django
TS React+Redux SSR приложение формы заказа
Telegram бот на Python (asyncio)
Flow React-Native Android приложение
Elixir Worker
AWS
CloudFormation
EC2 + AutoScaling
LoadBalancing
RDS
VPC with Bastion
SQS
Заказ через форму
Отправка данных на REST Server
Отправка сообщений в SQS
Worker подхватывает сообщение о новом заказе
Броадкаст курьерам
Performance:
7 couriers per second
Курьеры оставляют отклики
Берем первого готового и останавливаем броадкаст
Если курьер откажется от заказа,
броадкаст начинается заново
Если за 5 минут курьер не выехал и не отказался,
шлем СМС клиенту
Если курьер выехал, шлем другое СМС
(если первая не была отправлена, то ее отменяем)
Остановка броадкаста
Уведомления в чат-админку и курьеру
Не найден курьер после броадкаста в течение 3-х минут
Время заказа вышло, а он не завершен
...
Все это действо должно работать и для бота, и для приложения
Система типов
Богатая экосистема
Core — сыроват?
но есть отзывы переноса крупных проектов, и небольшой опыт
Язык хорош и зрел
Система типов
...давайте вы будете уметь готовить еще и Java
Быстро и круто, хайпово и удобно
Опыт
Асинхронность?
Динамическая типизация?
Мощная система типов
ФП
Экосистема?
Асинхронность??
Скорость разработки???
Шарим в JS
Один поток в 2017???
Масштабироваться почти только железом
оверхеды по памяти и системным процессам
JavaScript :(
Whatsap, Heroku, Amazon, Wargaming, Discord
Автогенерируемые, довольно подробные
Большой мир публикаций на Медиуме, есть чуть-чуть на Хабре
ПОСМОТРЕЛИ НА
Написан в середине 80-х в лабораториях компании Ericsson для работы в сфере телекома
По требованиям ПО в телекоме: быстро, эффективно, параллельно и отказоустойчиво
Развивается инженерами по сей день
ПОСМОТРЕЛИ НА
Разработчиков не так много, как на js, но они есть
Основное количество библиотек — весьма высокое качество
ПОСМОТРЕЛИ НА
В 90% случаев есть тесты, которые зачастую лучше документации
ПОСМОТРЕЛИ НА
ПОСМОТРЕЛИ НА
4к закрытых пуллерквестов, большинство которых - code style & docs
Остальное время комьюнити обсуждает красивости кода (лично мой взгляд)
...потом оказалось
--no-halt
В начале осени: допокрыли тестами и разнесли работу с курьерами
В конце осени: выделили под каждый заказ по отдельному процессу
Продолжаем переносить бизнес-логику из телеграм бота на воркер
Побочные эффекты:
Недетерминированность
Time.now()
sum = fn a, b -> a + b end
10 == Enum.reduce([1, 2, 3, 4], sum)
add2 = fn number -> sum(number, 2) end
[3, 4, 5] == Enum.map([1, 2, 3], add2)
Никто не перетрет ваши переменные в промежутке между строками кода
Быстрое сравнение по ссылке
Формализованность, которая улучшает статический анализ
Не нужны локи :)
man = %{name: "john"}
try_mutate_if_you_can(man)
assert man.name == "john"
[head | tail] = [1, 2, 3, 4]
man = %{gender: :male, name: "john", age: 34}
greet(man)
def greet (%{name: name} = man) do
..
end
def loop(state) do
receive do ->
:inc ->
loop(state + 1)
:stop ->
state
end
end
datetime
|> Timex.parse!("{ISO:Extended}")
|> Timex.Timezone.convert(@timezone)
|> Timex.format!("{h24}:{m}")
couriers = [
%{name: "joe", id: 1},
%{name: "mike", id: 2},
]
slot = Enum.map(couriers, &(&1.id))
# fn courier -> courier.id end
slot = {1400, [123, 126, 129]}
couriers_slots = couriers
|> Enum.sort(&(&1.priority > &2.priority))
|> Enum.chunk_by(&(&1.priority))
|> Enum.map(fn (slot = [head | _]) -> {
head.priority,
Enum.map(slot, &(&1.id))
} end)
defmodule Worker.Models.CourierTest do
use ExUnit.Case
test "is_has_permission_for_order" do
assert Courier.is_has_permission_for_order(
@default_courier, @default_order
) == true
assert Courier.is_has_permission_for_order(
@default_courier, @car_required_order
) == false
end
end
send(pid, {:hello, :world})
message =
receive do
{:hello, name} -> name
end
{:ok, message} =
receive do
{:hello, name} -> {:ok, name}
after
1_000 -> {:error, :timeout}
end
{from, ref, name} =
receive do
{:hello, from, ref, name} -> {from, ref, name}
end
send(from, {:aloha, ref, name})
{:message_queue_len, length} =
:erlang.process_info(pid, :message_queue_len)
Умирая, процесс шлет сигнал смерти всем слинкованным процессам
spawn_link(fn ... end)
Умирая, процесс шлет уведомление, что он умер
ref = Process.monitor(pid)
Слушают уведомления о дочерних процессах и перезапускают их
Супервизоры можно композировать
Другие супервизоры будут ветвями, а рабочие процессы — листьями
One For One
One For All
Rest For One
Количество процессов | 8782 |
Run queue | 0 |
Error logger queue | 0 |
Memory | 149Mb |
Bytes in/out | 0 |
GC count | 0 |
Reductions | 12010 |
Sheduler Usage | 1% |
busy_port |
busy_dist_port |
{long_gc, 50} |
{long_schedule, 50} |
{large_heap, 1_000_000}
iex --sname baz --remsh worker@sergey-laptop
У Эрланга есть подмена кода на лету
То есть да, можно без остановки сервера и приложения на нем сделать релиз
Но надо ручками почти во всех модулях написать хуки, которые умеют мигрировать стейт, например.
И еще много другой боли перетерпеть, мы туда даже не лезем...