Perché siamo passati da Python a Go

Aggiornato il 14 maggio 2019 per riflettere meglio i miglioramenti di Go negli ultimi 2 anni (gestione dei pacchetti, migliori prestazioni, tempi di compilazione più rapidi e un ecosistema più maturo) Passare a un nuovo linguaggio è sempre un grande passo, soprattutto quando solo uno dei membri del tuo team ha esperienza precedente con quel linguaggio. All’inizio di quest’anno, abbiamo cambiato il linguaggio di programmazione principale di Stream da Python a Go. Questo post spiegherà alcune delle ragioni per cui abbiamo deciso di abbandonare Python e passare a Go. Grazie a Ren Sakamoto per aver tradotto Perché siamo passati da Python a Go in giapponese, なぜ私達は Python から Go に移行したのか.

Ragione 1 – Performance

Go è veloce! Go è estremamente veloce. Le prestazioni sono simili a quelle di Java o C++. Per il nostro caso d’uso, Go è tipicamente 40 volte più veloce di Python. Ecco un piccolo gioco di benchmark che confronta Go con Python.

Ragione 2 – Le prestazioni del linguaggio contano

Per molte applicazioni, il linguaggio di programmazione è semplicemente il collante tra l’applicazione e il database. Le prestazioni del linguaggio stesso di solito non contano molto. Stream, tuttavia, è un fornitore di API che alimenta una piattaforma di feed e chat per 700 aziende e più di 500 milioni di utenti finali. Abbiamo ottimizzato Cassandra, PostgreSQL, Redis, ecc. per anni, ma alla fine si raggiungono i limiti del linguaggio che si sta usando. Python è un ottimo linguaggio, ma le sue prestazioni sono piuttosto lente per casi d’uso come la serializzazione/deserializzazione, la classificazione e l’aggregazione. Ci siamo spesso imbattuti in problemi di prestazioni in cui Cassandra impiegava 1ms per recuperare i dati e Python passava i successivi 10ms a trasformarli in oggetti.

Ragione 3 – Produttività dello sviluppatore &Non diventare troppo creativo

Guarda questo piccolo frammento di codice Go dal tutorial How I Start Go. (Questo è un grande tutorial e un buon punto di partenza per imparare un po’ di Go.)

Se sei nuovo di Go, non c’è molto che ti sorprenderà leggendo questo piccolo frammento di codice. Mostra assegnazioni multiple, strutture dati, puntatori, formattazione e una libreria HTTP integrata. Quando ho iniziato a programmare ho sempre amato usare le caratteristiche più avanzate di Python. Python ti permette di essere piuttosto creativo con il codice che stai scrivendo. Per esempio, è possibile:

  • Utilizzare MetaClassi per auto-registrare le classi all’inizializzazione del codice
  • Scambiare True e False
  • Aggiungere funzioni alla lista delle funzioni incorporate
  • Sovraccaricare gli operatori tramite metodi magici
  • Utilizzare funzioni come proprietà tramite il decoratore @property

Queste caratteristiche sono divertenti da usare ma, come la maggior parte dei programmatori concorderà, spesso rendono il codice più difficile da capire quando si legge il lavoro di qualcun altro. Go ti costringe ad attenerti alle basi. Questo rende molto facile leggere il codice di chiunque e capire immediatamente cosa sta succedendo. Nota: quanto sia “facile” dipende davvero dal vostro caso d’uso, naturalmente. Se vuoi creare un’API CRUD di base, raccomanderei ancora Django + DRF, o Rails.

Ragione 4 – Concurrency & Channels

Come linguaggio, Go cerca di mantenere le cose semplici. Non introduce molti nuovi concetti. L’attenzione è sulla creazione di un linguaggio semplice che è incredibilmente veloce e facile da usare. L’unica area in cui diventa innovativo sono le goroutine e i canali. (Per essere corretti al 100% il concetto di CSP è iniziato nel 1977, quindi questa innovazione è più un nuovo approccio ad una vecchia idea). Le goroutine sono l’approccio leggero di Go al threading, e i canali sono il modo preferito per comunicare tra le goroutine. Le goroutine sono molto economiche da creare e richiedono solo pochi KB di memoria aggiuntiva. Poiché le goroutine sono così leggere, è possibile averne centinaia o addirittura migliaia in esecuzione allo stesso tempo. È possibile comunicare tra le goroutine usando dei canali. Il runtime di Go gestisce tutta la complessità. Le goroutine e l’approccio alla concorrenza basato sui canali rendono molto facile usare tutti i core della CPU disponibili e gestire l’IO concorrente, il tutto senza complicare lo sviluppo. Rispetto a Python/Java, eseguire una funzione su una goroutine richiede un minimo di codice boilerplate. Semplicemente si aggiunge alla chiamata della funzione la parola chiave “go”:

https://tour.golang.org/concurrency/1 L’approccio di Go alla concorrenza è molto facile da usare. È un approccio interessante rispetto a Node, dove lo sviluppatore deve prestare molta attenzione a come viene gestito il codice asincrono. Un altro grande aspetto della concorrenza in Go è il rilevatore di corse. Questo rende facile capire se ci sono condizioni di gara all’interno del tuo codice asincrono.

Knock knock Race condition Chi c’è?

– I Am Devloper (@iamdevloper) November 11, 2013

Ecco alcune buone risorse per iniziare con Go e i canali:

  • 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

Ragione 5 – Tempo di compilazione veloce

Il nostro più grande microservizio scritto in Go impiega attualmente 4 secondi per compilare. I tempi di compilazione veloci di Go sono una grande vittoria di produttività rispetto a linguaggi come Java e C++ che sono famosi per la lentezza di compilazione. Mi piace combattere con le spade, ma è ancora più bello portare a termine le cose mentre ancora mi ricordo cosa dovrebbe fare il codice:

Ragione 6 – La capacità di costruire una squadra

Prima di tutto, iniziamo con l’ovvio: non ci sono così tanti sviluppatori Go rispetto ai vecchi linguaggi come C++ e Java. Secondo StackOverflow, il 38% degli sviluppatori conosce Java, il 19,3% conosce C++ e solo il 4,6% conosce Go. I dati di GitHub mostrano una tendenza simile: Go è più usato di linguaggi come Erlang, Scala ed Elixir, ma meno popolare di Java e C++. Fortunatamente, Go è un linguaggio molto semplice e facile da imparare. Fornisce le caratteristiche di base di cui avete bisogno e nient’altro. I nuovi concetti che introduce sono l’istruzione “defer” e la gestione integrata della concorrenza con “go routines” e canali. (Per i puristi: Go non è il primo linguaggio ad implementare questi concetti, solo il primo a renderli popolari). Qualsiasi sviluppatore Python, Elixir, C++, Scala o Java che si unisce ad un team può essere efficace in Go entro un mese a causa della sua semplicità. Abbiamo trovato più facile costruire un team di sviluppatori Go rispetto a molte altre lingue. Se stai assumendo persone in ecosistemi competitivi come Boulder e Amsterdam questo è un vantaggio importante.

Ragione 7 – Ecosistema forte

Per un team delle nostre dimensioni (~20 persone) l’ecosistema conta. Semplicemente non puoi creare valore per i tuoi clienti se devi reinventare ogni piccolo pezzo di funzionalità. Go ha un grande supporto per gli strumenti che usiamo. Erano già disponibili solide librerie per Redis, RabbitMQ, PostgreSQL, l’analisi dei template, la pianificazione delle attività, l’analisi delle espressioni e RocksDB. L’ecosistema di Go è una grande vittoria rispetto ad altri linguaggi più recenti come Rust o Elixir. Naturalmente non è buono come linguaggi come Java, Python o Node, ma è solido e per molte necessità di base troverete pacchetti di alta qualità già disponibili.

Ragione 8 – Gofmt, Enforced Code Formatting

Cominciamo con cos’è Gofmt? E no, non è una parolaccia. Gofmt è una fantastica utility a riga di comando, integrata nel compilatore Go per la formattazione del codice. In termini di funzionalità è molto simile ad autopep8 di Python. Anche se lo show Silicon Valley ritrae diversamente, alla maggior parte di noi non piace molto discutere di tabulazioni contro spazi. È importante che la formattazione sia coerente, ma lo standard di formattazione effettivo non ha molta importanza. Gofmt evita tutte queste discussioni avendo un modo ufficiale per formattare il tuo codice.

Ragione 9 – gRPC e buffer di protocollo

Go ha un supporto di prima classe per i buffer di protocollo e gRPC. Questi due strumenti lavorano molto bene insieme per costruire microservizi che hanno bisogno di comunicare via RPC. Basta scrivere un manifesto dove si definiscono le chiamate RPC che possono essere fatte e quali argomenti prendono. Sia il codice server che quello client sono poi generati automaticamente da questo manifesto. Questo codice risultante è veloce, ha un’impronta di rete molto piccola ed è facile da usare. Dallo stesso manifesto, si può generare codice client anche per molti linguaggi diversi, come C++, Java, Python e Ruby. Quindi, niente più ambigui endpoint REST per il traffico interno, per i quali si deve scrivere quasi lo stesso codice client e server ogni volta. .

Svantaggio 1 – Mancanza di Frameworks

Go non ha un singolo framework dominante come Rails per Ruby, Django per Python o Laravel per PHP. Questo è un argomento di acceso dibattito all’interno della comunità di Go, poiché molte persone sostengono che non si dovrebbe usare un framework per cominciare. Sono totalmente d’accordo che questo è vero per alcuni casi d’uso. Tuttavia, se qualcuno vuole costruire una semplice API CRUD avrà un tempo molto più facile con Django/DJRF, Rails Laravel o Phoenix. Aggiornamento: come sottolineato nei commenti ci sono diversi progetti che forniscono un framework per Go. Revel, Iris, Echo, Macaron e Buffalo sembrano essere i principali contendenti. Per il caso d’uso di Stream preferiamo non usare un framework. Comunque per molti nuovi progetti che stanno cercando di fornire una semplice API CRUD la mancanza di un framework dominante sarà un serio svantaggio.

Svantaggio 2 – Gestione degli errori

Go gestisce gli errori semplicemente restituendo un errore da una funzione e aspettandosi che il codice chiamante gestisca l’errore (o che lo restituisca nello stack chiamante). Anche se questo approccio funziona, è facile perdere di vista ciò che è andato storto per assicurarsi di poter fornire un errore significativo ai propri utenti. Il pacchetto errors risolve questo problema permettendovi di aggiungere un contesto e una traccia dello stack ai vostri errori. Un altro problema è che è facile dimenticare di gestire un errore per sbaglio. Strumenti di analisi statica come errcheck e megacheck sono utili per evitare di fare questi errori. Anche se queste soluzioni funzionano bene, non sembra del tutto giusto. Ci si aspetta che la gestione degli errori sia supportata dal linguaggio.

Svantaggio 3 – Gestione dei pacchetti

Aggiornamento: la gestione dei pacchetti di Go ha fatto molta strada da quando questo post è stato scritto. I moduli di Go sono una soluzione efficace, l’unico problema che ho visto con loro è che rompono alcuni strumenti di analisi statica come errcheck. Qui c’è un tutorial per imparare ad usare Go usando i moduli Go. La gestione dei pacchetti di Go non è affatto perfetta. Per impostazione predefinita, non ha un modo per specificare una versione specifica di una dipendenza e non c’è modo di creare build riproducibili. Python, Node e Ruby hanno tutti sistemi migliori per la gestione dei pacchetti. Tuttavia, con i giusti strumenti, la gestione dei pacchetti di Go funziona abbastanza bene. Puoi usare Dep per gestire le tue dipendenze per permettere di specificare e pinnare le versioni. A parte questo, abbiamo contribuito con uno strumento open-source chiamato VirtualGo che rende più facile lavorare su più progetti scritti in Go.

Python vs Go

Aggiornamento: La differenza di prestazioni tra Python e Go è aumentata da quando questo post è stato scritto. (Go è diventato più veloce e Python no) Un esperimento interessante che abbiamo condotto è stato prendere la nostra funzionalità di feed classificati in Python e riscriverla in Go. Date un’occhiata a questo esempio di un metodo di classificazione:

Entrambi i codici Python e Go devono fare quanto segue per supportare questo metodo di classificazione:

  1. Parse l’espressione per il punteggio. In questo caso, vogliamo trasformare questa stringa “simple_gauss(time)*popularity” in una funzione che prende un’attività come input e restituisce un punteggio come output.
  2. Crea funzioni parziali basate sulla configurazione JSON. Per esempio, vogliamo che “simple_gauss” chiami “decay_gauss” con una scala di 5 giorni, un offset di 1 giorno e un fattore di decadimento di 0,3.
  3. Parse la configurazione “defaults” in modo da avere un fallback se un certo campo non è definito su un’attività.
  4. Usa la funzione dal passo 1 per dare un punteggio a tutte le attività nel feed.

Lo sviluppo della versione Python del codice della classifica ha richiesto circa 3 giorni. Questo include la scrittura del codice, i test unitari e la documentazione. Poi, abbiamo passato circa 2 settimane ad ottimizzare il codice. Una delle ottimizzazioni è stata la traduzione dell’espressione del punteggio (simple_gauss(time)*popularity) in un albero sintattico astratto. Abbiamo anche implementato una logica di caching che ha precompilato il punteggio per certi tempi nel futuro. Al contrario, lo sviluppo della versione Go di questo codice ha richiesto circa 4 giorni. Le prestazioni non hanno richiesto ulteriori ottimizzazioni. Quindi, mentre la parte iniziale dello sviluppo è stata più veloce in Python, la versione basata su Go alla fine ha richiesto sostanzialmente meno lavoro al nostro team. Come ulteriore vantaggio, il codice Go è stato circa 40 volte più veloce del nostro codice Python altamente ottimizzato. Ora, questo è solo un singolo esempio dei guadagni di prestazioni che abbiamo sperimentato passando a Go. Si tratta, naturalmente, di paragonare mele e arance:

  • Il codice della classifica era il mio primo progetto in Go
  • Il codice Go è stato costruito dopo il codice Python, quindi il caso d’uso è stato meglio compreso
  • La libreria Go per l’analisi delle espressioni era di qualità eccezionale

Il vostro chilometraggio può variare. Alcuni altri componenti del nostro sistema hanno richiesto sostanzialmente più tempo per costruire in Go rispetto a Python. Come tendenza generale, vediamo che lo sviluppo del codice Go richiede uno sforzo leggermente maggiore. Tuttavia, passiamo molto meno tempo ad ottimizzare il codice per le prestazioni.

Elixir vs Go – The Runner Up

Un altro linguaggio che abbiamo valutato è Elixir. Elixir è costruito sopra la macchina virtuale Erlang. È un linguaggio affascinante e lo abbiamo considerato poiché uno dei membri del nostro team ha una tonnellata di esperienza con Erlang. Per i nostri casi d’uso, abbiamo notato che le prestazioni grezze di Go sono molto migliori. Sia Go che Elixir faranno un ottimo lavoro servendo migliaia di richieste concorrenti. Tuttavia, se si guarda alle prestazioni delle singole richieste, Go è sostanzialmente più veloce per il nostro caso d’uso. Un’altra ragione per cui abbiamo scelto Go invece di Elixir è stato l’ecosistema. Per i componenti di cui avevamo bisogno, Go aveva librerie più mature mentre, in molti casi, le librerie di Elixir non erano pronte per l’uso in produzione. È anche più difficile formare/trovare sviluppatori che lavorino con Elixir. Queste ragioni hanno fatto pendere la bilancia a favore di Go. Il framework Phoenix per Elixir sembra impressionante ed è sicuramente da vedere.

Conclusione

Go è un linguaggio molto performante con un grande supporto per la concorrenza. È veloce quasi quanto linguaggi come C++ e Java. Mentre ci vuole un po’ più di tempo per costruire cose usando Go rispetto a Python o Ruby, si risparmia una tonnellata di tempo speso per ottimizzare il codice. Abbiamo un piccolo team di sviluppo in Stream che alimenta feed e chat per oltre 500 milioni di utenti finali. La combinazione di Go di un grande ecosistema, un facile onboarding per i nuovi sviluppatori, prestazioni veloci, un solido supporto per la concorrenza e un ambiente di programmazione produttivo lo rendono una grande scelta. Stream sfrutta ancora Python per la nostra dashboard, il sito e l’apprendimento automatico per i feed personalizzati. Non diremo presto addio a Python, ma in futuro tutto il codice ad alte prestazioni sarà scritto in Go. Anche la nostra nuova Chat API è scritta interamente in Go. Se vuoi saperne di più su Go dai un’occhiata ai post del blog elencati qui sotto. Per saperne di più su Stream, questo tutorial interattivo è un ottimo punto di partenza.

Altre letture sul passaggio a 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

Imparare 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

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.