Por que mudamos de Python para Go

Atualizado em 14 de maio de 2019 para refletir melhor as melhorias para Go nos últimos 2 anos (gerenciamento de pacotes, melhor desempenho, tempos de compilação mais rápidos e um ecossistema mais maduro) Mudar para uma nova linguagem é sempre um grande passo, especialmente quando apenas um dos membros de sua equipe tem experiência prévia com essa linguagem. No início deste ano, trocamos a linguagem de programação principal do Stream de Python para Go. Este post vai explicar algumas das razões pelas quais decidimos deixar Python para trás e fazer a mudança para Go. Obrigado a Ren Sakamoto por traduzir Why we switched from Python to Go para japonês, なぜ私達は Python から Go に移行したのか.

Reason 1 – Performance

Go é rápido! Go é extremamente rápido. O desempenho é semelhante ao de Java ou C++. Para o nosso caso de uso, Go é tipicamente 40 vezes mais rápido que Python. Aqui está um pequeno jogo de benchmark comparando Go vs Python.

Reason 2 – Language Performance Matters

Para muitas aplicações, a linguagem de programação é simplesmente a cola entre a aplicação e o banco de dados. A performance da linguagem em si normalmente não importa muito. Stream, no entanto, é um provedor de API que alimenta uma plataforma de feeds e chat para 700 empresas e mais de 500 milhões de usuários finais. Nós temos otimizado Cassandra, PostgreSQL, Redis, etc. por anos, mas eventualmente, você atinge os limites da linguagem que você está usando. Python é uma ótima linguagem, mas seu desempenho é bastante lento para casos de uso como serialização/deserialização, ranking e agregação. Frequentemente encontramos problemas de performance onde Cassandra levava 1ms para recuperar os dados e Python gastava os próximos 10ms transformando-os em objetos.

Razão 3 – Produtividade do Desenvolvedor &Não Ficando Muito Criativo

Dê uma olhada neste pequeno trecho de código Go do tutorial How I Start Go. (Este é um ótimo tutorial e um bom ponto de partida para pegar um pouco de Go.)

Se você é novo no Go, não há muito que o surpreenda ao ler esse pequeno trecho de código. Ele mostra múltiplas atribuições, estruturas de dados, apontadores, formatação e uma biblioteca HTTP embutida. Quando comecei a programar, sempre adorei usar os recursos mais avançados do Python. Python permite que você seja bastante criativo com o código que está escrevendo. Por exemplo, você pode:

  • Utilizar MetaClasses para auto-registrar classes na inicialização do código
  • Swap out True and False
  • Adicionar funções à lista de funções embutidas
  • Overregar operadores via métodos mágicos
  • Utilizar funções como propriedades via @property decorator

Essas características são divertidas de se brincar com mas, como a maioria dos programadores concordarão, eles muitas vezes tornam o código mais difícil de entender quando lêem o trabalho de outra pessoa. Vá forçá-lo a se ater ao básico. Isto torna muito fácil ler o código de qualquer pessoa e imediatamente entender o que está acontecendo. Nota: O quão “fácil” é realmente depende do seu caso de uso, é claro. Se você quer criar uma API CRUD básica, eu ainda recomendo Django + DRF, ou Rails.

Reason 4 – Concurrency & Channels

As a language, Go tenta manter as coisas simples. Ele não introduz muitos conceitos novos. O foco está em criar uma linguagem simples que seja incrivelmente rápida e fácil de trabalhar. A única área em que ela se torna inovadora é a dos goroutinos e canais. (Para ser 100% correto o conceito de CSP começou em 1977, por isso esta inovação é mais uma nova abordagem a uma idéia antiga). Os goroutinos são a abordagem leve do Go ao threading, e os canais são a forma preferida de comunicação entre os goroutinos. Os goroutinos são muito baratos para criar e só levam alguns KBs de memória adicional. Como os goroutinos são tão leves, é possível ter centenas ou mesmo milhares deles funcionando ao mesmo tempo. Você pode se comunicar entre os goroutinos usando canais. O Go runtime trata de toda a complexidade. O Goroutines e a abordagem baseada em canais para concorrência torna muito fácil usar todos os núcleos de CPU disponíveis e lidar com IO concorrente – tudo sem complicar o desenvolvimento. Comparado ao Python/Java, executar uma função em um goroutine requer o mínimo de código de placa de caldeira. Você simplesmente prepende a chamada de função com a palavra-chave “go”:

https://tour.golang.org/concurrency/1 Go’s approach to concurrency é muito fácil de trabalhar. É uma abordagem interessante em comparação com Node onde o desenvolvedor tem que prestar muita atenção à forma como o código assíncrono é tratado. Outro grande aspecto da concurrency in Go é o detector de corridas. Isto torna mais fácil de descobrir se há alguma condição de corrida dentro do seu código assíncrono.

Knock knock Race condition Quem está lá?

– Eu sou Devloper (@iamdevloper) Novembro 11, 2013

Aqui estão alguns bons recursos para começar com Go e canais:

  • https://gobyexample.com/channels
  • https://tour.golang.org/concurrency/2
  • http://guzalexander.com/2013/12/06/golang-channels-tutorial.html>
  • https://www.golang-book.com/books/intro/10>
  • https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html>
  • Goroutines vs Green threads

Razão 5 – Tempo de Compilação Rápida

O nosso maior micro serviço escrito em Go demora actualmente 4 segundos a compilar. Os tempos rápidos de compilação do Go são uma grande vitória de produtividade em comparação com linguagens como Java e C++ que são famosas pela velocidade de compilação lenta. Eu gosto de lutar com espadas, mas é ainda melhor fazer as coisas enquanto ainda me lembro o que o código deve fazer:

Razão 6 – A Capacidade de Construir uma Equipe

Primeiro de tudo, vamos começar com o óbvio: não há tantos desenvolvedores Go em comparação com linguagens mais antigas como C++ e Java. Segundo o StackOverflow, 38% dos desenvolvedores conhecem Java, 19,3% conhecem C++ e apenas 4,6% conhecem Go. Os dados do GitHub mostram uma tendência similar: Go é mais amplamente utilizado do que linguagens como Erlang, Scala e Elixir, mas menos popular do que Java e C++. Felizmente, Go é uma linguagem muito simples e fácil de aprender. Ela fornece as características básicas que você precisa e nada mais. Os novos conceitos que introduz são a declaração “defer” e a gestão integrada da simultaneidade com “go routines” e canais. (Para os puristas: Go não é a primeira linguagem a implementar estes conceitos, apenas a primeira a torná-los populares). Qualquer Python, Elixir, C++, Scala ou Java dev que se junte a uma equipe pode ser eficaz no Go dentro de um mês por causa de sua simplicidade. Nós achamos mais fácil construir uma equipe de desenvolvedores Go em comparação com muitas outras linguagens. Se você está contratando pessoas em ecossistemas competitivos como Boulder e Amsterdam este é um benefício importante.

Razão 7 – Ecossistema Forte

Para uma equipe do nosso tamanho (~20 pessoas) o ecossistema importa. Você simplesmente não pode criar valor para seus clientes se você tiver que reinventar cada pequena parte da funcionalidade. Go tem um grande suporte para as ferramentas que usamos. Bibliotecas sólidas já estavam disponíveis para Redis, RabbitMQ, PostgreSQL, análise de templates, agendamento de tarefas, análise de expressões e RocksDB. O ecossistema Go é uma grande vitória em comparação com outras linguagens mais recentes como Rust ou Elixir. É claro que não é tão bom quanto linguagens como Java, Python ou Node, mas é sólido e para muitas necessidades básicas você encontrará pacotes de alta qualidade já disponíveis.

Razão 8 – Gofmt, Enforced Code Formatting

Comecemos com o que é Gofmt? E não, não é um palavrão. Gofmt é um fantástico utilitário de linha de comando, incorporado no compilador Go para formatar o seu código. Em termos de funcionalidade é muito similar ao autopep8 de Python. Enquanto o programa Silicon Valley retrata o resto, a maioria de nós não gosta de discutir sobre abas versus espaços. É importante que a formatação seja consistente, mas o padrão de formatação real não importa muito. Gofmt evita toda essa discussão tendo uma maneira oficial de formatar seu código.

Razão 9 – gRPC e Protocol Buffers

Go tem suporte de primeira classe para buffers de protocolo e gRPC. Estas duas ferramentas funcionam muito bem em conjunto para construir microserviços que precisam de comunicar via RPC. Você só precisa escrever um manifesto onde você define as chamadas RPC que podem ser feitas e quais os argumentos que elas levam. Tanto o código do servidor como o código do cliente são então gerados automaticamente a partir deste manifesto. Este código resultante é rápido, tem um tamanho de rede muito pequeno e é fácil de usar. A partir do mesmo manifesto, você pode gerar código cliente para muitas linguagens diferentes, tais como C++, Java, Python e Ruby. Assim, não há mais endpoints REST ambíguos para tráfego interno, que você tem que escrever quase o mesmo código cliente e servidor para cada vez. .

Desvantagem 1 – Falta de Frameworks

Go não tem um único framework dominante como Rails para Ruby, Django para Python ou Laravel para PHP. Este é um tópico de debate acalorado dentro da comunidade Go, já que muitas pessoas defendem que você não deve usar um framework para começar. Eu concordo totalmente que isto é verdade para alguns casos de uso. No entanto, se alguém quiser construir uma simples API CRUD, terá um tempo muito mais fácil com Django/DJRF, Rails Laravel ou Phoenix. Atualização: como os comentários apontados, há vários projetos que fornecem um framework para o Go. Revel, Iris, Echo, Macaron e Buffalo parecem ser os principais competidores. Para o caso do Stream preferimos não usar uma estrutura. Entretanto, para muitos projetos novos que estão procurando fornecer uma simples CRUD API a falta de um framework dominante será uma séria desvantagem.

Disvantagem 2 – Tratamento de Erros

Go trata de erros simplesmente retornando um erro de uma função e esperando que seu código de chamada trate do erro (ou o devolva para cima da pilha de chamadas). Enquanto essa abordagem funciona, é fácil perder o escopo do que deu errado para garantir que você possa fornecer um erro significativo para seus usuários. O pacote de erros resolve este problema permitindo-lhe adicionar contexto e um stack trace aos seus erros. Outro problema é que é fácil esquecer de lidar com um erro por acidente. Ferramentas de análise estática como errcheck e megacheck são úteis para evitar cometer esses erros. Embora estas soluções funcionem bem, não parece muito certo. Você esperaria que o tratamento adequado de erros fosse suportado pela linguagem.

Disvantagem 3 – Gerenciamento de Pacotes

Atualização: O gerenciamento de pacotes Go percorreu um longo caminho desde que este post foi escrito. Os módulos Go são uma solução eficaz, o único problema que eu vi com eles é que eles quebram algumas ferramentas de análise estática como o errcheck. Aqui está um tutorial para aprender a usar os módulos Go usando os módulos Go. O gerenciamento de pacotes do Go não é de forma alguma perfeito. Por padrão, ele não tem como especificar uma versão específica de uma dependência e não há como criar builds reprodutíveis. Python, Node e Ruby têm todos sistemas melhores para o gerenciamento de pacotes. No entanto, com as ferramentas certas, o gerenciamento de pacotes do Go funciona bastante bem. Você pode usar o Dep para gerenciar suas dependências de modo a permitir a especificação e a localização de versões. Além disso, contribuímos com uma ferramenta open-source chamada VirtualGo que facilita o trabalho em múltiplos projetos escritos em Go.

Python vs Go

Update: A diferença de performance entre Python e Go aumentou desde que este post foi escrito. (Go ficou mais rápido e Python não) Um experimento interessante que conduzimos foi pegar nossa funcionalidade de feed classificada em Python e reescrevê-la em Go. Dê uma olhada neste exemplo de um método de ranking:

Both o código Python e Go precisam fazer o seguinte para suportar este método de ranking:

  1. Parta a expressão para a pontuação. Neste caso, queremos transformar esta string “simple_gauss(time)*popularity” numa função que toma uma actividade como input e retorna uma pontuação como output.
  2. Criar funções parciais baseadas na configuração JSON. Por exemplo, queremos que “simple_gauss” chame “decay_gauss” com uma escala de 5 dias, offset de 1 dia e um fator de decaimento de 0.3.
  3. Parta a configuração “default” para que você tenha um fallback se um determinado campo não estiver definido em uma atividade.
  4. Utilize a função do passo 1 para pontuar todas as atividades no feed.

Desenvolver a versão Python do código de ranking levou aproximadamente 3 dias. Isso inclui a escrita do código, testes unitários e documentação. A seguir, passamos aproximadamente 2 semanas otimizando o código. Uma das otimizações foi traduzir a expressão de pontuação (simple_gauss(time)*popularity) em uma árvore de sintaxe abstrata. Também implementamos a lógica de cache que pré-computava a pontuação para determinados tempos no futuro. Em contraste, o desenvolvimento da versão Go deste código levou cerca de 4 dias. A performance não requereu nenhuma otimização adicional. Assim, enquanto o desenvolvimento inicial foi mais rápido em Python, a versão Go acabou por requerer substancialmente menos trabalho da nossa equipa. Como um benefício adicional, o código Go teve um desempenho cerca de 40 vezes mais rápido do que o nosso código Python altamente optimizado. Agora, este é apenas um único exemplo dos ganhos de performance que experimentamos ao mudar para Go. É, claro, comparar maçãs com laranjas:

  • O código de classificação foi meu primeiro projeto em Go
  • O código Go foi construído após o código Python, então o caso de uso foi melhor compreendido
  • A biblioteca Go para análise de expressão foi de excepcional qualidade

A sua quilometragem irá variar. Alguns outros componentes do nosso sistema demoraram substancialmente mais tempo a construir em Go do que em Python. Como uma tendência geral, vemos que o desenvolvimento do código Go requer um pouco mais de esforço. Entretanto, gastamos muito menos tempo otimizando o código para performance.

Elixir vs Go – The Runner Up

Outra linguagem que avaliamos é o Elixir. O Elixir é construído em cima da máquina virtual Erlang. É uma linguagem fascinante e nós a consideramos desde que um dos membros da nossa equipe tem uma tonelada de experiência com Erlang. Para nossos casos de uso, notamos que o desempenho bruto de Go é muito melhor. Tanto Go quanto Elixir farão um ótimo trabalho atendendo a milhares de pedidos simultâneos. No entanto, se você olhar para o desempenho de pedidos individuais, Go é substancialmente mais rápido para o nosso caso de uso. Outra razão pela qual escolhemos Go em vez de Elixir foi o ecossistema. Para os componentes que necessitávamos, Go tinha bibliotecas mais maduras enquanto, em muitos casos, as bibliotecas de Elixir não estavam prontas para uso em produção. Também é mais difícil treinar/encontrar desenvolvedores para trabalhar com o Elixir. Estas razões fizeram pender a balança a favor da Go. A estrutura Phoenix para o Elixir parece fantástica e definitivamente vale a pena dar uma olhada.

Conclusion

Go é uma linguagem muito performante com grande suporte para concorrência. É quase tão rápida quanto linguagens como C++ e Java. Embora leve um pouco mais de tempo para construir coisas usando Go comparado ao Python ou Ruby, você economizará uma tonelada de tempo gasto na otimização do código. Nós temos uma pequena equipe de desenvolvimento no Stream powering feeds e chat para mais de 500 milhões de usuários finais. A combinação de um grande ecossistema Go, fácil integração para novos desenvolvedores, desempenho rápido, suporte sólido para concorrência e um ambiente de programação produtivo fazem dela uma ótima escolha. O Stream ainda aproveita o Python para o nosso dashboard, site e aprendizagem de máquinas para feeds personalizados. Não vamos nos despedir do Python tão cedo, mas continuar com todo o código de performance intensiva será escrito em Go. Nossa nova API de Chat também foi escrita inteiramente em Go. Se você quiser saber mais sobre Go, confira os posts do blog listados abaixo. Para saber mais sobre o Stream, este tutorial interativo é um ótimo ponto de partida.

Mais Leitura sobre Mudança para Golang

  • https://movio.co/en/blog/migrate-Scala-to-Go/
  • https://hackernoon.com/why-i-love-golang-90085898b4f7
  • https://sendgrid.com/blog/convince-company-go-golang/
  • >https://dave.cheney.net/2017/03/20/why-go>

Learning Go

  • https://learnxinyminutes.com/docs/go/
  • >https://tour.golang.org/
  • http://howistart.org/posts/go/1/
  • https://getstream.io/blog/building-a-performant-api-using-go-and-cassandra/>
  • https://www.amazon.com/gp/product/0134190440
  • Go Rocket Tutorial

Deixe uma resposta

O seu endereço de email não será publicado.