10 évvel ezelőtt a Google egy kritikus szűk keresztmetszettel nézett szembe, amelyet a rendkívül hosszú C++ fordítási idő okozott, és egy teljesen új megoldási módra volt szüksége. A Google mérnökei egy új nyelv, a Go (más néven Golang) létrehozásával oldották meg a kihívást. Az új Go nyelv a C++ legjobb részeit kölcsönzi (leginkább a teljesítmény és a biztonsági funkciókat), és a Python sebességével kombinálja, hogy a Go gyorsan használhasson több magot, miközben képes az egyidejűség megvalósítására.

Itt, a Coralogixnál elemezzük ügyfeleink naplóit, hogy valós idejű betekintést, riasztásokat és metaadatokat nyújtsunk nekik a naplóikról. Ehhez rendkívül gyorsnak kell lennie az elemzési fázisnak, amely nagyon összetett és rengeteg szabállyal terhelt minden egyes naplósor-szolgáltatáshoz. Ez az egyik oka annak, hogy a Go lang használata mellett döntöttünk.

Az új szolgáltatás már teljes munkaidőben fut a termelésben, és bár nagyszerű eredményeket látunk, nagy teljesítményű gépeken kell futnia. Naponta több tízmilliárdnyi naplót elemez ez a Go szolgáltatás, amely egy AWS m4.2xlarge példányon fut 8 CPU-val és 36 GB memóriával.

Ebben a szakaszban akár le is zárhattuk volna a napot azzal a nagyszerű érzéssel, hogy minden jól működik, de mi itt a Coralogixnál nem így működünk. Több funkciót akartunk (teljesítmény stb.) kevesebb (AWS-példányok) felhasználásával. A javulás érdekében először meg kellett értenünk a szűk keresztmetszeteink természetét, és azt, hogy hogyan tudjuk csökkenteni vagy teljesen kiküszöbölni őket.

Úgy döntöttünk, hogy lefuttatunk néhány Golang profilozást a szolgáltatásunkon, és megnézzük, hogy pontosan mi okozta a magas CPU-fogyasztást, hogy lássuk, optimalizálhatunk-e.

Először is frissítettünk a legújabb stabil Go verzióra (a szoftver életciklusának kulcsfontosságú része). A Go v1.12.4-es verzióján voltunk, a legújabb pedig az 1.13.8-as volt. Az 1.13-as kiadás a dokumentáció szerint jelentős fejlesztéseket tartalmazott a futásidejű könyvtárban és néhány más komponensben, amelyek elsősorban a memóriahasználatot használták. Lényegében a legújabb stabil verzióval való munka hasznos volt, és elég sok munkát megspórolt nekünk →

Így a memóriafogyasztás körülbelül ~800MB-ről ~180MB-ra javult.

Második lépésként, hogy jobban megértsük a folyamatunkat, és megértsük, hol költünk időt és erőforrásokat, elkezdtünk profilozni.

A különböző szolgáltatások és programozási nyelvek profilozása összetettnek és ijesztőnek tűnhet, de valójában elég egyszerű a Go-ban, és néhány paranccsal leírható. A Go rendelkezik egy dedikált ‘pprof’ nevű eszközzel, amelyet engedélyezni kell az alkalmazásodban egy útvonal figyelésével (alapértelmezett port- 6060) és a Go csomag használatával a http kapcsolatok kezelésére:

import _ "net/http/pprof"

Ezután inicializáld a következőket a főfüggvényedben vagy az útvonal csomagod alatt:

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

Most elindíthatod a szolgáltatásodat és csatlakozhatsz

http://localhost:6060/debug/pprof

A Go teljes dokumentációja itt található.

A pprof alapértelmezett profilozása a CPU használat 30 másodperces mintavételezése lesz. Van néhány különböző útvonal, amely lehetővé teszi a mintavételt a CPU használatra, a heap használatra és másra.

A CPU-használatra koncentráltunk, ezért 30 másodperces profilozást végeztünk a termelésben, és azt fedeztük fel, amit az alábbi képen látsz (emlékeztető: ez a Go verzió frissítése és a Go belső részeinek minimálisra csökkentése után történt):

Go profilozás – Coralogix

Amint látható, sok futásidejű csomagaktivitást találtunk, ami kifejezetten GC aktivitást jelzett → A CPU-nk majdnem 29%-át (csak a 20 legtöbbet fogyasztott objektumot) a GC használja. Mivel a Go GC elég gyors és eléggé optimalizált, a legjobb gyakorlat az, ha nem változtatjuk vagy módosítjuk, és mivel a memóriafogyasztásunk nagyon alacsony volt (az előző Go verzióhoz képest), a fő gyanúsított a magas objektumkiosztási arány volt.

Ha ez a helyzet, két dolgot tehetünk:

  • A Go GC aktivitást úgy hangoljuk, hogy alkalmazkodjon a szolgáltatásunk viselkedéséhez, vagyis – késleltetjük a kiváltását, hogy a GC-t ritkábban aktiváljuk. Ez arra kényszerít minket, hogy több memóriával kompenzáljuk.
  • Keresd meg azt a függvényt, területet vagy sort a kódunkban, amely túl sok objektumot allokál.

A példánytípusunkat megnézve egyértelmű volt, hogy rengeteg memóriánk van, és jelenleg a gép processzorához vagyunk kötve. Ezért egyszerűen átállítottuk ezt az arányt. A Golangnak már a kezdeti idők óta van egy flagje, amit a legtöbb fejlesztő nem ismer, ez a GOGC. Ez a flag, amelynek alapértelmezett értéke 100, egyszerűen megmondja a rendszernek, hogy mikor indítsa el a GC-t. Az alapértelmezés szerint a GC folyamatot akkor indítja el, amikor a heap eléri a kezdeti méretének 100%-át. Ha ezt az értéket magasabb számra változtatjuk, akkor a GC indítása késleltetve lesz, ha pedig alacsonyabbra csökkentjük, akkor a GC hamarabb fog elindulni. Elkezdtünk benchmarkolni néhány különböző értéket, és a célunknak megfelelő legjobb teljesítményt akkor értük el, ha a: GOGC=2000.

Ez azonnal megnövelte a memóriahasználatunkat ~200MB-ről ~2.7GB-ra (Ez azután van, hogy a memóriafogyasztás csökkent a Go verziófrissítésünk miatt) és ~10%-kal csökkentette a CPU használatot.
A következő képernyőkép mutatja a benchmark eredményeket:

GOGC =2000 eredmények – Coralogix benchmark

Az első 4 CPU-fogyasztó funkció a mi szolgáltatásunk funkciói, ami érthető. A teljes GC-használat most ~13%, kevesebb mint fele a korábbi fogyasztásnak(!)

Megállhattunk volna itt, de úgy döntöttünk, hogy feltárjuk, hol és miért allokálunk ennyi objektumot. Sokszor jó okunk van rá (például folyamfeldolgozás esetén, ahol minden egyes kapott üzenetre sok új objektumot hozunk létre, és meg kell szabadulnunk tőle, mert a következő üzenet szempontjából irreleváns), de vannak olyan esetek, amikor könnyen lehet optimalizálni és drasztikusan csökkenteni az objektum létrehozását.

Kezdésként futtassuk le ugyanazt a parancsot, mint korábban, egy apró változtatással, hogy a heap dumpot készítsük:

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

Az eredményfájl lekérdezéséhez a következő parancsot futtathatjuk a kódmappánkon belül, hogy elemezzük a dumpot:

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

a pillanatfelvételünk így nézett ki:

Minden ésszerűnek tűnt, kivéve a harmadik sort, amely egy felügyeleti funkció, amely minden Coralogix szabályelemzési fázis végén jelentést tesz a prometheus exporterünknek. Hogy mélyebbre ássunk, lefuttattuk a következő parancsot:

list <FunctionName>

Egy példa:

list reportRuleExecution

Az alábbiakat kaptuk:

list reportRuleExecution

Ezután pedig a következőket:

Leave a comment

Az e-mail-címet nem tesszük közzé.