Kymmenen vuotta sitten Google kohtasi kriittisen pullonkaulan, joka johtui erittäin pitkistä C++-käännösajoista, ja se tarvitsi täysin uudenlaisen tavan ratkaista ongelma. Googlen insinöörit vastasivat haasteeseen luomalla uuden kielen nimeltä Go (eli Golang). Uusi Go-kieli lainaa C++:n parhaat puolet (erityisesti sen suorituskyky- ja tietoturvaominaisuudet) ja yhdistää ne Pythonin nopeuteen, jotta Go pystyy nopeasti käyttämään useita ytimiä ja toteuttamaan samanaikaisuuden.

Täällä Coralogixissa analysoimme asiakkaidemme lokitietoja antaaksemme heille reaaliaikaisia oivalluksia, hälytyksiä ja metatietoja lokitiedoistaan. Tätä varten jäsennysvaiheen, joka on hyvin monimutkainen ja ladattu tonneittain sääntöjä kutakin lokirivipalvelua varten, on oltava äärimmäisen nopea. Tämä on yksi syy siihen, että päätimme käyttää Go langia.

Uusi palvelu toimii nyt täysipäiväisesti tuotannossa, ja vaikka näemme hienoja tuloksia, sen on toimittava suorituskykyisillä koneilla. Tämä Go-palvelu, joka toimii AWS:n m4.2xlarge-instanssissa, jossa on 8 prosessoria ja 36 gigatavua muistia, jäsentää päivittäin yli kymmeniä miljardeja lokitietoja.

Tässä vaiheessa olisimme voineet lopettaa päivän ja tuntea olomme loistavaksi siitä, että kaikki sujui hyvin, mutta niin me emme toimi täällä Coralogixissa. Halusimme enemmän ominaisuuksia (suorituskyky jne.) käyttämällä vähemmän (AWS-instansseja). Parantaaksemme toimintaa meidän oli ensin ymmärrettävä pullonkaulojemme luonne ja se, miten voimme vähentää tai poistaa ne kokonaan.

Päätimme suorittaa palvelussamme Golang-profilointia ja tarkistaa, mikä tarkalleen ottaen aiheutti korkean suorittimen kulutuksen, jotta voisimme optimoida sen.

Ensiksi päivitimme viimeisimpään stabiiliin Go -versioon (keskeinen osa ohjelmiston elinkaarta). Käytössämme oli Go versio v1.12.4, ja uusin oli 1.13.8. Dokumentaation mukaan 1.13-versiossa oli merkittäviä parannuksia suorituskirjastossa ja muutamassa muussa komponentissa, jotka lähinnä hyödynsivät muistin käyttöä. Pohjimmiltaan työskentely viimeisimmän vakaan version kanssa oli hyödyllistä ja säästi meiltä melkoisesti työtä →

Siten muistinkulutus parani noin ~800MB:stä ~180MB:iin.

Toiseksi, saadaksemme paremman käsityksen prosessistamme ja ymmärtääkseemme, mihin käytämme aikaa ja resursseja, aloimme profiloida.

Erilaiset palvelut ja ohjelmointikielet voivat tuntua monimutkaisilta ja pelottavilta, mutta itse asiassa Go:n kohdalla homma on melko helppoa ja se voidaan kuvata muutamalla komennolla. Go:lla on oma työkalu nimeltä ’pprof’, joka tulisi ottaa käyttöön sovelluksessasi kuuntelemalla reittiä (oletusportti- 6060) ja käyttämällä Go:n pakettia http-yhteyksien hallintaan:

import _ "net/http/pprof"

Sitten alustetaan seuraavat pääfunktiossasi tai reittipakettisi alla:

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

Nyt voit käynnistää palvelusi ja muodostaa yhteyden

http://localhost:6060/debug/pprof

Täydellinen dokumentaatio Go:lla löytyy täältä.

Pprof:n oletusprofilointi on 30 sekunnin näytteenotto suorittimen käytöstä. On olemassa muutamia eri polkuja, jotka mahdollistavat näytteenoton CPU-käytölle, heapin käytölle ja muille.

Keskityimme CPU-käyttöön, joten otimme 30 sekunnin profiloinnin tuotannossa ja havaitsimme sen, mitä näet alla olevassa kuvassa (muistutus: tämä on sen jälkeen, kun olimme päivittäneet Go -versiomme ja pienentäneet Go:n sisäiset osat minimiin):

Go-profilointi – Coralogix

Kuten näet, löysimme paljon pakettien ajoaikaista aktiivisuutta, joka osoitti nimenomaan GC-aktiviteettia → Lähes 29 % suorittimestamme (vain 20 eniten kulutettua objektia) on GC:n käytössä. Koska Go GC on melko nopea ja melko optimoitu, paras käytäntö on olla muuttamatta tai muokkaamatta sitä, ja koska muistinkulutuksemme oli hyvin vähäistä (verrattuna edelliseen Go -versiomme), pääepäilty oli korkea objektien allokointinopeus.

Jos näin on, voimme tehdä kaksi asiaa:

  • Virittää Go GC -aktiviteettia sopeutumaan palvelukäyttäytymiseemme, mikä tarkoittaa – lykätä sen laukaisua aktivoidaksemme GC:n vähemmän usein. Tämä pakottaa meidät kompensoimaan enemmän muistia.
  • Löydä funktio, alue tai rivi koodistamme, joka allokoi liikaa objekteja.

Katsomalla instanssityyppimme oli selvää, että meillä oli paljon muistia vapaana ja olemme tällä hetkellä koneen suorittimen rajoittamia. Joten vaihdoimme vain tuota suhdetta. Golangilla on alkuaikoina ollut lippu, josta useimmat kehittäjät eivät ole tietoisia, nimeltään GOGC. Tämä lippu, jonka oletusarvo on 100, yksinkertaisesti kertoo järjestelmälle, milloin GC käynnistetään. Oletusarvo käynnistää GC-prosessin aina, kun kasa saavuttaa 100 % sen alkuperäisestä koosta. Tämän arvon muuttaminen suuremmaksi viivästyttää GC:n käynnistämistä ja sen alentaminen käynnistää GC:n aikaisemmin. Aloitimme vertailuanalyysin muutamalla eri arvolla ja paras suorituskyky tarkoitukseemme saavutettiin, kun käytimme: GOGC=2000.

Tämä lisäsi välittömästi muistinkäyttöämme ~200MB:stä ~2.7GB:iin (Tämä sen jälkeen, kun muistinkulutus väheni Go:n versiopäivityksen vuoksi) ja vähensi suorittimen käyttöä ~10%.
Seuraava kuvakaappaus havainnollistaa benchmark-tuloksia:

GOGC =2000 tulokset – Coralogix benchmark

Lisäksi 4 parasta CPU:ta kuluttavaa funktiota ovat palvelumme funktioita, mikä on järkevää. GC:n kokonaiskäyttö on nyt ~13 %, eli alle puolet aiemmasta kulutuksesta(!)

Olisimme voineet lopettaa tähän, mutta päätimme paljastaa, mistä ja miksi allokoimme niin paljon objekteja. Monesti siihen on hyvä syy (esimerkiksi stream-prosessoinnissa, jossa luomme paljon uusia objekteja jokaista saamaamme viestiä varten ja niistä on päästävä eroon, koska ne ovat merkityksettömiä seuraavan viestin kannalta), mutta on tapauksia, joissa on helppo tapa optimoida ja vähentää objektien luomista dramaattisesti.

Aluksi ajetaan sama komento kuin aiemmin yhdellä pienellä muutoksella heap dumpin ottamiseksi:

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

Tulostiedoston kyselyä varten voit ajaa seuraavan komennon koodikansiossasi analysoidaksesi dumpin:

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

tilannekuvamme näytti tältä:

Kaikki näytti kohtuulliselta lukuun ottamatta kolmatta riviä, joka on monitorointitoiminto, joka raportoi prometheus-viejälaitteellemme jokaisen Coralogix-sääntöjen jäsennysvaiheen lopussa. Jotta pääsisimme syvemmälle, suoritimme seuraavan komennon:

list <FunctionName>

Esimerkiksi:

list reportRuleExecution

Ja saimme sitten seuraavaa:

Leave a comment

Sähköpostiosoitettasi ei julkaista.