Dlaczego przeszliśmy z Pythona na Go

Uaktualnione 14 maja 2019, aby lepiej odzwierciedlić ulepszenia Go w ciągu ostatnich 2 lat (zarządzanie pakietami, lepsza wydajność, szybsze czasy kompilacji i bardziej dojrzały ekosystem) Przejście na nowy język jest zawsze dużym krokiem, zwłaszcza gdy tylko jeden z członków twojego zespołu ma wcześniejsze doświadczenie z tym językiem. Na początku tego roku zmieniliśmy podstawowy język programowania Streamu z Pythona na Go. Ten post wyjaśni niektóre z powodów, dla których zdecydowaliśmy się porzucić Pythona i przejść na Go. Podziękowania dla Ren Sakamoto za przetłumaczenie na japoński tłumaczenia Why we switched from Python to Go, なぜ私達は Python から Go に移行したのか.

Reason 1 – Performance

Go is fast! Go jest niezwykle szybkie. Wydajność jest podobna do tej z Javy lub C++. Dla naszego przypadku użycia, Go jest typowo 40 razy szybszy niż Python. Oto mały benchmark porównujący Go vs Python.

Powód 2 – Wydajność języka ma znaczenie

Dla wielu aplikacji, język programowania jest po prostu klejem pomiędzy aplikacją a bazą danych. Wydajność samego języka zazwyczaj nie ma większego znaczenia. Stream jest jednak dostawcą API zasilającym platformę feeds i chat dla 700 firm i ponad 500 milionów użytkowników końcowych. Od lat optymalizujemy Cassandrę, PostgreSQL, Redis itp., ale w końcu dochodzimy do granic możliwości języka, którego używamy. Python jest świetnym językiem, ale jego wydajność jest dość niska w takich zastosowaniach jak serializacja/deserializacja, ranking i agregacja. Często napotykaliśmy na problemy z wydajnością, gdzie Cassandra potrzebowała 1ms na pobranie danych, a Python spędzał następne 10ms na przekształceniu ich w obiekty.

Powód 3 – Produktywność programisty &Nie stajemy się zbyt kreatywni

Spójrz na ten mały wycinek kodu Go z samouczka How I Start Go. (Jest to świetny samouczek i dobry punkt startowy, aby poznać trochę Go.)

Jeśli jesteś nowy w Go, nie ma wiele rzeczy, które zaskoczą Cię podczas czytania tego małego wycinka kodu. Pokazuje on wielokrotne przypisania, struktury danych, wskaźniki, formatowanie i wbudowaną bibliotekę HTTP. Kiedy zaczynałem programować, zawsze lubiłem korzystać z bardziej zaawansowanych funkcji Pythona. Python pozwala na całkiem kreatywne podejście do pisanego kodu. Na przykład, możesz:

  • Używać MetaClasses do samodzielnego rejestrowania klas przy inicjalizacji kodu
  • Zamieniać True i False
  • Dodawać funkcje do listy funkcji wbudowanych
  • Przeładowywać operatory za pomocą metod magicznych
  • Używać funkcji jako właściwości za pomocą dekoratora @property

Tymi funkcjami można się fajnie bawić, ale, jak większość programistów się zgodzi, często sprawiają, że kod jest trudniejszy do zrozumienia podczas czytania czyjejś pracy. Go zmusza cię do trzymania się podstaw. To sprawia, że bardzo łatwo jest przeczytać czyjś kod i od razu zrozumieć co się w nim dzieje. Uwaga: To jak „łatwo” jest naprawdę zależy oczywiście od Twojego przypadku użycia. Jeśli chcesz stworzyć podstawowe API CRUD, nadal polecałbym Django + DRF, lub Rails.

Powód 4 – Współbieżność & Kanały

Jako język, Go stara się utrzymywać rzeczy w prostocie. Nie wprowadza wielu nowych koncepcji. Skupia się na stworzeniu prostego języka, który jest niewiarygodnie szybki i łatwy do pracy. Jedynym obszarem, w którym Go wprowadza innowacje są goroutines i kanały. (Aby być w 100% poprawnym, koncepcja CSP została zapoczątkowana w 1977 roku, więc ta innowacja jest raczej nowym podejściem do starego pomysłu). Goroutines są lekkim podejściem Go do wątkowania, a kanały są preferowanym sposobem komunikacji pomiędzy goroutines. Goroutines są bardzo tanie w tworzeniu i zajmują tylko kilka KB dodatkowej pamięci. Ponieważ goroutines są tak lekkie, możliwe jest posiadanie setek lub nawet tysięcy z nich działających w tym samym czasie. Między goroutinami można się komunikować za pomocą kanałów. Runtime Go zajmuje się całą złożonością. Goroutines i oparte na kanałach podejście do współbieżności sprawia, że bardzo łatwo jest używać wszystkich dostępnych rdzeni procesora i obsługiwać współbieżne IO – wszystko bez komplikowania rozwoju. W porównaniu do Pythona/Javy, uruchomienie funkcji na goroutinie wymaga minimalnej ilości kodu. Wystarczy poprzedzić wywołanie funkcji słowem kluczowym „go”:

https://tour.golang.org/concurrency/1 Podejście Go do współbieżności jest bardzo łatwe do pracy. Jest to ciekawe podejście w porównaniu do Node, gdzie programista musi zwracać uwagę na to, jak obsługiwany jest kod asynchroniczny. Innym świetnym aspektem współbieżności w Go jest detektor wyścigów. Dzięki temu można łatwo dowiedzieć się, czy w kodzie asynchronicznym występują jakieś warunki wyścigu.

Knock knock Warunek wyścigu Kto tam?

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

Oto kilka dobrych zasobów, aby rozpocząć pracę z Go i kanałami:

  • 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

Powód 5 – Szybki czas kompilacji

Nasza największa mikro usługa napisana w Go zajmuje obecnie 4 sekundy na kompilację. Szybki czas kompilacji Go jest główną wygraną produktywności w porównaniu do języków takich jak Java i C++, które słyną z powolnej prędkości kompilacji. Lubię walkę na miecze, ale jeszcze przyjemniej jest robić rzeczy, gdy jeszcze pamiętam, co kod ma robić:

Powód 6 – Możliwość zbudowania zespołu

Po pierwsze, zacznijmy od oczywistego: nie ma tak wielu programistów Go w porównaniu do starszych języków, takich jak C++ i Java. Według StackOverflow, 38% programistów zna Javę, 19,3% zna C++ i tylko 4,6% zna Go. Dane z GitHuba pokazują podobny trend: Go jest szerzej używany niż języki takie jak Erlang, Scala i Elixir, ale mniej popularny niż Java i C++. Na szczęście Go jest bardzo prostym i łatwym do nauczenia językiem. Zapewnia on podstawowe funkcje, których potrzebujesz i nic poza tym. Nowe koncepcje, które wprowadza to deklaracja „defer” i wbudowane zarządzanie współbieżnością za pomocą „go routines” i kanałów. (Dla purystów: Go nie jest pierwszym językiem, który implementuje te koncepcje, tylko pierwszym, który je spopularyzował). Każdy programista Pythona, Elixiru, C++, Scali czy Javy, który dołączy do naszego zespołu, może w ciągu miesiąca stać się efektywnym użytkownikiem Go z powodu jego prostoty. Stwierdziliśmy, że łatwiej jest zbudować zespół programistów Go w porównaniu z wieloma innymi językami. Jeśli zatrudniasz ludzi w konkurencyjnych ekosystemach, takich jak Boulder i Amsterdam, jest to ważna zaleta.

Powód 7 – Silny ekosystem

Dla zespołu naszej wielkości (~20 osób) ekosystem ma znaczenie. Po prostu nie możesz tworzyć wartości dla swoich klientów, jeśli musisz wymyślać na nowo każdy mały element funkcjonalności. Go ma świetne wsparcie dla narzędzi, których używamy. Solidne biblioteki były już dostępne dla Redis, RabbitMQ, PostgreSQL, parsowania szablonów, planowania zadań, parsowania wyrażeń i RocksDB. Ekosystem Go jest wielką wygraną w porównaniu do innych nowszych języków, takich jak Rust czy Elixir. Oczywiście nie jest on tak dobry jak języki takie jak Java, Python czy Node, ale jest solidny i dla wielu podstawowych potrzeb znajdziesz już dostępne wysokiej jakości pakiety.

Powód 8 – Gofmt, wymuszone formatowanie kodu

Zacznijmy od tego czym jest Gofmt? I nie, to nie jest przekleństwo. Gofmt jest niesamowitym narzędziem wiersza poleceń, wbudowanym w kompilator Go, służącym do formatowania kodu. Pod względem funkcjonalności jest bardzo podobny do autopep8 z Pythona. Chociaż program Dolina Krzemowa przedstawia to inaczej, większość z nas nie lubi spierać się o tabulatory i spacje. Ważne jest, aby formatowanie było spójne, ale rzeczywisty standard formatowania nie ma aż tak dużego znaczenia. Gofmt unika całej tej dyskusji, mając jeden oficjalny sposób formatowania kodu.

Powód 9 – gRPC i bufory protokołów

Go ma pierwszorzędne wsparcie dla buforów protokołów i gRPC. Te dwa narzędzia bardzo dobrze współpracują przy budowaniu mikroserwisów, które muszą komunikować się za pomocą RPC. Wystarczy napisać manifest, w którym definiuje się wywołania RPC, które mogą być wykonane i jakie argumenty przyjmują. Zarówno kod serwera jak i klienta są automatycznie generowane na podstawie tego manifestu. Wynikowy kod jest szybki, ma bardzo mały ślad sieciowy i jest łatwy w użyciu. Z tego samego manifestu można wygenerować kod klienta nawet dla wielu różnych języków, takich jak C++, Java, Python czy Ruby. Tak więc, koniec z niejednoznacznymi punktami końcowymi REST dla ruchu wewnętrznego, dla których musisz pisać prawie taki sam kod klienta i serwera za każdym razem.

Wada 1 – Brak frameworków

Go nie ma jednego dominującego frameworka jak Rails dla Ruby, Django dla Pythona czy Laravel dla PHP. Jest to temat gorącej debaty w społeczności Go, ponieważ wiele osób uważa, że nie powinno się używać frameworków na początku. Całkowicie zgadzam się, że jest to prawdą w niektórych przypadkach użycia. Jednakże, jeśli ktoś chce zbudować proste API CRUD, będzie miał znacznie łatwiejszy czas z Django/DJRF, Rails Laravel lub Phoenix. Aktualizacja: jak wskazano w komentarzach, istnieje kilka projektów, które zapewniają ramy dla Go. Revel, Iris, Echo, Macaron i Buffalo wydają się być głównymi pretendentami. W przypadku Stream’a wolimy nie używać frameworków. Jednak dla wielu nowych projektów, które chcą zapewnić proste API CRUD, brak dominującego frameworka będzie poważną wadą.

Wada 2 – Obsługa błędów

Go obsługuje błędy po prostu zwracając błąd z funkcji i oczekując, że kod wywołujący poradzi sobie z błędem (lub zwróci go na stos). Chociaż to podejście działa, łatwo jest stracić zakres tego, co poszło nie tak, aby zapewnić, że możesz dostarczyć znaczący błąd do swoich użytkowników. Pakiet errors rozwiązuje ten problem, pozwalając na dodanie kontekstu i śladu stosu do błędów. Inną kwestią jest to, że łatwo jest zapomnieć o obsłudze błędu przez przypadek. Narzędzia do analizy statycznej takie jak errcheck i megacheck są przydatne, aby uniknąć popełniania takich błędów. Podczas gdy te obejścia działają dobrze, nie wydaje się to całkiem w porządku. Oczekiwałbyś, że właściwa obsługa błędów będzie wspierana przez język.

Wada 3 – Zarządzanie pakietami

Uaktualnienie: Zarządzanie pakietami Go przeszło długą drogę od czasu napisania tego postu. Moduły Go są skutecznym rozwiązaniem, jedynym problemem jaki z nimi widziałem jest to, że łamią niektóre narzędzia do analizy statycznej, takie jak errcheck. Tutaj jest tutorial do nauki używania Go przy użyciu modułów Go. Zarządzanie pakietami w Go nie jest bynajmniej doskonałe. Domyślnie, nie ma sposobu na określenie konkretnej wersji zależności i nie ma sposobu na tworzenie powtarzalnych kompilacji. Python, Node i Ruby mają lepsze systemy do zarządzania pakietami. Jednakże, z odpowiednimi narzędziami, zarządzanie pakietami w Go działa całkiem dobrze. Możesz użyć Dep do zarządzania swoimi zależnościami, aby umożliwić określanie i przypinanie wersji. Poza tym, stworzyliśmy narzędzie open-source o nazwie VirtualGo, które ułatwia pracę nad wieloma projektami napisanymi w Go.

Python vs Go

Uaktualnienie: Różnica wydajności między Pythonem a Go wzrosła od czasu napisania tego postu. (Go stał się szybszy, a Python nie) Jednym z interesujących eksperymentów, które przeprowadziliśmy było wzięcie naszej funkcjonalności ranked feed w Pythonie i przepisanie jej w Go. Spójrz na ten przykład metody rankingowej:

Oba kody Pythona i Go muszą wykonać następujące czynności, aby obsłużyć tę metodę rankingową:

  1. Parse wyrażenie dla wyniku. W tym przypadku chcemy przekształcić ciąg „simple_gauss(time)*popularność” w funkcję, która pobiera aktywność jako dane wejściowe i zwraca wynik jako dane wyjściowe.
  2. Twórz funkcje częściowe w oparciu o konfigurację JSON. Na przykład, chcemy, aby „simple_gauss” wywołało „decay_gauss” ze skalą 5 dni, przesunięciem 1 dnia i współczynnikiem rozkładu 0.3.
  3. Przeanalizuj konfigurację „defaults”, abyś miał awaryjne rozwiązanie, jeśli pewne pole nie jest zdefiniowane w aktywności.
  4. Użyj funkcji z kroku 1, aby ocenić wszystkie aktywności w feedzie.

Opracowanie kodu rankingu w wersji Pythona zajęło około 3 dni. Obejmuje to pisanie kodu, testy jednostkowe i dokumentację. Następnie, spędziliśmy około 2 tygodni na optymalizacji kodu. Jedną z optymalizacji było przetłumaczenie wyrażenia score (simple_gauss(time)*popularity) na abstrakcyjne drzewo składniowe. Zaimplementowaliśmy również logikę cache’owania, która wstępnie obliczała wynik dla określonych czasów w przyszłości. Dla porównania, opracowanie wersji Go tego kodu zajęło około 4 dni. Wydajność nie wymagała żadnej dalszej optymalizacji. Tak więc, podczas gdy początkowy etap rozwoju był szybszy w Pythonie, wersja Go wymagała ostatecznie znacznie mniej pracy od naszego zespołu. Dodatkową korzyścią jest to, że kod Go działał około 40 razy szybciej niż nasz wysoce zoptymalizowany kod Pythona. To tylko pojedynczy przykład wzrostu wydajności, jakiego doświadczyliśmy dzięki przejściu na Go. Jest to oczywiście porównanie jabłek z pomarańczami:

  • Kod rankingu był moim pierwszym projektem w Go
  • Kod Go został zbudowany po kodzie Pythona, więc przypadek użycia był lepiej zrozumiany
  • Biblioteka Go do parsowania wyrażeń była wyjątkowej jakości

Twoja droga będzie inna. Niektóre inne komponenty naszego systemu wymagały znacznie więcej czasu na zbudowanie w Go w porównaniu z Pythonem. Ogólnie rzecz biorąc, widzimy, że tworzenie kodu Go wymaga nieco więcej wysiłku. Spędzamy jednak znacznie mniej czasu na optymalizacji kodu pod kątem wydajności.

Elixir vs Go – The Runner Up

Kolejnym językiem, który ocenialiśmy, jest Elixir. Elixir jest zbudowany na szczycie wirtualnej maszyny Erlang. Jest to fascynujący język i braliśmy go pod uwagę, ponieważ jeden z członków naszego zespołu ma duże doświadczenie z Erlangiem. Dla naszych przypadków użycia zauważyliśmy, że surowa wydajność Go jest znacznie lepsza. Zarówno Go jak i Elixir wykonają świetną pracę obsługując tysiące współbieżnych żądań. Jednakże, jeśli spojrzymy na wydajność pojedynczych żądań, Go jest znacznie szybszy dla naszego przypadku użycia. Kolejnym powodem, dla którego wybraliśmy Go zamiast Elixiru, był ekosystem. Dla komponentów, których potrzebowaliśmy, Go posiadał bardziej dojrzałe biblioteki, podczas gdy w wielu przypadkach biblioteki Elixiru nie były gotowe do użytku produkcyjnego. Trudniej jest również wyszkolić/znaleźć programistów do pracy z Elixirem. Te powody przechyliły szalę na korzyść Go. Jednak framework Phoenix dla Elixir wygląda niesamowicie i zdecydowanie warto się mu przyjrzeć.

Wnioski

Go jest bardzo wydajnym językiem z doskonałym wsparciem dla współbieżności. Jest prawie tak szybki jak języki takie jak C++ i Java. Chociaż tworzenie rzeczy przy użyciu Go zajmuje trochę więcej czasu niż w przypadku Pythona czy Ruby, zaoszczędzisz mnóstwo czasu poświęconego na optymalizację kodu. Mamy mały zespół programistów w Stream, który obsługuje kanały informacyjne i czat dla ponad 500 milionów użytkowników końcowych. Połączenie świetnego ekosystemu, łatwego wdrożenia dla nowych programistów, szybkiej wydajności, solidnego wsparcia dla współbieżności i wydajnego środowiska programistycznego sprawia, że Go to świetny wybór. Stream nadal używa Pythona do tworzenia pulpitu nawigacyjnego, strony internetowej i uczenia maszynowego dla spersonalizowanych kanałów. Nie pożegnamy się z Pythonem w najbliższym czasie, ale w przyszłości cały kod wymagający dużej wydajności będzie pisany w języku Go. Nasze nowe Chat API jest również w całości napisane w Go. Jeśli chcesz dowiedzieć się więcej o Go, zapoznaj się z poniższymi wpisami na blogu. Aby dowiedzieć się więcej o Stream, ten interaktywny samouczek jest świetnym punktem wyjścia.

Więcej o przejściu na 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

.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.