Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Predlagam, da preberete prepis poročila Aleksandra Valjalkina konec leta 2019 »Optimizacije Go v VictoriaMetrics«

VictoriaMetrics — hiter in razširljiv DBMS za shranjevanje in obdelavo podatkov v obliki časovne serije (zapis tvori čas in niz vrednosti, ki ustrezajo temu času, na primer pridobljene s periodičnim anketiranjem statusa senzorjev ali zbiranjem meritve).

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Tukaj je povezava do videoposnetka tega poročila - https://youtu.be/MZ5P21j_HLE

Diapozitivi

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Povej nam o sebi. Jaz sem Alexander Valyalkin. Tukaj moj račun GitHub. Navdušen sem nad Go in optimizacijo delovanja. Napisal sem veliko uporabnih in manj uporabnih knjižnic. Začnejo z bodisi fastali z quick predpono.

Trenutno delam na VictoriaMetrics. Kaj je in kaj počnem tam? O tem bom govoril v tej predstavitvi.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Osnutek poročila je naslednji:

  • Najprej vam bom povedal, kaj je VictoriaMetrics.
  • Potem vam bom povedal, kaj so časovne vrste.
  • Nato vam bom povedal, kako deluje podatkovna zbirka časovnih vrst.
  • Nato vam bom povedal o arhitekturi baze podatkov: iz česa je sestavljena.
  • In potem preidimo na optimizacije, ki jih ima VictoriaMetrics. To je optimizacija za invertni indeks in optimizacija za implementacijo bitset v Go.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Ali kdo v občinstvu ve, kaj je VictoriaMetrics? Vau, veliko ljudi že ve. To je dobra novica. Za tiste, ki ne vedo, je to baza časovnih vrst. Temelji na arhitekturi ClickHouse, na nekaterih podrobnostih implementacije ClickHouse. Na primer, kot so: MergeTree, vzporedni izračun na vseh razpoložljivih procesorskih jedrih in optimizacija zmogljivosti z delom na podatkovnih blokih, ki so v predpomnilniku procesorja.

VictoriaMetrics zagotavlja boljše stiskanje podatkov kot druge baze podatkov časovnih vrst.

Prilagodi se navpično - to pomeni, da lahko dodate več procesorjev, več RAM-a na en računalnik. VictoriaMetrics bo uspešno uporabila te razpoložljive vire in izboljšala linearno produktivnost.

VictoriaMetrics se skalira tudi vodoravno – to pomeni, da lahko dodate dodatna vozlišča v gručo VictoriaMetrics in njegova zmogljivost se bo povečala skoraj linearno.

Kot ste uganili, je VictoriaMetrics hitra zbirka podatkov, ker ne morem pisati drugih. In to je napisano v Go, zato o tem govorim na tem srečanju.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kdo ve, kaj je časovna vrsta? Pozna tudi veliko ljudi. Časovni niz je niz parov (timestamp, значение), kjer so ti pari razvrščeni po času. Vrednost je število s plavajočo vejico – float64.

Vsaka časovna vrsta je edinstveno identificirana s ključem. Iz česa je sestavljen ta ključ? Sestavljen je iz neprazne množice parov ključ-vrednost.

Tukaj je primer časovne serije. Ključ te serije je seznam parov: __name__="cpu_usage" je ime metrike, instance="my-server" - to je računalnik, na katerem se zbira ta metrika, datacenter="us-east" - to je podatkovni center, kjer se nahaja ta računalnik.

Na koncu smo dobili ime časovne serije, sestavljeno iz treh parov ključ-vrednost. Ta ključ ustreza seznamu parov (timestamp, value). t1, t3, t3, ..., tN - to so časovni žigi, 10, 20, 12, ..., 15 — ustrezne vrednosti. To je poraba procesorja v danem času za dano serijo.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kje je mogoče uporabiti časovne vrste? Ima kdo kakšno idejo?

  • V DevOps lahko merite CPE, RAM, omrežje, rps, število napak itd.
  • IoT - merimo lahko temperaturo, pritisk, geo koordinate in še kaj.
  • Tudi finance – spremljamo lahko cene za vse vrste delnic in valut.
  • Poleg tega se časovne vrste lahko uporabljajo pri spremljanju proizvodnih procesov v tovarnah. Imamo uporabnike, ki uporabljajo VictoriaMetrics za spremljanje vetrnih turbin za robote.
  • Časovne vrste so uporabne tudi za zbiranje informacij iz senzorjev različnih naprav. Na primer za motor; za merjenje tlaka v pnevmatikah; za merjenje hitrosti, razdalje; za merjenje porabe bencina itd.
  • Časovne vrste se lahko uporabljajo tudi za spremljanje letal. Vsako letalo ima črno skrinjico, ki zbira časovne vrste za različne parametre zdravstvenega stanja letala. Časovne vrste se uporabljajo tudi v vesoljski industriji.
  • Zdravstvo je krvni tlak, pulz itd.

Morda obstaja več aplikacij, na katere sem pozabil, vendar upam, da razumete, da se časovne vrste aktivno uporabljajo v sodobnem svetu. In obseg njihove uporabe vsako leto narašča.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Zakaj potrebujete bazo časovnih vrst? Zakaj ne morete uporabiti navadne relacijske baze podatkov za shranjevanje časovnih vrst?

Kajti časovne vrste običajno vsebujejo veliko količino informacij, ki jih je težko shraniti in obdelati v običajnih bazah podatkov. Zato so se pojavile specializirane zbirke podatkov za časovne vrste. Te baze učinkovito shranjujejo točke (timestamp, value) z danim ključem. Zagotavljajo API za branje shranjenih podatkov po ključu, po enem paru ključ-vrednost ali po več parih ključ-vrednost ali po regularnem izrazu. Na primer, če želite ugotoviti obremenitev procesorja vseh vaših storitev v podatkovnem centru v Ameriki, potem morate uporabiti to psevdo poizvedbo.

Podatkovne zbirke časovnih vrst običajno zagotavljajo specializirane poizvedovalne jezike, ker SQL časovnih vrst ni zelo primeren. Čeprav obstajajo baze podatkov, ki podpirajo SQL, ni zelo primeren. Poizvedovalni jeziki, kot je npr PromQL, InfluxQL, Flux, Q. Upam, da je kdo slišal vsaj enega od teh jezikov. Mnogi ljudje so verjetno slišali za PromQL. To je jezik poizvedb Prometheus.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Tako je videti sodobna arhitektura baze podatkov časovnih vrst na primeru VictoriaMetrics.

Sestavljen je iz dveh delov. To je shramba za invertni indeks in shramba za vrednosti časovnih vrst. Ti repozitoriji so ločeni.

Ko v bazo podatkov prispe nov zapis, najprej dostopamo do obrnjenega indeksa, da poiščemo identifikator časovne vrste za dani niz label=value za dano metriko. Ta identifikator poiščemo in vrednost shranimo v shrambo podatkov.

Ko pride zahteva za pridobitev podatkov iz TSDB, gremo najprej na obrnjeni indeks. Dobimo vse timeseries_ids zapisov, ki ustrezajo temu nizu label=value. In potem dobimo vse potrebne podatke iz podatkovnega skladišča, indeksirane s timeseries_ids.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Oglejmo si primer, kako baza podatkov časovnih vrst obdela dohodno poizvedbo za izbiro.

  • Najprej dobi vse timeseries_ids iz obrnjenega indeksa, ki vsebuje podane pare label=value, ali zadovoljijo dani regularni izraz.
  • Nato pridobi vse podatkovne točke iz pomnilnika podatkov v danem časovnem intervalu za najdene timeseries_ids.
  • Po tem baza podatkov izvede nekaj izračunov na teh podatkovnih točkah glede na zahtevo uporabnika. In po tem vrne odgovor.

V tej predstavitvi vam bom povedal o prvem delu. To je iskanje timeseries_ids z obrnjenim indeksom. Drugi del in tretji del si lahko ogledate kasneje Viri VictoriaMetrics, ali počakajte, da pripravim druga poročila :)

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Preidimo na obrnjeni indeks. Mnogi morda mislijo, da je to preprosto. Kdo ve, kaj je invertni indeks in kako deluje? Oh, ni več toliko ljudi. Poskusimo razumeti, kaj je to.

Pravzaprav je preprosto. To je preprosto slovar, ki preslika ključ v vrednost. Kaj je ključ? Ta par label=valueČe label и value - to so vrstice. In vrednosti so nabor timeseries_ids, ki vključuje dani par label=value.

Obrnjen indeks vam omogoča, da hitro najdete vse timeseries_ids, ki so dali label=value.

Omogoča tudi hitro iskanje timeseries_ids časovne vrste za več parov label=value, ali za pare label=regexp. Kako se to zgodi? Z iskanjem presečišča množice timeseries_ids za vsak par label=value.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Oglejmo si različne izvedbe obrnjenega indeksa. Začnimo z najpreprostejšo naivno izvedbo. Izgleda takole.

Funkcija getMetricIDs dobi seznam nizov. Vsaka vrstica vsebuje label=value. Ta funkcija vrne seznam metricIDs.

Kako deluje? Tukaj imamo globalno spremenljivko, imenovano invertedIndex. To je običajni slovar (map), ki bo preslikal niz v rezino int. Vrstica vsebuje label=value.

Izvedba funkcije: get metricIDs za prvo label=value, potem gremo skozi vse ostalo label=value, razumemo metricIDs za njih. In pokličite funkcijo intersectInts, o katerem bo govora v nadaljevanju. In ta funkcija vrne presečišče teh seznamov.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kot lahko vidite, implementacija obrnjenega indeksa ni zelo zapletena. Ampak to je naivna izvedba. Kakšne slabosti ima? Glavna pomanjkljivost naivne izvedbe je, da je tako obrnjen indeks shranjen v RAM-u. Po ponovnem zagonu aplikacije izgubimo ta indeks. Ta indeks se ne shrani na disk. Takšen obrnjen indeks verjetno ne bo primeren za bazo podatkov.

Druga pomanjkljivost je povezana tudi s spominom. Obrnjeni indeks se mora prilegati RAM-u. Če preseže velikost RAM-a, potem bomo očitno dobili napako - zmanjkalo pomnilnika. In program ne bo deloval.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Ta problem je mogoče rešiti z že pripravljenimi rešitvami, kot je npr LevelDBAli RocksDB.

Skratka, potrebujemo bazo podatkov, ki nam omogoča hitro izvedbo treh operacij.

  • Prva operacija je snemanje ключ-значение v to bazo podatkov. To počne zelo hitro, kjer ключ-значение so poljubni nizi.
  • Druga operacija je hitro iskanje vrednosti z danim ključem.
  • In tretja operacija je hitro iskanje vseh vrednosti po dani predponi.

LevelDB in RocksDB - ti bazi podatkov sta razvila Google in Facebook. Najprej je prišel LevelDB. Potem so fantje iz Facebooka vzeli LevelDB in ga začeli izboljševati, naredili so RocksDB. Zdaj skoraj vse notranje baze podatkov delujejo na RocksDB znotraj Facebooka, vključno s tistimi, ki so bile prenesene v RocksDB in MySQL. Poimenovali so ga MyRocks.

Obrnjen indeks je mogoče implementirati z uporabo LevelDB. Kako narediti? Shranjujemo kot ključ label=value. In vrednost je identifikator časovne serije, kjer je par prisoten label=value.

Če imamo veliko časovnih vrst z danim parom label=value, potem bo v tej zbirki podatkov veliko vrstic z enakim in različnim ključem timeseries_ids. Da bi dobili seznam vseh timeseries_ids, ki se začnejo s tem label=prefix, opravimo pregled obsega, za katerega je ta zbirka podatkov optimizirana. To pomeni, da izberemo vse vrstice, ki se začnejo z label=prefix in dobite potrebno timeseries_ids.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Tukaj je vzorčna izvedba, kako bi to izgledalo v Go. Imamo obrnjen indeks. To je LevelDB.

Funkcija je enaka kot pri naivni izvedbi. Skoraj vrstico za vrstico ponavlja naivno izvedbo. Edina točka je, da namesto da bi se obrnil na map dostopamo do obrnjenega indeksa. Dobimo vse vrednosti za prvo label=value. Nato gremo skozi vse preostale pare label=value in zanje pridobite ustrezne nize metričnih ID-jev. Nato najdemo križišče.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Zdi se, da je vse v redu, vendar ima ta rešitev pomanjkljivosti. VictoriaMetrics je sprva implementiral invertni indeks, ki temelji na LevelDB. Toda na koncu sem moral opustiti.

Zakaj? Ker je LevelDB počasnejši od naivne izvedbe. V naivni izvedbi z danim ključem takoj pridobimo celotno rezino metricIDs. To je zelo hiter postopek - celotna rezina je pripravljena za uporabo.

V LevelDB vsakič, ko se pokliče funkcija GetValues iti morate skozi vse vrstice, ki se začnejo z label=value. In dobite vrednost za vsako vrstico timeseries_ids. Takih timeseries_ids zberite rezino teh timeseries_ids. Očitno je to veliko počasneje kot preprost dostop do običajnega zemljevida s ključem.

Druga pomanjkljivost je, da je LevelDB napisan v C. Klicanje funkcij C iz Go ni zelo hitro. Traja na stotine nanosekund. To ni zelo hitro, ker je v primerjavi z običajnim klicem funkcije, napisanim v go, ki traja 1–5 nanosekund, razlika v zmogljivosti desetkratna. Za VictoriaMetrics je bila to usodna napaka :)

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Zato sem napisal lastno implementacijo obrnjenega indeksa. In poklical jo je mergeset.

Mergeset temelji na podatkovni strukturi MergeTree. Ta struktura podatkov je izposojena pri ClickHouse. Očitno bi moral biti mergeset optimiziran za hitro iskanje timeseries_ids po danem ključu. Mergeset je v celoti napisan v Go. Lahko vidiš Viri VictoriaMetrics na GitHub. Izvedba mergeseta je v mapi /lib/mergeset. Lahko poskusite ugotoviti, kaj se tam dogaja.

API mergeset je zelo podoben LevelDB in RocksDB. To pomeni, da vam omogoča, da tam hitro shranite nove zapise in hitro izberete zapise po dani predponi.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

O slabostih mergeseta bomo govorili kasneje. Zdaj pa se pogovorimo o tem, kakšne težave so se pojavile pri VictoriaMetrics v proizvodnji pri izvajanju obrnjenega indeksa.

Zakaj so nastali?

Prvi razlog je visoka stopnja odliva. Prevedeno v ruščino, je to pogosta sprememba časovnih vrst. Takrat se časovna vrsta konča in začne nova serija ali pa se začnejo številne nove časovne vrste. In to se pogosto zgodi.

Drugi razlog je veliko število časovnih vrst. Na začetku, ko je spremljanje postajalo vse bolj priljubljeno, je bilo število časovnih vrst majhno. Na primer, za vsak računalnik morate spremljati CPU, pomnilnik, omrežje in obremenitev diska. 4 časovne serije na računalnik. Recimo, da imate 100 računalnikov in 400 časovnih vrst. To je zelo malo.

Sčasoma so ljudje ugotovili, da lahko merijo bolj natančne informacije. Na primer, izmerite obremenitev ne celotnega procesorja, temveč ločeno vsakega jedra procesorja. Če imate 40 procesorskih jeder, potem imate 40-krat več časovnih vrst za merjenje obremenitve procesorja.

A to še ni vse. Vsako procesorsko jedro ima lahko več stanj, na primer stanje mirovanja, ko je nedejavno. In tudi delo v uporabniškem prostoru, delo v prostoru jedra in drugih stanjih. In vsako takšno stanje je mogoče izmeriti tudi kot ločeno časovno vrsto. S tem se dodatno poveča število vrstic za 7-8 krat.

Iz ene metrike smo dobili 40 x 8 = 320 metrik za samo en računalnik. Če pomnožimo s 100, dobimo 32 namesto 000.

Potem je prišel Kubernetes. Še huje je postalo, ker lahko Kubernetes gosti veliko različnih storitev. Vsaka storitev v Kubernetesu je sestavljena iz številnih sklopov. In vse to je treba spremljati. Poleg tega imamo stalno uvajanje novih različic vaših storitev. Za vsako novo različico je treba ustvariti nove časovne vrste. Posledično število časovnih vrst eksponentno narašča in soočeni smo s problemom velikega števila časovnih vrst, kar imenujemo visoka kardinalnost. VictoriaMetrics se s tem uspešno spopada v primerjavi z drugimi bazami časovnih vrst.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Oglejmo si podrobneje visoko stopnjo odliva. Kaj povzroča visoko stopnjo osipa v proizvodnji? Ker se nekateri pomeni oznak in oznak nenehno spreminjajo.

Na primer, vzemite Kubernetes, ki ima koncept deployment, tj. ko je uvedena nova različica vaše aplikacije. Iz neznanega razloga so se razvijalci Kubernetesa odločili, da nalepki dodajo ID uvedbe.

Kaj je to vodilo? Poleg tega se z vsako novo uvedbo vse stare časovne vrste prekinejo in namesto njih se začnejo nove časovne serije z novo vrednostjo oznake deployment_id. Takšnih vrstic je lahko več sto tisoč in celo milijone.

Pri vsem tem je pomembno, da skupno število časovnih vrst raste, vendar ostaja število časovnih vrst, ki so trenutno aktivne in prejemajo podatke, konstantno. To stanje se imenuje visoka stopnja odliva.

Glavna težava visoke stopnje osipa je zagotoviti konstantno hitrost iskanja za vse časovne vrste za dani nabor oznak v določenem časovnem intervalu. Običajno je to časovni interval za zadnjo uro ali zadnji dan.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kako rešiti ta problem? Tukaj je prva možnost. To je razdelitev obrnjenega indeksa na neodvisne dele skozi čas. To pomeni, da mine nekaj časovnega intervala, zaključimo delo s trenutnim obrnjenim indeksom. In ustvarite nov obrnjen indeks. Mine še en časovni interval, ustvarimo še enega in še enega.

In pri vzorčenju iz teh obrnjenih indeksov najdemo niz obrnjenih indeksov, ki spadajo v dani interval. In v skladu s tem od tam izberemo ID časovne serije.

To prihrani sredstva, ker nam ni treba gledati delov, ki ne spadajo v dani interval. To pomeni, da običajno, če izberemo podatke za zadnjo uro, potem za prejšnje časovne intervale preskočimo poizvedbe.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Obstaja še ena možnost za rešitev te težave. To je za shranjevanje za vsak dan ločenega seznama ID-jev časovnih vrst, ki so se zgodile na ta dan.

Prednost te rešitve pred prejšnjo rešitvijo je, da ne podvajamo informacij o časovnih vrstah, ki s časom ne izginejo. Stalno so prisotni in se ne spreminjajo.

Pomanjkljivost je, da je takšno rešitev težje implementirati in težje odpravljati napake. In VictoriaMetrics je izbrala to rešitev. Tako se je zgodovinsko zgodilo. Tudi ta rešitev se dobro obnese v primerjavi s prejšnjo. Ker ta rešitev ni bila implementirana zaradi dejstva, da je treba v vsaki particiji podvojiti podatke za časovne vrste, ki se ne spreminjajo, torej ki s časom ne izginejo. VictoriaMetrics je bil v prvi vrsti optimiziran za porabo prostora na disku, prejšnja izvedba pa je porabo prostora na disku poslabšala. Toda ta izvedba je bolj primerna za zmanjšanje porabe prostora na disku, zato je bila izbrana.

Moral sem se boriti z njo. Težava je bila v tem, da morate pri tej izvedbi še vedno izbrati veliko večje število timeseries_ids za podatke kot takrat, ko je obrnjeni indeks časovno particioniran.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kako smo rešili ta problem? Rešili smo ga na izviren način - tako, da smo namesto enega identifikatorja v vsak vnos obrnjenega indeksa shranili več identifikatorjev časovnih vrst. Se pravi, imamo ključ label=value, ki se pojavlja v vsaki časovni seriji. In zdaj jih prihranimo nekaj timeseries_ids v enem vnosu.

Tukaj je primer. Prej smo imeli N vnosov, zdaj pa imamo en vnos, katerega predpona je enaka vsem ostalim. Za prejšnji vnos vrednost vsebuje vse ID-je časovnih vrst.

To je omogočilo povečanje hitrosti skeniranja takšnega obrnjenega indeksa do 10-krat. Omogočil nam je tudi zmanjšanje porabe pomnilnika za predpomnilnik, ker zdaj shranjujemo niz label=value samo enkrat v predpomnilnik skupaj N-krat. In ta vrstica je lahko velika, če v oznake in nalepke shranite dolge vrstice, ki jih Kubernetes rad potisne tja.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Druga možnost za pospešitev iskanja po obrnjenem indeksu je razrezovanje. Ustvarjanje več obrnjenih indeksov namesto enega in razdelitev podatkov med njimi po ključu. To je komplet key=value paro. To pomeni, da dobimo več neodvisnih invertiranih indeksov, po katerih lahko vzporedno poizvedujemo na več procesorjih. Prejšnje izvedbe so dovoljevale samo delovanje v enoprocesorskem načinu, torej skeniranje podatkov v samo enem jedru. Ta rešitev vam omogoča skeniranje podatkov v več jedrih hkrati, kot to rad počne ClickHouse. To nameravamo uresničiti.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Zdaj pa se vrnimo k našim ovcam - k funkciji presečišča timeseries_ids. Razmislimo, kakšne izvedbe lahko obstajajo. Ta funkcija vam omogoča iskanje timeseries_ids za dani niz label=value.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Prva možnost je naivna izvedba. Dve ugnezdeni zanki. Tukaj dobimo vnos funkcije intersectInts dve rezini - a и b. Na izhodu nam mora vrniti presečišče teh rezin.

Naivna izvedba izgleda takole. Ponavljamo vse vrednosti iz rezine a, znotraj te zanke gremo skozi vse vrednosti rezine b. In jih primerjamo. Če se ujemata, potem smo našli presečišče. In ga shranite result.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kakšne so slabosti? Kvadratna kompleksnost je njegova glavna pomanjkljivost. Na primer, če so vaše mere rezine a и b en milijon naenkrat, potem vam ta funkcija nikoli ne bo vrnila odgovora. Ker bo moral narediti bilijon iteracij, kar je veliko tudi za sodobne računalnike.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Druga izvedba temelji na zemljevidu. Ustvarjamo zemljevid. V ta zemljevid smo postavili vse vrednosti iz rezine a. Nato gremo skozi rezino v ločeni zanki b. In preverimo, ali je ta vrednost iz rezine b v zemljevidu. Če obstaja, ga dodajte rezultatu.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Kakšne so prednosti? Prednost je, da obstaja samo linearna kompleksnost. To pomeni, da se bo funkcija za večje rezine izvajala veliko hitreje. Za rezino velikosti milijona se bo ta funkcija izvedla v 2 milijonih ponovitev, v nasprotju z bilijoni ponovitev prejšnje funkcije.

Slaba stran je, da ta funkcija za ustvarjanje tega zemljevida potrebuje več pomnilnika.

Druga pomanjkljivost so veliki stroški zgoščevanja. Ta pomanjkljivost ni zelo očitna. In tudi za nas to ni bilo zelo očitno, zato je bila sprva v VictoriaMetrics implementacija presečišča prek zemljevida. Toda nato je profiliranje pokazalo, da se čas glavnega procesorja porabi za pisanje zemljevida in preverjanje prisotnosti vrednosti na tem zemljevidu.

Zakaj se na teh mestih zapravlja čas procesorja? Ker Go izvede operacijo zgoščevanja teh vrstic. To pomeni, da izračuna zgoščeno vrednost ključa, da bi nato do njega dostopal pri danem indeksu v HashMap. Operacija izračuna zgoščene vrednosti se zaključi v desetinah nanosekund. To je za VictoriaMetrics počasno.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Odločil sem se implementirati bitset, optimiziran posebej za ta primer. Tako je zdaj videti presečišče dveh rezin. Tukaj ustvarimo bitset. Vanj dodamo elemente iz prve rezine. Nato preverimo prisotnost teh elementov v drugi rezini. In jih dodajte rezultatu. To pomeni, da se skoraj ne razlikuje od prejšnjega primera. Edina stvar tukaj je, da smo zamenjali dostop do zemljevida s funkcijami po meri add и has.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Na prvi pogled se zdi, da bi to moralo delovati počasneje, če je bil prej tam uporabljen standardni zemljevid, potem pa se kličejo nekatere druge funkcije, vendar profiliranje pokaže, da ta stvar deluje 10-krat hitreje kot standardni zemljevid v primeru VictoriaMetrics.

Poleg tega uporablja veliko manj pomnilnika v primerjavi z izvedbo zemljevida. Ker tukaj shranjujemo bite namesto osembajtnih vrednosti.

Slabost te izvedbe je, da ni tako očitna, ni trivialna.

Druga pomanjkljivost, ki je mnogi morda ne opazijo, je, da ta izvedba v nekaterih primerih morda ne bo delovala dobro. To pomeni, da je optimiziran za poseben primer, za ta primer presečišča ID-jev časovnih vrst VictoriaMetrics. To ne pomeni, da je primeren za vse primere. Če je uporabljen nepravilno, ne bomo dobili povečanja zmogljivosti, ampak napako zmanjkanja pomnilnika in upočasnitev delovanja.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Oglejmo si izvedbo te strukture. Če želite pogledati, se nahaja v virih VictoriaMetrics, v mapi lib/uint64set. Optimiziran je posebej za primer VictoriaMetrics, kjer timeseries_id je 64-bitna vrednost, kjer je prvih 32 bitov v bistvu stalnih in se spreminja samo zadnjih 32 bitov.

Ta podatkovna struktura ni shranjena na disku, ampak deluje samo v pomnilniku.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Tukaj je njegov API. Ni zelo zapleteno. API je prilagojen posebej za določen primer uporabe VictoriaMetrics. To pomeni, da tukaj ni nepotrebnih funkcij. Tukaj so funkcije, ki jih izrecno uporablja VictoriaMetrics.

Obstajajo funkcije add, ki dodaja nove vrednosti. Obstaja funkcija has, ki preverja nove vrednosti. In obstaja funkcija del, ki odstrani vrednosti. Obstaja pomočna funkcija len, ki vrne velikost nabora. funkcija clone veliko klonira. In funkcija appendto pretvori ta niz v rezino timeseries_ids.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Takole izgleda implementacija te podatkovne strukture. set ima dva elementa:

  • ItemsCount je pomožno polje za hitro vrnitev števila elementov v nizu. Brez tega pomožnega polja bi bilo mogoče, vendar ga je bilo treba dodati tukaj, ker VictoriaMetrics v svojih algoritmih pogosto povprašuje po dolžini bitseta.

  • Drugo polje je buckets. To je delček strukture bucket32. Vsaka struktura shranjuje hi polje. To je zgornjih 32 bitov. In dve rezini - b16his и buckets z dne bucket16 strukture.

Tukaj je shranjenih 16 zgornjih bitov drugega dela 64-bitne strukture. In tukaj so bitni nizi shranjeni za spodnjih 16 bitov vsakega bajta.

Bucket64 je sestavljen iz niza uint64. Dolžina se izračuna z uporabo teh konstant. V enem bucket16 lahko shranite največ 2^16=65536 bit. Če to delite z 8, potem je 8 kilobajtov. Če še enkrat delite z 8, je 1000 uint64 pomen. To je Bucket16 – to je naša 8-kilobajtna struktura.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Poglejmo, kako je implementirana ena od metod te strukture za dodajanje nove vrednosti.

Vse se začne z uint64 pomeni. Izračunamo zgornjih 32 bitov, izračunamo spodnjih 32 bitov. Pojdimo skozi vse buckets. Zgornjih 32 bitov v vsakem vedru primerjamo z dodano vrednostjo. In če se ujemata, potem pokličemo funkcijo add v strukturi b32 buckets. In tam dodajte spodnjih 32 bitov. In če se je vrnilo true, potem to pomeni, da smo tam dodali takšno vrednost in je nismo imeli. Če se vrne false, potem je tak pomen že obstajal. Nato povečamo število elementov v strukturi.

Če nismo našli tistega, ki ga potrebujete bucket z zahtevano hi-vrednostjo, nato pokličemo funkcijo addAlloc, ki bo izdelala novo bucket, ki ga doda v strukturo vedra.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

To je izvajanje funkcije b32.add. Podobno je prejšnji izvedbi. Izračunamo najpomembnejših 16 bitov, najmanj pomembnih 16 bitov.

Nato gremo skozi vseh zgornjih 16 bitov. Najdemo ujemanja. In če obstaja ujemanje, pokličemo metodo dodajanja, ki jo bomo obravnavali na naslednji strani bucket16.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

In tukaj je najnižja raven, ki jo je treba čim bolj optimizirati. Računamo za uint64 vrednost id v bitu rezine in tudi bitmask. To je maska ​​za dano 64-bitno vrednost, s katero lahko preverite prisotnost tega bita ali jo nastavite. Preverimo, ali je ta bit nastavljen, ga nastavimo in vrnemo prisotnost. To je naša izvedba, ki nam je omogočila, da smo delovanje sekajočih se ID-jev časovnih vrst pohitrili za 10-krat v primerjavi s konvencionalnimi zemljevidi.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Poleg te optimizacije ima VictoriaMetrics še veliko drugih optimizacij. Večina teh optimizacij je bila dodanih z razlogom, vendar po profiliranju kode v proizvodnji.

To je glavno pravilo optimizacije – ne dodajajte optimizacije ob predpostavki, da bo tukaj ozko grlo, ker se lahko izkaže, da tam ozkega grla ne bo. Optimizacija običajno poslabša kakovost kode. Zato se splača optimizirati šele po profiliranju in po možnosti v produkciji, da so to pravi podatki. Če koga zanima, si lahko ogleda izvorno kodo VictoriaMetrics in razišče druge optimizacije, ki so tam.

Pojdi na optimizacijo v VictoriaMetrics. Aleksander Valjalkin

Imam vprašanje glede bitset-a. Zelo podoben vektorski bool implementaciji C++, optimiziran bitset. Ste implementacijo prevzeli od tam?

Ne, ne od tam. Pri implementaciji tega bitseta me je vodilo poznavanje strukture teh časovnih vrst id, ki se uporabljajo v VictoriaMetrics. In njihova struktura je takšna, da je zgornjih 32 bitov v bistvu konstantnih. Spodnjih 32 bitov se lahko spremeni. Nižji kot je bit, pogosteje se lahko spreminja. Zato je ta izvedba posebej optimizirana za to podatkovno strukturo. Implementacija C++ je, kolikor vem, optimizirana za splošni primer. Če optimizirate za splošen primer, to pomeni, da za konkreten primer ne bo najbolj optimalno.

Svetujem vam tudi ogled poročila Alekseja Milovida. Pred približno mesecem dni je govoril o optimizaciji v ClickHouse za določene specializacije. Pove le, da je v splošnem implementacija C++ ali kakšna druga implementacija prilagojena tako, da v povprečju dobro deluje v bolnišnici. Morda deluje slabše kot implementacija, specifična za znanje, kot je naša, kjer vemo, da je zgornjih 32 bitov večinoma konstantnih.

Imam drugo vprašanje. Kakšna je temeljna razlika od InfluxDB?

Obstaja veliko temeljnih razlik. Kar zadeva zmogljivost in porabo pomnilnika, InfluxDB v testih pokaže 10-krat večjo porabo pomnilnika za časovne serije z visoko kardinalnostjo, ko jih imate veliko, na primer na milijone. Na primer, VictoriaMetrics porabi 1 GB na milijon aktivnih vrstic, medtem ko InfluxDB porabi 10 GB. In to je velika razlika.

Druga temeljna razlika je, da ima InfluxDB čudne jezike poizvedb - Flux in InfluxQL. Niso zelo priročni za delo s časovnimi serijami v primerjavi z PromQL, ki ga podpira VictoriaMetrics. PromQL je poizvedovalni jezik podjetja Prometheus.

In še ena razlika je ta, da ima InfluxDB nekoliko nenavaden podatkovni model, kjer lahko vsaka vrstica shrani več polj z drugačnim nizom oznak. Te vrstice so nadalje razdeljene v različne tabele. Ti dodatni zapleti otežujejo nadaljnje delo s to bazo podatkov. Težko je podpirati in razumeti.

V VictoriaMetrics je vse veliko preprostejše. Tam je vsaka časovna serija ključna vrednost. Vrednost je niz točk - (timestamp, value), ključ pa je komplet label=value. Med polji in meritvami ni ločnice. Omogoča vam, da izberete poljubne podatke in nato združite, seštejete, odštejete, pomnožite, delite, za razliko od InfluxDB, kjer izračuni med različnimi vrsticami še vedno niso izvedeni, kolikor vem. Tudi če so implementirani, je težko, napisati je treba veliko kode.

Imam pojasnjevalno vprašanje. Ali sem prav razumel, da je prišlo do nekakšne težave, o kateri ste govorili, da se ta obrnjen indeks ne prilega pomnilniku, zato je tam particioniranje?

Najprej sem pokazal naivno izvedbo obrnjenega indeksa na standardnem zemljevidu Go. Ta izvedba ni primerna za baze podatkov, ker ta obrnjeni indeks ni shranjen na disk, baza podatkov pa se mora shraniti na disk, tako da ti podatki ostanejo na voljo po ponovnem zagonu. V tej izvedbi, ko znova zaženete aplikacijo, bo vaš obrnjeni indeks izginil. In izgubili boste dostop do vseh podatkov, ker jih ne boste mogli najti.

Zdravo! Hvala za poročilo! Moje ime je Pavel. Jaz sem iz Wildberries. Imam nekaj vprašanj za vas. Prvo vprašanje. Ali menite, da bi, če bi izbrali drugačen princip pri gradnji arhitekture vaše aplikacije in razdelili podatke skozi čas, morda lahko presekali podatke pri iskanju samo na podlagi dejstva, da ena particija vsebuje podatke za eno časovnem obdobju , torej v enem časovnem intervalu in vam ne bi bilo treba skrbeti, da so vaši deli različno razpršeni? Vprašanje številka 2 - ker implementirate podoben algoritem z bitsetom in vsem ostalim, ste morda poskusili uporabiti navodila procesorja? Ste morda že poskusili takšne optimizacije?

Na drugo odgovorim takoj. Nismo še prišli do te točke. A če bo treba, pridemo tja. In prvo, kaj je bilo vprašanje?

Razpravljali ste o dveh scenarijih. In rekli so, da so izbrali drugega, ki ima bolj zapleteno izvedbo. In ni jim bil ljubši prvi, kjer so podatki časovno razdeljeni.

ja V prvem primeru bi bil skupni obseg indeksa večji, ker bi morali v vsaki particiji shraniti podvojene podatke za tiste časovne serije, ki se nadaljujejo skozi vse te particije. In če je stopnja opuščanja vaše časovne serije majhna, tj. nenehno se uporabljajo iste serije, potem bi v prvem primeru izgubili veliko več pri količini zasedenega prostora na disku v primerjavi z drugim primerom.

In tako – ja, časovna delitev je dobra možnost. Prometej ga uporablja. Toda Prometej ima še eno pomanjkljivost. Pri združevanju teh kosov podatkov mora v pomnilniku hraniti meta informacije za vse oznake in časovne serije. Če so torej deli podatkov, ki jih združuje, veliki, se poraba pomnilnika med združevanjem zelo poveča, za razliko od VictoriaMetrics. Pri združevanju VictoriaMetrics sploh ne porablja pomnilnika, porabi se le nekaj kilobajtov, ne glede na velikost spojenih podatkov.

Algoritem, ki ga uporabljate, uporablja pomnilnik. Označuje oznake časovnih vrst, ki vsebujejo vrednosti. In na ta način preverite prisotnost parov v enem in drugem podatkovnem nizu. In razumete, ali je do preseka prišlo ali ne. Običajno baze podatkov izvajajo kazalce in iteratorje, ki shranjujejo svojo trenutno vsebino in tečejo skozi razvrščene podatke zaradi preproste zapletenosti teh operacij.

Zakaj ne uporabljamo kazalcev za premikanje po podatkih?

Da.

Razvrščene vrstice shranjujemo v LevelDB ali mergeset. Kazalec lahko premaknemo in poiščemo križišče. Zakaj ga ne uporabimo? Ker je počasen. Ker kazalci pomenijo, da morate za vsako vrstico poklicati funkcijo. Klic funkcije je 5 nanosekund. In če imate 100 vrstic, potem se izkaže, da porabimo pol sekunde samo za klicanje funkcije.

Obstaja kaj takega, ja. In moje zadnje vprašanje. Vprašanje se morda sliši nekoliko čudno. Zakaj ni mogoče prebrati vseh potrebnih agregatov v trenutku, ko podatki prispejo in jih shraniti v zahtevani obliki? Zakaj bi prihranili ogromne količine v nekaterih sistemih, kot so VictoriaMetrics, ClickHouse itd., in potem zanje porabili veliko časa?

Bom dal primer, da bo bolj jasno. Recimo, kako deluje mali merilnik hitrosti igrače? Beleži razdaljo, ki ste jo prepotovali, in jo ves čas dodaja eni vrednosti, drugi pa času. In deli. In dobi povprečno hitrost. Lahko storite približno enako. Sproti seštejte vsa potrebna dejstva.

V redu, razumem vprašanje. Vaš primer ima svoje mesto. Če veste, katere agregate potrebujete, potem je to najboljša izvedba. Toda težava je v tem, da ljudje te metrike, nekatere podatke shranjujejo v ClickHouse in še ne vedo, kako jih bodo v prihodnosti združevali in filtrirali, zato morajo shraniti vse neobdelane podatke. Če pa veste, da morate nekaj izračunati v povprečju, zakaj potem tega ne bi izračunali, namesto da bi tam shranili kup neobdelanih vrednosti? A to je le, če natančno veste, kaj potrebujete.

Mimogrede, baze podatkov za shranjevanje časovnih vrst podpirajo štetje agregatov. Na primer, Prometheus podpira pravila snemanja. Se pravi, to je mogoče storiti, če veste, katere enote boste potrebovali. VictoriaMetrics tega še nima, je pa običajno pred njim Prometheus, v katerem je to mogoče narediti v pravilih za rekodiranje.

Na primer, v prejšnji službi sem moral prešteti število dogodkov v drsečem oknu v zadnji uri. Problem je v tem, da sem moral v Go narediti custom implementacijo, torej storitev za štetje te stvari. Ta storitev je bila na koncu nepomembna, saj jo je težko izračunati. Implementacija je lahko preprosta, če morate nekaj agregatov prešteti v določenih časovnih intervalih. Če želite šteti dogodke v drsečem oknu, potem ni tako preprosto, kot se zdi. Mislim, da to še ni implementirano v ClickHouse ali v baze podatkov časovnih vrst, ker je težko implementirati.

In še eno vprašanje. Ravno smo govorili o povprečenju in spomnil sem se, da je nekoč obstajal Graphite z ogljikovim zaledjem. In znal je redčiti stare podatke, se pravi pustiti eno točko na minuto, eno točko na uro itd. Načeloma je to kar priročno, če rabimo surove podatke, relativno za en mesec, vse ostalo pa lahko biti razredčen . Toda Prometheus in VictoriaMetrics ne podpirata te funkcije. Ali je predvidena podpora? Če ne, zakaj ne?

Hvala za vprašanje. Naši uporabniki občasno postavljajo to vprašanje. Sprašujejo, kdaj bomo dodali podporo za znižanje vzorčenja. Tukaj je več težav. Prvič, vsak uporabnik razume downsampling nekaj drugega: nekdo želi dobiti poljubno točko na danem intervalu, nekdo želi maksimalne, minimalne, povprečne vrednosti. Če veliko sistemov zapisuje podatke v vašo zbirko podatkov, potem ne morete združiti vseh skupaj. Lahko se zgodi, da vsak sistem zahteva drugačno redčenje. In to je težko izvedljivo.

In druga stvar je, da je VictoriaMetrics, tako kot ClickHouse, optimiziran za delo z velikimi količinami neobdelanih podatkov, tako da lahko v manj kot sekundi odstrani milijardo vrstic, če imate v sistemu veliko jeder. Skeniranje točk časovne serije v VictoriaMetrics – 50 točk na sekundo na jedro. In ta zmogljivost se prilagaja obstoječim jedrom. Se pravi, če imate na primer 000 jeder, boste skenirali milijardo točk na sekundo. In ta lastnost VictoriaMetrics in ClickHouse zmanjšuje potrebo po zniževanju vzorcev.

Druga značilnost je, da VictoriaMetrics te podatke učinkovito stisne. Stiskanje v produkciji je v povprečju od 0,4 do 0,8 bajtov na točko. Vsaka točka je časovni žig + vrednost. In v povprečju je stisnjen v manj kot en bajt.

Sergej. Imam vprašanje. Kolikšen je minimalni kvantum časa snemanja?

Ena milisekunda. Nedavno smo se pogovarjali z drugimi razvijalci baz podatkov časovnih vrst. Njihov minimalni časovni odrez je ena sekunda. In na primer v Graphite je tudi ena sekunda. V OpenTSDB je tudi ena sekunda. InfluxDB ima nanosekundno natančnost. V VictoriaMetrics je ena milisekunda, ker je v Prometheusu ena milisekunda. In VictoriaMetrics je bil prvotno razvit kot oddaljena shramba za Prometheus. Zdaj pa lahko shranjuje podatke iz drugih sistemov.

Oseba, s katero sem govoril, pravi, da imajo natančnost od sekunde do sekunde – to jim zadošča, ker je odvisno od vrste podatkov, ki so shranjeni v zbirki časovnih vrst. Če so to podatki DevOps ali podatki iz infrastrukture, kjer jih zbirate v intervalih 30 sekund, na minuto, je sekundna natančnost dovolj, nič manj ne potrebujete. In če te podatke zbirate iz visokofrekvenčnih trgovalnih sistemov, potem potrebujete nanosekundno natančnost.

Milisekundna natančnost v VictoriaMetrics je primerna tudi za primer DevOps in je lahko primerna za večino primerov, ki sem jih omenil na začetku poročila. Edina stvar, za katero morda ni primeren, so sistemi visokofrekvenčnega trgovanja.

Hvala vam! In še eno vprašanje. Kaj je združljivost v PromQL?

Popolna združljivost za nazaj. VictoriaMetrics v celoti podpira PromQL. Poleg tega dodaja dodatno napredno funkcionalnost v PromQL, ki se imenuje MetricsQL. Na YouTubu se govori o tej razširjeni funkcionalnosti. Spomladi sem govoril na Monitoring Meetupu v St.

Telegram kanal VictoriaMetrics.

V anketi lahko sodelujejo samo registrirani uporabniki. Prijaviti se, prosim.

Kaj vam preprečuje, da bi preklopili na VictoriaMetrics kot svojo dolgoročno shrambo za Prometheus? (Napiši v komentarje, dodam v anketo))

  • 71,4%Prometheus5 ne uporabljam

  • 28,6%Nisem vedel za VictoriaMetrics2

Glasovalo je 7 uporabnikov. 12 uporabnikov se je vzdržalo.

Vir: www.habr.com

Dodaj komentar