Slide 1

Slide 1 text

@renanranelli Reescrevendo software crítico em Elixir -- Um estudo de caso 2019-05-08 Renan Ranelli

Slide 2

Slide 2 text

@renanranelli

Slide 3

Slide 3 text

Senior Software Engineer @ Telnyx (Chicago, IL) São Paulo @ Brasil Elixir desde 2015 Organizador do ElugSP Antes de Elixir, varios Ruby, Python, C#, etc

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

(Há vagas !)

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Reescrevendo software crítico em Elixir

Slide 9

Slide 9 text

disclaimer

Slide 10

Slide 10 text

Contexto e Motivações

Slide 11

Slide 11 text

Back-to-back User-Agent

Slide 12

Slide 12 text

Back-to-back User-Agent “The Dialplan Service”

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

- (almost) Stateless - Super sensível à latência - Baixo throughput (bem baixo) Propriedades do Dialplan service

Slide 15

Slide 15 text

- A antiga implementação do serviço era escrita em python. (De agora em diante chamada de “dialplan python”) - Todos os devs python acabaram saindo do time de telefonia. - Python é notoriamente mais difícil de escalar vertical & horizontalmente do que Elixir “The Python situation”

Slide 16

Slide 16 text

A decisão: Vamos re-escrever esse serviço em Elixir!

Slide 17

Slide 17 text

Mas pera lá… Elixir?

Slide 18

Slide 18 text

Linguagem vs Plataforma

Slide 19

Slide 19 text

Definições: 1. Erlang é uma linguagem 2. Erlang/OTP é plataforma 3. Elixir é uma linguagem que roda na plataforma Erlang/OTP

Slide 20

Slide 20 text

Erlang/OTP

Slide 21

Slide 21 text

- "Erlang é uma linguagem funcional"! - Erlang "Resolve" concorrência! - É o "segredo" por trás do Whatsapp - Metade da telefonia do mundo é feita em Erlang (citation needed) - "Nine-nines availability" Coisas que ouvimos falar:

Slide 22

Slide 22 text

The Erlang/OTP Runtime

Slide 23

Slide 23 text

- "Soft real time" - Preemptive scheduling (responsivo, justo & baixa variância da latência) - Verticalmente Escalável - Distribuição e Concorrência são cidadãos de 1a classe - Garantias de execução fornecidas pelo runtime (a base para tolerância a falha) - "Alta performance" Características da VM do Erlang

Slide 24

Slide 24 text

Tá, mas e Elixir com isso?

Slide 25

Slide 25 text

- Elixir compila para o bytecode Erlang - Criada por um BR! José Valim <3 - A semântica é absolutamente a mesma entre Elixir e Erlang. (mais parecido com TypeScript/JS do que Clojure/Java, Elm/JS, etc) - Para a plataforma, código Elixir e código Erlang são *indistinguíveis* Elixir é só uma pequena gota

Slide 26

Slide 26 text

"When designing the Erlang language and the Erlang VM, Joe,Mike and Robert did not aim to implement a functional programming language, they wanted a runtime where they could build distributed, fault-tolerant applications. It just happened that the foundation for writing such systems share many of the functional programming principles. And it reflects in both Erlang and Elixir. Therefore, the discussion becomes much more interesting when you ask about their end-goals and how functional programming helped them achieve them. The further we explore those goals,we realize how they tie in with immutability and the control of shared state, for example: ..." -- Valim, José Elixir Beyond Functional Programming

Slide 27

Slide 27 text

TOLERÂNCIA A FALHAS

Slide 28

Slide 28 text

Fault-tolerance requires hardware redundancy Hardware redundancy requires distribution Distribution on a single node is concurrency

Slide 29

Slide 29 text

Mas… Se Erlang é tão topper, pra quê Elixir?

Slide 30

Slide 30 text

- A sintaxe e ecossistema Erlang são *alienigenas* para os desenvolvedores dessa geração. - Existem inúmeras barreiras para adoção de Erlang: falta ferramental, docs difíceis de ler, zero polimorfismo de dados, etc. - Elixir traz o "21st century" para a Erlang-landia, através de metaprogramming, ferramental coeso e bem desenhado, documentação *excelente* e uma comunidade vibrante e acolhedora Raison d'être do Elixir:

Slide 31

Slide 31 text

Erlang -> Hard Skills Elixir -> Soft Skills

Slide 32

Slide 32 text

Exemplo: Preemptive Scheduling

Slide 33

Slide 33 text

Suponha que temos 6 tarefas: T1 T2 T3 T4 T5

Slide 34

Slide 34 text

Com *cooperative scheduling*: T1 T2 T3 T4 T5 T1 T2 T3 T4 T5 T1 T2 T3 T4 T5 1) Or 2) Or 3)

Slide 35

Slide 35 text

Sem *preemptive scheduling*: T1 T2 T3 T4 T5 1) Or 2) E por ai vai... T4 T5 T1 T2 T3 T4 T5 T4 T5 T5

Slide 36

Slide 36 text

Mais sobre isso: https://www.youtube.com/watch?v=a7s25To6oII

Slide 37

Slide 37 text

Tá… e como re-escrevemos?

Slide 38

Slide 38 text

- Buscar "buy-in" stakeholders de negócio - Reservar recursos para o projeto - Codar & deploy-ar continuamente (shadow deploys) - Verificar a paridade de features (Tudo isso enquanto o tráfego de *PRODUÇÃO* cresce dia após dia!) Nossa jornada

Slide 39

Slide 39 text

- Tivemos de convencer vários stakeholders de negócio que seria necessário "desacelerar" o desenvolvimento de novas features para pagar "débito técnico" - É fundamental frasear os benefícios em *termos de negócio* Buscar "buy-in" stakeholders de negócio

Slide 40

Slide 40 text

- Conseguimos aprovar um desenvolvedor dedicado full-time para esse projeto. Nenhuma outra responsabilidade. (#sqn) - Re-escrever é um trabalho imenso em que você busca um "alvo em movimento". Se você não conseguir focar, você vai fracassar Reservar recursos

Slide 41

Slide 41 text

Codar & deploy-ar continuamente (shadow deploys)

Slide 42

Slide 42 text

Codar & deploy-ar continuamente Proxy Python Dialplan Graylog XML Request XML Response

Slide 43

Slide 43 text

Codar & deploy-ar continuamente Proxy Python Dialplan Graylog XML Request Elixir Dialplan Ignore response! XML Response XML Response

Slide 44

Slide 44 text

Codar & deploy-ar continuamente Proxy Python Dialplan Graylog XML Request Elixir Dialplan Ignore response! XML Response XML Response “Operational database”

Slide 45

Slide 45 text

Verificar paridade de Features SELECT p.xml_dialplan, e.xml_dialplan, p.request FROM python_dialplan_logs as p INNER JOIN elixir_dialplan_logs as e ON p.call_id == e.call_id WHERE e.xml_dialplan != p.xml_dialplan

Slide 46

Slide 46 text

Verificar paridade de Features

Slide 47

Slide 47 text

Logo no começo, percebemos: - A maioria das diferenças eram causadas por falhas em serviços "downstream". - Quando não era esse o caso, "gravar" a resposta das dependências era suficiente para reproduzir o cenário de erro. Verificar paridade de Features

Slide 48

Slide 48 text

Logo no começo, percebemos: - A maioria das diferenças eram causadas por falhas em serviços "downstream". - Quando não era esse o caso, "gravar" a resposta das dependências era suficiente para reproduzir o cenário de erro. Verificar paridade de Features (Tipo o VCR do Ruby)

Slide 49

Slide 49 text

Um efeito colateral top: Regressão

Slide 50

Slide 50 text

E continuamos fazendo isso...

Slide 51

Slide 51 text

E continuamos fazendo isso... … até que um dia acabaram as diffs!

Slide 52

Slide 52 text

O familiar ciclo “TDD”

Slide 53

Slide 53 text

Nosso ciclo “tipo-TDD” Capture um request com erro Corrige & cria cenário de regressão Reescreve

Slide 54

Slide 54 text

Resultados

Slide 55

Slide 55 text

Praticamente *zero* incidentes em produção após o cutover

Slide 56

Slide 56 text

Um runtime muito superior. Code-base 100% Elixir.

Slide 57

Slide 57 text

Melhorias *enormes* em observabilidade

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

Features shipando mais rápido

Slide 60

Slide 60 text

Paralelização ridiculamente mais fácil & barata. (less latency -> happier users)

Slide 61

Slide 61 text

Agora… A parte menos gloriosa

Slide 62

Slide 62 text

Expectativas de negócio mal-gerenciadas

Slide 63

Slide 63 text

Amostragem burra das discrepâncias

Slide 64

Slide 64 text

O recurso "dedicado" acabou não ficando tão dedicado assim

Slide 65

Slide 65 text

O serviço python *continuou sendo modificado* enquanto a re-escrita acontecia!

Slide 66

Slide 66 text

98% pronto por … muito tempo

Slide 67

Slide 67 text

Tentando refatorar código bizarro no momento errado do ciclo "tipo-TDD"

Slide 68

Slide 68 text

Wrapping up

Slide 69

Slide 69 text

Valeu a pena?

Slide 70

Slide 70 text

Vá para produção o mais cedo e o mais frequentemente possível!

Slide 71

Slide 71 text

Monitoração e observabilidade valem a pena. Você nunca deve ter medo de *testar em produção*!

Slide 72

Slide 72 text

Refatorar >>> Reescrever (sempre que possível)

Slide 73

Slide 73 text

Re-escrever é difícil e perigoso. Porém, possível.

Slide 74

Slide 74 text

Agora, uma reflexão final

Slide 75

Slide 75 text

O que garante que a re-escrita em Elixir não vai degringolar da mesma forma que o python degringolou?

Slide 76

Slide 76 text

Elixir economiza um pouquinho de tempo em *várias* situações

Slide 77

Slide 77 text

Elixir economiza um pouquinho de tempo em *várias* situações … portanto, sobra *mais tempo* pra fazer o que é *importante*!

Slide 78

Slide 78 text

“Fully utilize *all* your resources”

Slide 79

Slide 79 text

“Fully utilize *all* your resources” (incluindo humanos)

Slide 80

Slide 80 text

Renan Ranelli milhouse@taming-chaos.com renan@telnyx.com Obrigado! Check us out! www.telnyx.com @renanranelli We're hiring!!