Elixir Brasil 2019 - Primeiro dia
Nos dias 25 e 26 de maio tive a honra de participar da segunda edição do evento #ElixirBrasil, realizado pela CodamosClub, o Elug SP e o Nubank - que sediou o evento em seu lindo prédio em São Paulo/SP.
As palestras ocorreram durante os dois dias inteiros - foram 34 talks e 33 palestrantes para mais de 400 participantes, números impressionantes e de dar muito orgulho - principalmente se considerarmos o quão nova é a comunidade Elixir. Haviam duas trilhas: uma iniciante e uma avançada onde foi possível transitar entre elas conforme a afinidade com cada assunto. No site do evento é possível encontrar toda a grade de palestras, informações sobre os palestrantes, patrocinadores e organizadores além do código de conduta.
Infelizmente as palestras não foram gravadas, mas nesse post - primeira parte de duas - vou resumir todas as talks que vi durante o primeiro dia.
Abertura
A Alda Rocha e o Guilherme de Maio fizeram uma breve abertura do #ElixirBrasil desse ano. É incrível ver a diversidade do evento na organização, comunidades presentes e o código de conduta sendo seguido. Vemos que essas ações se refletem no público também, que é muito diverso - com certeza o evento mais diverso que já participei. Cada mínimo detalhe foi pensado para o conforto de todos os participantes. Enfim, bora começar?
Como uma empresa brasileira criou uma linguagem que é usada no mundo inteiro. O case da Plataformatec com o Elixir - Hugo Baraúna
O Hugo, da Plataformatec, deu início às palestras com o seu keynote simultâneo para as duas trilhas. Ele veio mostrar como surgiu o Elixir, que problemas buscavam resolver e os frutos que colhem hoje.
Parte 1 - Por que criar uma linguagem nova?
Em 2010, o pessoal da Plataformatec estava tentando lidar com o
Ruby on Rails multithread, utilizando uma
funcionalidade para thread safety
que tinha sido recentemente lançada. Porém
vários bugs e problemas foram aparecendo em aplicações, mostrando que não era
tão fácil lidar com esse problema.
Mas, por que thread safe
era tão importante? O paper
The free lunch is over (O
almoço grátis acabou), de 2005, expõe uma visão interessante sobre o tema. Cita
a Lei de Moore, que diz que o “número de transistores dos processadores dobra
a cada dois anos” - o que na prática significava que a velocidade dos CPUs
basicamente dobrava também. Ou seja, para ter performance em um software era só
esperar um pouco, atualizar o hardware e pronto, almoço grátis!
Porém, a partir dos anos 2000, esse cenário começou a mudar e a Lei de Moore já não mais funcionava. É importante ressaltar que as CPUs não pararam de evoluir, porém evoluíram de modo diferente, com hyperthreading e multicore. Ao invés de um processador ficando mais potente, mais processadores de mais ou menos mesma potência foram sendo adicionados.
Com essa mudança podemos entender que o modo como escrevemos softwares deve levar isso em conta, aproveitando-se de concorrência e paralelização.
O Hugo cita ainda a Lei de Amdahl, que diz que “quanto menos concorrente for seu código, menos velocidade ele ganha pelo aumento de núcleos do processador (cores)”. E cita também o seguinte:
“Provavelmente, o maior custo da concorrência é que concorrência é realmente difícil”
Herb Sutter - tradução livre
Mas e se fosse fácil fazer concorrência?
Parte 2 - A busca por outras tecnologias e por concorrência fácil
Concorrência deveria ser fácil, mas na prática não é bem por aí. Segundo o Herb Sutter, é natural que a maioria dos desenvolvedores não saibam concorrência, da mesma forma que 15 anos atrás a maioria não sabia sobre orientação a objetos. Já fazem 10 anos desde o artigo “The Free Lunch is Over”, mas vemos que a maioria do código ainda é “single-threaded”, ou seja, não-concorrente.
O problema é o modelo de threads e locks, que são abstrações de baixo nível?
E se tivéssemos uma abstração de mais alto nível, que facilitasse nossa vida
como desenvolvedores de software? Por exemplo, isso ocorre com o gerenciamento
de memória - não precisamos mais de malloc
s e etc, pois a abstração do
garbage collector lida com isso para a gente.
Parte 3 - O desenvolvimento do Elixir
Lendo o livro Seven Languages in Seven Weeks (Sete linguagens em sete semanas), três linguagens chamaram a atenção do José Valim: Haskell, Erlang e Clojure, tendo entre elas características funcionais, com distribuição e tolerância a falhas e com uma abordagem moderna, com polimorfismo e meta-programação. Porém nenhuma delas tinha as três coisas ao mesmo tempo e foi para atender a esses três pontos que o Elixir surgiu, criado pelo José Valim.
Por que a Erlang Virtual Machine?
Porque foi pensada desde o começo para concorrência, distribuição e tolerância a falhas. É uma máquina virtual (VM) que já tem mais de 30 anos de desenvolvimento, sendo bem testada no mercado, ou seja, que tem seu funcionamento garantido.
Em 2011 foi lançado o primeiro protótipo do Elixir. Porém esse “Elixir” tinha um modelo de “objetos”, era muito lento e quebrava compatibilidade com a VM. Depois de vários altos e baixos no desenvolvimento, eles perceberam que o design da linguagem estava errado. Então, redefiniram os direcionamentos do Elixir: produtividade, extensibilidade e compatibilidade. Foram meses de estudo, praticamente sem desenvolvimento ativo e esse novo caminho foi o momento “Eureka!”. Em 2012 decidiram investir e lançar o Elixir - foi em uma conversa entre a diretoria da Plataformatec que Valim os convenceu.
Após quase um ano e meio nessa jornada, veio um período de incertezas sobre o projeto, mas que contou com uma surpresa inesperada. O Dave Thomas, um dos escritores do famoso livro The Pragmatic Programmer (e também um dos fundadores da plataforma Pragmatic Programmers), resolveu escrever o primeiro livro sobre a linguagem Elixir e “evangelizar” sobre a mesma. O efeito foi extremamente positivo e o número de acessos no site do Elixir aumentou consideravelmente.
No ano seguinte veio o ponto de inflexão. Joe Armstrong (in memoriam - #rememberingjoe), um dos criadores do Erlang, fez um post elogiando a linguagem. Ainda em 2013 a O’Reilly anunciou a criação de um livro sobre a linguagem.
Em 2014, tivemos a primeira ElixirConf nos EUA, com cerca de 100 pessoas. O Phoenix também surgiu nessa época, o web-framework do Elixir. Por volta de 2015 surge o Nerves para software embarcado (IoT) com Elixir.
Com a base web estabilizada, em 2016 a parte de ingestão e processamento de dados foi trabalhada na linguagem, com suporte a streaming, concorrência e back pressure. Para a Ptec, foi também quando conseguiram o primeiro cliente Elixir! A Gartner, provavelmente a maior empresa de pesquisa e aconselhamento em tecnologia para grandes corporações, citou o Elixir em seus relatórios.
Hoje em dia, temos milhares de bibliotecas, mais de 30 livros, mais de 15 conferências, mais de 200 meetups. Encontramos vagas de trabalho em empresas do mundo todo. A visão da Plataformatec é que o Elixir é maior do que eles.
Parte 4 - Por que o Elixir tem crescido?
Na opinião do Hugo, são basicamente três fatores: ele crê que a tendência do futuro concorrente veio para ficar; o Elixir te permite pensar diferente e, por fim, possui ferramental para diferentes domínios. E ele explica o porquê:
O futuro é concorrente
O “futuro de 2005” é hoje e a Erlang VM foi projetada para concorrência. Não que seja impossível fazer concorrência com outras tecnologias, mas com Elixir é muito fácil. Concorrência é sobre muito mais do que paralelismo, que te ajuda a fazer software responsivo, distribuído, resiliente… O Elixir/OTP suporta “nativamente” o Manifesto Reativo, pois é:
- Reativo: respondendo rapidamente aos usuários, com um tempo de resposta previsível.
- Resiliente: cada linha de processamento é isolado, com falhas isoladas e supervisores cuidando da saúde de outros processos.
- Elástico: com por exemplo 2 milhões de conexões simultâneas com nenhum timeout.
- Orientado a Mensagens: quando dois processos estão se comunicando, não é necessário saber se eles estão na mesma máquina ou se estão distribuídos, isso é transparente.
Além desses motivos, concorrência ajuda em desenvolvimento, não apenas em produção.
“Tudo que você faz na sua máquina deveria usar todos os cores. Bootar sua aplicação, compilar código, resolver dependências, rodar os testes, etc. Até o seu relógio tem 2 cores. Concorrência não é mais a exceção, é a regra.”
José Valim em The fallacies of web application performance
O Elixir te permite pensar diferente
Aplicações com UI rica e em tempo real estão aumentando a expectativa dos usuários; IoT traz a necessidade de monitoramento real-time de múltiplos dispositivos. Phoenix Channels e Phoenix LiveView permitem funcionalidades real-time facilmente.
Também é possível conceber uma arquitetura de “Nanoserviços”, ao invés do monolito concorrente. Explicando, o Elixir roda milhares ou milhões de processos “leves” na máquina virtual do Erlang, com alguns deles se comunicando entre si, se supervisionando… alguns na mesma máquina, outros em outra, sem problemas. É como se fosse um monolito distribuído. Para se aprofundar no assunto, leia os posts Elixir in times of microservices e Dawn of the Microlith - Monoservices with Elixir.
Ferramental para diferentes domínios
O Elixir tem ferramental para diferentes domínios técnicos:
- Phoenix para aplicações web, APIs, backend para mobile e dispositivos IoT
- GenStage, Flow e Brodway para ingestão e processamento de dados
- Nerves para dispositivos embarcados
- e muitos outros, para streaming de mídia, etc.
Conclusões
Para concluir, o Hugo deixou algumas reflexões.
Concorrência é uma grande evolução na maneira como escrevemos software
Herb Sutter - tradução livre
Ainda que alguém pense que:
Entendi, concorrência é bom para fazer software reativo, com alta performance, escalável, distribuído, que não cai, etc. Mas minha app é muito simples, eu não preciso de tudo isso. Não preciso de concorrência.
Alguém
este pode ser um pensamento muito simplista. Talvez hoje seja assim, mas não sabemos como será no futuro, quais serão os requisitos. Mas como sabemos que o futuro é concorrente, ou seja, faz todo o sentido.
Como sair do nada para uma comunidade mundial? Segundo a Ptec, isso pode ser alcançado com visão; execução e persistência; e com a ajuda da comunidade open-source. Por fim, a foto abaixo mostra o número de colaboradores nas empresas criadoras das linguagens:
Confira os slides da apresentação no SpeakerDeck.
Processamento paralelo de mensagens em Elixir - Erich Rodrigues
Como o evento conta com duas trilhas, decidi seguir na trilha avançada, com essa talk do Erich Rodrigues sobre processamento de mensagens, que ele já esclarece serem mensagens de texto, mais especificamente da plataforma do WhatsApp. É uma talk sobre o case técnico de produto, como resolveram o problema que tiveram.
Ele é tech lead do Squad de Relacionamento com o Cliente na empresa QueroEducação. Eles possuem um sistema chamado OperatorPAnel (OPA), usado para o relacionamento integrado com telefonia, WhatsApp, entre outros. No exemplo de uma faculdade, o sistema possui informações de todos os alunos e permite a interação como se estivéssemos no WhatsApp Web, porém totalmente integrado na plataforma, trazendo insights e os dados de cada aluno durante a interação.
Esse painel OPA foi totalmente desenvolvido em Elixir e Phoenix, lá por 2016, sendo uma aposta que ocorreu bem cedo em relação a linguagem, pois eles já tinham a visão de chegar no que de fato é a plataforma hoje, mostrando que foi uma decisão correta na época. Outra aposta que eles fizeram foi de usar o Vue.JS com Nuxt. Eles usam o banco PostgreSQL, além de Redis com a biblioteca Nebulex para cache e Algolia para busca. Eles conseguiram entrar no programa fechado da API Beta do WhatsApp, sendo uma das duas primeiras empresas do Brasil a testar o serviço - lá em 2017.
Eles recebem centenas de milhares de mensagens todos os dias, caindo até em problemas não pensados como Bots de WhatsApp, muitas mensagens de SPAM e delay nos serviços. O Atendimento, para eles, precisa ser eficiente, eficaz e de alta qualidade.
Numa primeira abordagem para resolver o problema proposto, o Erich fez um sistema com tudo síncrono, usando HTTP. Ele mostrou o código que lida com o seguinte: mensagem chegando, processando a mensagem sincronamente, Vue.JS fazendo polling. Como resultado, o limite de conexões com o banco fica ok, e o limite de concorrência com o mesmo processo também fica ok. Como problemas, tempo entre a mensagem ser enviada até chegar no operador do sistema; bloqueio desnecessário no envio da mensagem; tempo de interação e desperdício de recursos.
Na real abordagem agora, com processamento paralelo das mensagens com Elixir, o Erich explica por cima como funcionam os Processos do Elixir: são leves, utilizam pouca memória, sem compartilhamento dessa memória, só funcionam por passagem de mensagens e são lidados pela VM do Erlang. Um jeito bem simples de criar um processo é através da função spawn:
spawn fn -> 1 + 2 end
Temos também as Tasks, com async/1
e
await/2
:
task = Task.async(fn -> do_some_work() end)
res = do_some_other_work()
res + Task.await(task)
E os GenServers:
defmodule Stack do
use GenServer
# Callbacks
@impl true
def init(stack) do
{:ok, stack}
end
@impl true
def handle_call(:pop, _from, [head | tail]) do
{:reply, head, tail}
end
@impl true
def handle_cast({:push, item}, state) do
{:noreply, [item | state]}
end
end
# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])
# This is the client
GenServer.call(pid, :pop)
#=> :hello
GenServer.cast(pid, {:push, :world})
#=> :ok
GenServer.call(pid, :pop)
#=> :world
Ele citou também o Poolboy, uma biblioteca Erlang para limitar a quantidade de processos simultâneos, para limitar por exemplo o acesso ao banco de dados com um processo por usuário. Inclusive, ele ressaltou que é a maneira utilizada pelo Ecto para interagir com o banco.
Os Phoenix Channels tornam possível a comunicação entre milhões de processos, podendo ser usados por exemplo para notícias em tempo real, rastreamento, eventos em jogos multiplayer, sensores, chats… Esses channels (canais) conseguem fazer comunicação bidirecional, por exemplo entre o backend e o frontend, podendo substituir o polling da primeira abordagem.
Na segunda abordagem, o tempo entre a troca de mensagens é ok, não tem bloqueios desnecessários, o tempo de interação é ok, não há desperdício de recursos e ainda assim há limites de conexões e de processos de forma OK.
Foi bem legal ver os exemplos que ele mostrou e poder refletir sobre como estruturar uma aplicação nesses moldes. Muda um pouco a forma como estamos (ou pelo menos eu) acostumados a fazer software, mas não é nada de outro mundo.
Os slides da apresentação estão disponíveis no Google Presentations.
Eventos de domínio podem ser simples - Bernardo Amorim
Ainda na trilha avançada, o Bernardo veio trazer o case da Stone, empresa em que trabalha, sobre como foi o uso de Event Sourcing por lá. Ele diz que se deparou com a buzzword “Eventos” e foi estudar sobre, chegando à seguinte definição:
“Evento é algo que aconteceu, um fato”
É uma informação que aconteceu no passado. Um conceito relativamente simples porém que, sem cuidado, pode ser usado de forma errada.
Para o Martin Fowler, sistemas event-driven consistem em sistemas que possuem notificação por evento, transferência de estado por eventos, event sourcing, CQRS e event collaboration.
Explicando Event Sourcing, basicamente o seu estado não é mais uma informação que você simplesmente pega do banco de dados, ele é inferido através da análise de todos os eventos que ocorreram anteriormente. Para dar um exemplo real, o próprio git é uma aplicação que utiliza o conceito de event sourcing. O seu código (estado) é a aplicação de todos os commits (eventos) e você pode navegar entre eles.
A jornada Event-Driven da Stone começou com a biblioteca Commanded, que te dá algumas facilidades para construir um sistema desse tipo. O Bernardo mostrou alguns exemplos para criar uma conta de banco utilizando esses conceitos, que eles tentaram fazer na Stone. Ele inclusive citou alguns problemas que tiveram:
Dificuldades
- Eventos são imutáveis - e você vai errar. Você pode até querer editá-los no banco de dados e etc, mas não é assim que eventos deveriam ocorrer, vistos que eles são fatos.
- Consistência eventual - o seu sistema não vai estar consistente a todo momento, cada parte processa os eventos e muda o estado em seu próprio tempo.
- Existem muitos conceitos - como todos os citados, mais aggregate, etc etc…
- Treinamento - barreira de entrada para novos desenvolvedores.
Resumindo, muita complexidade acidental foi criada no projeto. Assim, eles precisaram refletir e agir.
Nem tudo precisa ser Event Sourcing
Dando um passo atrás, viram que nem tudo precisava dessa complexidade. Por exemplo, máquinas de estado finitas, podem ser representadas em tabelas no banco de dados, com cada estado sendo uma coluna dessa tabela. Assim, fica simples de gerenciar com o Ecto, criando migrations, etc.
Algumas coisas o deixam nostálgico:
Saudades
- Linguagem de domínio - eventos capturam uma mensagem poderosa sob o domínio da sua aplicação.
- Muitos efeitos são desacoplados, por exemplo, o código de abertura de conta não precisa ter nenhuma lógica para enviar emails, essa lógica pode estar desacoplada e escutando o evento de nova abertura de conta.
- Processamento assíncrono - não são mais necessárias tantas queues, podendo ser somente eventos.
- Auditoria facilitada - você possui todos os eventos que ocorreram!
Explorando alternativas
Nesse momento, o Bernardo fez uma live coding pra gente, explorando tudo o que ele passou de conhecimento na sua palestra. E assim ele fechou sua apresentação - um mergulho em eventos, com teoria e prática.
O código apresentado por ele na live coding e o os slides da apresentação estão no GitHub.
TOP: Criando seu próprio GenServer - Geovane Fedrecheski
Nessa talk da trilha iniciante, o Geovane busca, através de live coding, responder a três perguntas: qual a diferença entre códigos sequenciais versus códigos concorrentes; como guardamos estado nos processos e, por último, se o GenServer pode ajudar.
Contextualizando, em Erlang (e Elixir) tudo é um processo. Ou seja, não precisamos nos preocupar com semáforos, mutex, etc. Porém, código concorrente é mais difícil de se fazer do que código sequencial.
Utilizando-nos da OTP (Open Telecom Platform), temos algumas facilidades para escrever código concorrente. Além disso, existem abstrações trazidas para a plataforma pelo próprio Elixir, como o GenServer. Segundo a própria documentação, um GenServer é um processo como qualquer outro, que mantém estado, executa código de maneira assíncrona e assim por diante…
Assim, para nos ensinar sobre os processos e suas trocas de mensagens, o Geovane
fez um live coding, mostrando os conceitos do spawn
e do GenServer
através
de um exemplo prático.
Após o live coding, ele esclarece às três perguntas:
- Guardamos estado em loops e usamos
send
ereceive
para “nos comunicar com o loop”. - Devemos separar o código concorrente do sequencial em nossos sistemas.
- O GenServer abstrai a escovação de mensagens, permitindo-nos focar na lógica de negócio.
Confira os slides da apresentação no SpeakerDeck.
Talvez você não precise de um GenServer - Ulisses Almeida
Para ter um comparativo sobre a talk anterior, quis voltar para a trilha avançada e ver essa palestra do Ulisses agora que estava entendendo um pouco melhor como funciona os GenServers.
Segundo ele, um GenServer é um processo com um conjunto poderoso de ferramentas. Útil para uma relação cliente-servidor, mantém estado, executa código síncrona e assincronamente, pode ser supervisionado e provê ferramentas de tracing e error reporting.
Existem alguns tipos de GenServers: Agent
; Task
; Task.Supervisor
;
Supervisor
e DynamicSupervisor
.
Fazendo um exemplo, o Ulisses mostrou como fazer uma calculadora com o uso de um GenServer e uma sem, chegando a conclusão após alguns benchmarks que o GenServer é cerca de 200 vezes mais lento e gasta quase 10 vezes mais recursos.
Segundo ele, a Erlang OTP provê hierarquia de processos, estratégias de reinício dos mesmos, concorrência e o GenServer tem um papel central nisso tudo.
Refletindo sobre os motivos para se utilizar GenServer, disponível na checklist da imagem abaixo, talvez a calculadora não foi um bom caso para o uso dessa funcionalidade.
Mas, eu não deveria estar usando essa funcionalidade da linguagem?
Por sua própria experiência, o Ulisses nunca escreveu um GenServer para código em produção em três anos trabalhando com a linguagem - inclusive disse que já apagou alguns que existiam para tirar gargalos da aplicação. E diz que não há vergonha em nunca precisar criar um GenServer.
O Ecossistema te dá cobertura!
Para uma aplicação web, que é acessada por usuários, devemos ser capazes de lidar com múltiplas conexões de usuários, outros processos podem mandar mensagens para essas conexões e devemos poder fechar essas conexões de maneira limpa e segura. Parece uma boa oportunidade de usar um GenServer, com base no checklist? Bem, parece, porém Phoenix e Cowboy já lidam com isso para você, então não é necessário fazer isso por conta própria.
Para a interação da aplicação com banco de dados, um banco de dados é uma comunicação externa; nós temos que prover um número limitado de conexões para muitos processos; essas conexões devem funcionar e devemos fechar essas conexões de maneira limpa e segura. Parece um outro bom caso para o GenServer? Pode até ser, mas o Ecto já faz isso pra gente.
Para a comunicação da aplicação com outros serviços, normalmente nos comunicamos pela rede; temos que ter um mecanismo para não derrubar serviços mais fracos; esse mecanismo tem que lidar com concorrência e também devem fechar de maneira limpa e segura. GenServer? Adivinha, o Hackney e outras bibliotecas já fazem isso pra gente.
Nem tudo é armazenado em bancos de dados SQL, como cache, sessão, presença de usuários. Geralmente vemos implementações NoSQL para resolver esses problemas. Mas, esses dados são persistentes ou transitórios na sua aplicação? Será um bom caso pra GenServer?
Talvez você precise uma abstração mais específica, como por exemplo o Cachex para cache. Talvez o Redis seja mais fácil de lidar do que alguns GenServers, que podem ter o risco de shutdown em deploys e etc. O ideal é discutir entre o time para encontrar a ferramenta certa para o problema.
Ou talvez ainda que queira usar para tarefas como serialização, mas seu ambiente não permita um GenServer global rodando, como no caso da plataforma Heroku.
Para tarefas em background, como emails, push-notifications, tarefas agendadas e fora do ciclo do usuário, precisamos de alguma forma de rodá-las. Pode parecer que finalmente tenhamos chegado num caso válido, porém, como lidamos com erros? Precisamos de algum tipo de persistência para esses eventos? Talvez podemos usar algo como RabbitMQ ou Redis mesmo.
Após apresentar todos esses casos de uso, chegamos nas conclusões.
Conclusões
GenServers são fundamentais. As bibliotecas e frameworks lidam com eles para a gente, então talvez não precisemos criar os nossos. É importante aprendê-los para saber fazer a configuração correta dessas ferramentas, bem como saber lidar com situações inesperadas e tudo o mais. O importante é sempre comparar e discutir com o time as ferramentas corretas. Não é vergonha nunca precisar de um GenServer. Lembre-se de que um GenServer utilizado em um local incorreto pode trazer mais problemas do que benefícios.
O Ulisses é autor do livro
Learn Functional Programming with Elixir - New Foundations for a New World
e disponibilizou um cupom de desconto de 20% para comprar o mesmo:
Elixir_Brazil_2019
. Para conferir os slides de sua talk,
clique aqui.
Essa foi um talk bem divertida, o Ulisses é particularmente engraçado e foi bom pra pegar um ânimo pro resto do dia - ainda tinha muita coisa pra escrever!
Conjuntos em 3 Atos - Luciano Ramalho
O Luciano Ramalho, da ThoughtWorks, famoso pelo seu livro de Python, preparou essa palestra a partir de outras que tinha feito para as linguagens Go e Python. Então essa é a versão Elixir. Ele busca explicar porque e como devemos usar Conjuntos.
Porque conjuntos podem simplificar seu código
Para nos convencer disso, o Luciano propõe três casos de uso. O primeiro deles é “exibir item se todas as palavras da consulta aparecerem na descrição” aplicado a um buscador de emojis por palavras-chave.
Ele implementou em Elixir, a partir do arquivo
UnicodeData.txt
.
É possível tomar uma abordagem sem conjuntos, “desconjuntada”, que usa
substring
s e vários if
s para fazer essa busca por emoji. Ele explica
detalhadamente pra gente o algoritmo e faz reflexões sobre a maneira que ele
funciona.
John Backus, um dos criadores da linguagem FORTRAN, levanta uma questão: Será que a programação pode ser liberada do estilo von Neumann? Muitas linguagens estão apegadas ao fato de a CPU trabalhar com uma palavra de cada vez. Pensando no exemplo, podemos usar a teoria dos conjuntos da matemática, onde um conjunto está contido dentro de outro conjunto e tentar chegar a uma solução assim no código.
Assim, ele mostra sua solução em Elixir, que está disponível no
Github. Ela se baseia no
MapSet
da linguagem.
No segundo caso de uso para Conjuntos, a solução que ele chamou de Gimel é similar à primeira solução na funcionalidade, porém tem uma estratégia diferente para resolver e também tem um terminal interativo onde você pode ficar buscando por emojis. Nessa versão, o arquivo unicode é lido uma única vez, gerando dois índices que são mantidos na memória e usados para a busca. É a estratégia do “índice invertido”. Essa solução também está no GitHub.
Para o terceiro caso de uso, dá o exemplo de uma Loja Online: “Destacar todos os produtos favoritados, exceto aqueles que já estão no carrinho de compras”. É um problema trivial de se resolver com a teoria dos conjuntos, representando uma operação de diferença entre conjuntos.
Conjuntos em várias linguagens
Linguagem | Recurso | Nível |
---|---|---|
Elixir | MapSet: 14 métodos | 😻 |
Ruby | Set: > 10 métodos e operadores | 😻 |
Python | set, frozenset: > 10 métodos e operadores | 😻 |
.Net(C# e etc.) | ISet: > 10 métodos; 2 implementações | 😻 |
JavaScript (ES6) | Set: < 10 métodos | 😿 |
Java | Set interface: < 10 métodos; 8 implementações | 😿 |
Go | Faça você mesmo, ou escolha um dos N pacotes | 😾 |
A API do MapSet
do Elixir é bastante rica. Com base na sugestão presente no
livro “The Go Programming Language”, de Alan A. A.
Donavan & Brian W. Kernighan - que é um dos melhores livros já lidos pelo
Luciano, ele tenta implementar o seu próprio UIntSet
, que utiliza os bits
para fazer seus conjuntos. Ele explicou detalhadamente como foi feita sua
solução utilizando bits
e recursos do Elixir.
Conclusões
Operações com conjuntos podem simplificar algoritmos dramaticamente. Elixir
oferece uma implementação rica! O código do MapSet é um excelente exemplo de
abstração de dados usando structs
e protocols
. A interface de UIntSet
é
quase a mesma de MapSet
mas a implementação é mais simples, com operadores
Bitwise
para manipular inteiros como vetores de bits.
O código do UIntSet
mostrado na apresentação está disponível
no GitHub. E os slides, que contém
diversos exemplos e explicações que deixei de fora aqui, estão no
SpeakerDeck.
Elixir: o que pode dar errado? - Guilherme de Maio
O Guilherme - também conhecido como Nirev - é um dos organizadores do Meetup de Elixir aqui em São Paulo, foi um dos apresentadores do evento e veio falar sobre sua experiência com o ecossistema Elixir. O objetivo dessa talk foi a de fazer um contraponto aos benefícios do Elixir com problemas enfretados por ele e as equipes em que trabalhou.
Dando um pouco de contexto antes de fato citar o problema, mostrou algumas
coisas que normalmente associamos à BEAM
:
- Modelo de atores
- Processos isolados
- Garbage Collector por Processo
- Super escalável, só escreve e funciona!
Porém essa é uma visão limitada que ele quer por à prova. Então, ele vai ensinar: como quebrar sua app!
O caso da tonelada de átomos:
Podemos transformar um JSON
em átomos da linguagem utilizando
Poison:
Poison.decode(body, keys: :atoms!)
Porém, nossa tabela de átomos tem um limite, configurável, mas ainda assim um
limite e dependendo da nossa mensagem JSON, podemos explodir nossa aplicação!
Você pode até pensar: então é só não fazer isso! Porém esse foi só um exemplo, e
talvez você pode chegar nesse limite por conta de algumas coisas: nomes de
módulos, nomes de nós, campos de struct
e “decode as atom”.
O caso do Agent
linkado
-------------- start_link --------------------
| Processo 1 | -------------> | Processo Linkado |
-------------- --------------------
Caso seu processo 1 morra, o processo linkado morre também. Porém caso o término do processo 1 seja “normal”, o processo linkado continua vivo! Esse é o comportamento padrão, está na documentação, porém pode ser uma armadilha caso você não se atente, com processos “zumbis” que podem explodir a quantidade máxima de processos, quebrando sua app.
O caso da monitoração de Requests
A ideia era monitorar requests, subindo um agent
que receberia os
breadcrumbs das chamadas dentro da aplicação no request caso ele falhasse, ou
apenas morreria se o request ocorresse normalmente. Era criado um monitor para o
processo e o agent
. Porém o keep-alive
do Cowboy faz processos serem
reutilizados sem morrerem, e o plug
utilizado para subir o monitor era chamado
continuamente, criando um novo agent
por request e quebrando a aplicação
eventualmente.
O caso dos Reinícios Infinitos
Toda vez que um processo morria, um Task Supervisor
, configurado com
restart: transient
subia uma Error Reporter Task
que reportava erros para
uma API externa, porém se a API externa estivesse indisponível, fazia com que
essas tasks morressem e que o supervisor subisse novas tasks
para reportar
esses novos erros. Isso também gerava um monstro com mais erros explodindo e
novas tasks subindo apenas para falhar novamente. Uma solução simples nesse caso
foi o uso de restart: temporary
.
O caso do Message Router
Esse caso ocorreu em uma aplicação feita para um dispositivo de rastreamento de
veículos de uma frota. Essa aplicação se comunicava via TCP com uma API. E por
sua vez, essa API também poderia enviar comandos para cada dispositivo. Na
implementação feita pela equipe do Guilherme, existia um GenServer
para cada
dispositivo/veículo. As mensagens passavam por um Message Router
que passavam
as mensagens para a frente. Mesmo sem persistência dessas mensagens e sendo
relativamente muito simples, o Message Router
estava morrendo. Depois de uma
longa inspeção, descobriram que o problema se devia ao funcionamento do garbage
collector e o fato de o Message Router
utilizar pouca memória para seu
funcionamento, apenas passando as mensagens para os GenServer
s e não chegando
a ativar o garbage collector, ficando eventualmente sem memória - irônico, né?
O que fazer quando isso acontece (ou antes de acontecer!)
- Introspecção: a possibilidade de você se conectar a um nó e analisar o que
está acontecendo nele. Algumas funções como
Process.list/0
,Process.info/1
,:sys.get_*
ou até mesmo módulos criados por você comoMeuModulo.minha_task()
podem ajudar! Também podemos utilizar o:observer.start()
ou o Wobserver, uma interface web para oobserver
que não necessita que você se conecte ao nó. A biblioteca erlang Recon já possui bastante helpers para ajudar na introspecção, como:recon.bin_leak(3)
que roda o GC para todos os processos e mostra os que liberaram mais memória (talvez significando memory leaks) - Coletar e analisar métricas da VM. Temos algumas bibliotecas para ajudar,
como a vmstats, que manda métricas pro
statsd
; a deadtrickster/prometheus.ex para mandar para o Prometheus ou ainda a telemetry, que é bem leve e você pode customizar como quiser. - Ter visibilidade do que acontece na aplicação. Podemos fazer agregação de logs, usando por exemplo o Graylog e ferramentas que coletam erros da sua aplicação, como o Sentry ou o Bugsnag, dentre outras.
Conclusão
Tem várias maneiras de quebrar sua aplicação Elixir, então não vá para produção sem visibilidade. Há um livro gratuito “Stuff goes bad: Erlang in Anger” sobre o que fazer quando as coisas derem errado em Erlang e como atuar, porém a recomendação do Guilherme é de que você leia de cabeça fria, você não vai querer fazer isso enquanto as coisas estão pegando fogo.
O Guilherme trouxe diversos casos de problemas que ele e seus colegas enfrentaram em produção e vários insights legais de como evitar que isso aconteça. Espero aprender com ele e não passar pelas mesmas tretas :)
Os slides contém algumas explicações melhores e também alguns snippets para quebrar sua aplicação 😈
Keynote de encerramento do primeiro dia - Edward Wible
O Edward é co-fundador e CTO do Nubank e o keynote de encerramento do primeiro dia do evento foi com ele. Esse keynote foi no formato entrevista, onde o Alexandre Cisneiros, que trabalha no Nubank, foi o entrevistador. A ideia era fazer um apanhado de como foram esses 6 anos de Nubank. Tentei fazer o meu melhor para escrever sobre esse bate-papo mas confesso que foi bem difícil e alguns assuntos foram perdidos e muitos estão desconexos, sem uma linha de raciocínio lógica. De qualquer forma, consegui reunir algumas coisas interessantes e por isso resolvi postar :)
Segundo o Edward, todos os erros possíveis foram cometidos nesses 6 anos. Porém, ainda assim, contam hoje com mais de 8,5 milhões de clientes!
“Qual foi o maior desafio para escalar o Nubank?”, indaga Alexandre. E assim descobrimos que o mais estressante mesmo foi o lado humano, com 8 engenheiros no início, sem gerentes, sem alguns papéis sendo desempenhados, ocorrendo bem no processo “hacking” mesmo. O que o Edward aprendeu a duras penas é que colocar pessoas técnicas como gerentes pode não ser a melhor coisa, e contou de um movimento que vem ocorrendo por lá agora onde muitos desses engenheiros estão voltando para o lado técnico.
Tecnicamente falando agora, o que foi difícil no movimento de escalar foi o domínio. Ocorreram alguns erros de modelagem que até hoje assombram o negócio. Com a escala que o Nubank conquistou, todos esses erros devem ser investigados para cobrir os casos, mesmo afetando pouquíssimos usuários. Chegar a 100% de automatização é muito difícil e alguns engenheiros têm que deixar de entregar algumas novas funcionalidades para poder analisar esses casos. Ele comentou ainda que existe um Kafka na zona A do AWS em São Paulo e outro na zona C (já que a B nunca funcionou mesmo). Ele também comentou que ocorreram alguns erros de monitoria de serviços, que estão evoluindo hoje em dia.
Mesmo desde o começo, não quiseram fazer uma arquitetura “bagunçada” e depois evoluir quando o dinheiro viesse para a startup, algo que vemos por acontecer em outras empresas. Então, desde o começo buscaram montar algo sólido, com as soluções corretas para os problemas.
Ainda assim, alguns serviços cresceram muito, como o Account
que na verdade
estava representando 8 serviços do domínio. Eles tiveram de ser divididos, o que
é um processo difícil de se fazer enquanto se garante a disponibilidade do
serviço para os clientes. Ele também citou que alguns casos de dependências
circulares (um serviço que depende de outro que depende de novo do primeiro)
acabaram levando a indisponibilidades parciais. Eles também sofreram um pouco
com o RefluxDB, mas não consegui ouvir o porquê.
A decisão de usar tudo em uma conta só do AWS foi difícil e hoje em dia estão quebrando em diversas contas - a abordagem melhora a organização.
Uma coisa legal foi que eles não tinham medo de nada porque tinham “ignorância total” sobre a complexidade do que estavam fazendo. E citou que cartão de crédito é muito difícil, sendo que 6 anos depois de lançar o serviço, muito ainda está sendo desenvolvido.
Vários segmentos ainda não estavam automatizados quando eles tinham “apenas” 100 mil clientes. Por exemplo, o controle de chargebacks era feito no Excel. Ainda hoje existem problemas de consistência e de domínio.
Para garantir performance e acesso aos dados, cada cliente do Nubank hoje em dia
vive em um só shard dos bancos de dados. A comunicação entre clientes em
diferentes shards ocorre com um router
global. Eles começaram a fazer essa
migração quando estavam com cerca de 800 mil clientes e terminaram quando
estavam com 2 milhões - número inclusive que é o tamanho máximo de cada um
desses shards.
No desafio de pessoas na Engenharia, com a escala de hoje, existem mais oportunidades de ter pessoas especialistas em temas muito específicos. Existem times horizontais para apoiar os próprios colaboradores, com papéis de infraestutura, especialistas de Redis, Kubernetes, etc. Com a plataforma mais sofisticada, os desenvolvedores conseguem ter mais produtividade e entregar produtos mais rapidamente.
O Nubank tem cerca de 240 serviços, que é mais ou menos o número de pessoas na engenharia. A cultura é um grande desafio com a chegada constante de novos desenvolvedores. O onboarding hoje tem um processo de uma semana, mas em uma reflexão, o Edward comenta que talvez seria legal um processo de um mês…
Eles estão investindo em mais documentação escrita, mais ferramentas e menos em deixar o conhecimento como algo falado, facilitando o treinamento de novos engenheiros e descentralizando o conhecimento. Também estão fazendo Requests for Comments (RFCs) para avaliarem decisões técnicas que são impactantes, permitindo a outros times poder colaborar com as decisões.
Nesses anos, muitos desenvolvedores entraram querendo usar outras tecnologias além das já utilizadas, porém eles tentaram manter a consistência e disciplina de usar Clojure. Isso facilita em entender como outros serviços funcionam (basta ler o código) e foi uma decisão acertada segundo o Edward, com ótimos frutos. Não é uma decisão de “religião” a respeito da linguagem, mas puramente de consistência mesmo. Ainda assim usam algumas coisas em Scala, em Python, para tarefas específicas em ecossistemas já desenvolvidos nessas linguagens.
Na aplicação web, o frontend sofre mudanças constantes então eles não forçam essa consistência tão grande. Acabam usando também React Native para algumas funcionalidades dos apps. O Nubank está tentando atender a todas as funcionalidades que os clientes pedem no app, que ainda está atrás quando comparado a outros bancos. Ele sente falta dessa consistência no backoffice, os componentes ainda podem ser feitos de diversas maneiras diferentes.
Hoje possuem 4 ou 5 produtos no Nubank, sendo que antes era só o cartão de crédito. Sofreram com muitos problemas quanto a isso por algumas coisas “assumidas”: por exemplo, assumir que o usuário possui cartão de crédito - não é mais sempre verdade - e agora contam com vários testes de regressão, etc. Foi difícil entender o que é específico sobre cartão de crédito e o que fazia sentido para outros produtos. Com a expansão internacional, tiveram que repensar ainda o que era específico para o Brasil e o que era geral. Foi uma decisão de focar exatamente no produto no começo e hoje estão pagando o custo disso.
A resolução de incidentes no Nubank está cada vez maior e mais assertiva. No começo a plataforma era muito estável, com serviços de pé por 18 meses, realidade que foi mudando com a escala. Com algumas instabilidades, foram melhorando esse ferramental e cultura. Tudo agora fica dentro do Kubernetes, o que facilitou algumas coisas.
Os times de desenvolvimento possuem métricas de negócio também. E eles tem até monitoramentos por ligações telefônicas (se der ruim, o telefone toca!), o que deixa os engenheiros menos exaustos por não terem que checar o Slack constantemente para saber se está tudo bem.
Para os próximos seis anos, ele prevê que algumas coisas vão mudar - porém cultura não é uma delas. Acredita que trabalhos remotos vão crescer e que vai haver um investimento massivo nisso. Isso vai mudar um pouco a maneira como trabalham, podendo ser eficientes ainda que não na mesma sala.
Isso foi tudo o que conseguir pegar do keynote - foram várias dicas em várias áreas, mas que pode ajudar muitos projetos. O Edward é uma pessoa bem lúcida e foi muito legal poder contar com esses insights - outras empresas e líderes talvez não seriam tão transparentes.
Com isso, chegamos ao fim do primeiro dia desse evento incrível - talvez o melhor que já fui. E ainda tem o segundo dia pra contar!
Para conferir como foi o segundo dia, clique aqui.