Před 10 lety se společnost Google potýkala s kritickým úzkým místem způsobeným extrémně dlouhou dobou kompilace jazyka C++ a potřebovala zcela nový způsob řešení. Inženýři společnosti Google se s tímto problémem vypořádali vytvořením nového jazyka Go (alias Golang). Nový jazyk Go si vypůjčil nejlepší části jazyka C++ (především jeho výkonnostní a bezpečnostní funkce) a zkombinoval je s rychlostí jazyka Python, aby jazyk Go mohl rychle využívat více jader a zároveň byl schopen implementovat souběžnost.

V Coralogixu analyzujeme logy našich klientů, abychom jim mohli v reálném čase poskytnout přehled, upozornění a metadata o jejich logách. Aby toho bylo možné dosáhnout, musí být fáze parsování, která je velmi složitá a zatížená tunami pravidel pro každou službu řádku protokolu, extrémně rychlá. To je jeden z důvodů, proč jsme se rozhodli použít jazyk Go.

Nová služba nyní běží na plný úvazek v produkci, a i když vidíme skvělé výsledky, musí běžet na výkonných strojích. Tato služba jazyka Go, která běží na instanci AWS m4.2xlarge s 8 procesory a 36 GB paměti, denně analyzuje více než desítky miliard protokolů.

V této fázi bychom to mohli zabalit s dobrým pocitem, že vše běží dobře, ale tak to u nás ve společnosti Coralogix nefunguje. Chtěli jsme více funkcí (výkon atd.) s použitím menšího množství (instance AWS). Abychom se mohli zlepšit, museli jsme nejprve pochopit podstatu našich úzkých míst a zjistit, jak je můžeme snížit nebo zcela odstranit.

Rozhodli jsme se spustit profilování Golangu na naší službě a zkontrolovat, co přesně způsobuje vysokou spotřebu procesoru, abychom zjistili, zda můžeme optimalizovat.

Nejprve jsme provedli upgrade na nejnovější stabilní verzi Go (klíčová část životního cyklu softwaru). Dosud jsme používali verzi Go v1.12.4 a nejnovější byla 1.13.8. Nyní jsme se rozhodli pro stabilní verzi Go. Verze 1.13 měla podle dokumentace významná vylepšení v knihovně runtime a několika dalších komponentách, které využívaly hlavně využití paměti. Sečteno a podtrženo, práce s nejnovější stabilní verzí byla užitečná a ušetřila nám dost práce →

Zlepšila se tak spotřeba paměti z přibližně ~800 MB na ~180 MB.

Druhé, abychom lépe porozuměli našemu procesu a pochopili, kde utrácíme čas a zdroje, začali jsme profilovat.

Profilování různých služeb a programovacích jazyků se může zdát složité a zastrašující, ale ve skutečnosti je to v Go docela snadné a lze to popsat několika příkazy. Go má speciální nástroj ‚pprof‘, který by měl být ve vaší aplikaci povolen nasloucháním na route (výchozí port- 6060) a použitím balíčku Go pro správu http spojení:

import _ "net/http/pprof"

Poté inicializujte následující ve vaší hlavní funkci nebo v rámci balíčku route:

go func() {log.Println(http.ListenAndServe("localhost:6060", nil))}()

Nyní můžete spustit vaši službu a připojit se k

http://localhost:6060/debug/pprof

Plnou dokumentaci Go najdete zde.

Výchozí profilování pro pprof bude 30sekundové vzorkování využití procesoru. Existuje několik různých cest, které umožňují vzorkování využití CPU, využití haldy a další.

Zaměřili jsme se na využití CPU, takže jsme provedli 30sekundové profilování v produkci a zjistili jsme to, co vidíte na obrázku níže (připomínám: toto je po aktualizaci naší verze Go a snížení vnitřních částí Go na minimum):

Profilování Go – Coralogix

Jak vidíte, zjistili jsme spoustu aktivity balíčků za běhu, která indikovala konkrétně aktivitu GC → Téměř 29 % našeho CPU (jen 20 nejvíce spotřebovávaných objektů) využívá GC. Vzhledem k tomu, že GC v Go je poměrně rychlý a docela optimalizovaný, je nejlepším postupem jej neměnit ani neupravovat, a protože naše spotřeba paměti byla velmi nízká (ve srovnání s předchozí verzí Go), hlavním podezřelým byla vysoká míra alokace objektů.

Pokud je tomu tak, můžeme udělat dvě věci:

  • Vyladit aktivitu GC v Go tak, aby se přizpůsobila chování naší služby, což znamená – odložit její spouštění, aby se GC aktivoval méně často. To nás donutí kompenzovat to větším množstvím paměti.
  • Najděte v našem kódu funkci, oblast nebo řádek, který alokuje příliš mnoho objektů.

Při pohledu na náš typ instance bylo jasné, že máme spoustu volné paměti a v současné době jsme vázáni na strojový procesor. Takže jsme tento poměr prostě prohodili. Golang má od svých počátků příznak, o kterém většina vývojářů neví, nazvaný GOGC. Tento příznak, jehož výchozí hodnota je 100, jednoduše říká systému, kdy má spustit GC. Ve výchozím nastavení se proces GC spustí vždy, když halda dosáhne 100 % své původní velikosti. Změna této hodnoty na vyšší číslo zpozdí spuštění GC a její snížení spustí GC dříve. Začali jsme srovnávat několik různých hodnot a nejlepšího výkonu pro naše účely jsme dosáhli při použití: GOGC=2000.

Tím se okamžitě zvýšilo využití paměti z ~200 MB na ~2,7 GB (to je po snížení spotřeby paměti v důsledku aktualizace naší verze Go) a snížilo se využití procesoru o ~10 %.
Následující snímek obrazovky demonstruje výsledky benchmarku:

GOGC =2000 results – Coralogix benchmark

4 funkce s nejvyšší spotřebou CPU jsou funkce naší služby, což dává smysl. Celková spotřeba GC je nyní ~13 %, což je méně než polovina předchozí spotřeby(!)

Mohli jsme u toho skončit, ale rozhodli jsme se odhalit, kde a proč alokujeme tolik objektů. Mnohdy k tomu existuje dobrý důvod (například v případě proudového zpracování, kdy pro každou přijatou zprávu vytváříme spoustu nových objektů a potřebujeme se jich zbavit, protože jsou pro další zprávu irelevantní), ale existují případy, kdy existuje snadný způsob, jak optimalizovat a dramaticky snížit vytváření objektů.

Na začátek spustíme stejný příkaz jako předtím s jednou malou změnou, abychom pořídili výpis haldy:

http://localhost:6060/debug/pprof/heap

Pro dotaz na výsledný soubor můžete ve složce s kódem spustit následující příkaz pro analýzu výpisu:

go tool pprof -alloc_objects <HEAP.PROFILE.FILE>

Náš snímek vypadal takto:

Vše vypadalo rozumně až na třetí řádek, což je monitorovací funkce, která se na konci každé fáze rozboru pravidel Coralogix hlásí našemu exportéru prometheus. Abychom se dostali hlouběji, spustili jsme následující příkaz:

list <FunctionName>

Například:

list reportRuleExecution

A pak jsme získali následující:

Leave a comment

Vaše e-mailová adresa nebude zveřejněna.