Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Predlažem da pročitate transkript izvještaja Alexandera Valyalkina s kraja 2019. godine „Idite optimizacije u VictoriaMetrics“

VictoriaMetrics — brz i skalabilan DBMS za pohranjivanje i obradu podataka u obliku vremenske serije (zapis formira vrijeme i skup vrijednosti koje odgovaraju ovom vremenu, na primjer, dobijene periodičnim ispitivanjem statusa senzora ili prikupljanjem metrika).

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Evo linka do videa ovog izvještaja - https://youtu.be/MZ5P21j_HLE

Slajdovi

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Reci nam nešto o sebi. Ja sam Alexander Valyalkin. Evo moj GitHub nalog. Ja sam strastven za Go i optimizaciju performansi. Napisao sam mnogo korisnih i ne baš korisnih biblioteka. Počinju sa bilo kojim fast, ili sa quick prefiks.

Trenutno radim na VictoriaMetrics. Šta je to i šta ja radim tamo? O tome ću govoriti u ovoj prezentaciji.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pregled izvještaja je sljedeći:

  • Prvo ću vam reći šta je VictoriaMetrics.
  • Onda ću vam reći šta su vremenske serije.
  • Zatim ću vam reći kako funkcioniše baza podataka vremenskih serija.
  • Zatim ću vam reći o arhitekturi baze podataka: od čega se sastoji.
  • A onda idemo na optimizacije koje VictoriaMetrics ima. Ovo je optimizacija za obrnuti indeks i optimizacija za implementaciju skupa bitova u Go.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Da li neko u publici zna šta je VictoriaMetrics? Vau, mnogi ljudi već znaju. To je dobra vijest. Za one koji ne znaju, ovo je baza podataka vremenskih serija. Zasnovan je na ClickHouse arhitekturi, na nekim detaljima implementacije ClickHouse. Na primjer, na kao što su: MergeTree, paralelno izračunavanje na svim dostupnim procesorskim jezgrama i optimizacija performansi radom na blokovima podataka koji se stavljaju u keš procesora.

VictoriaMetrics pruža bolju kompresiju podataka od drugih baza podataka vremenskih serija.

Skalira se vertikalno - to jest, možete dodati više procesora, više RAM-a na jednom računaru. VictoriaMetrics će uspješno koristiti ove dostupne resurse i poboljšati linearnu produktivnost.

VictoriaMetrics se također skalira horizontalno - to jest, možete dodati dodatne čvorove VictoriaMetrics klasteru, a njegove performanse će se povećati gotovo linearno.

Kao što ste pretpostavili, VictoriaMetrics je brza baza podataka, jer ne mogu pisati druge. I to je napisano u Go, tako da govorim o tome na ovom susretu.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Ko zna šta je vremenska serija? On takođe poznaje mnogo ljudi. Vremenska serija je niz parova (timestamp, значение), gdje su ovi parovi sortirani po vremenu. Vrijednost je broj s pomičnim zarezom – float64.

Svaka vremenska serija je jedinstveno identificirana ključem. Od čega se sastoji ovaj ključ? Sastoji se od nepraznog skupa parova ključ/vrijednost.

Evo primjera vremenske serije. Ključ ove serije je lista parova: __name__="cpu_usage" je naziv metrike, instance="my-server" - ovo je računar na kojem se prikuplja ova metrika, datacenter="us-east" - ovo je data centar u kojem se nalazi ovaj računar.

Na kraju smo dobili naziv vremenske serije koji se sastoji od tri para ključ/vrijednost. Ovaj ključ odgovara listi parova (timestamp, value). t1, t3, t3, ..., tN - ovo su vremenske oznake, 10, 20, 12, ..., 15 — odgovarajuće vrijednosti. Ovo je upotreba procesora u datom trenutku za datu seriju.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Gdje se mogu koristiti vremenske serije? Ima li neko ideju?

  • U DevOps-u možete mjeriti CPU, RAM, mrežu, rps, broj grešaka itd.
  • IoT - možemo mjeriti temperaturu, pritisak, geo koordinate i još nešto.
  • Također i finansije – možemo pratiti cijene za sve vrste akcija i valuta.
  • Pored toga, vremenske serije se mogu koristiti u praćenju proizvodnih procesa u fabrikama. Imamo korisnike koji koriste VictoriaMetrics za praćenje vjetroturbina, za robote.
  • Vremenske serije su takođe korisne za prikupljanje informacija sa senzora različitih uređaja. Na primjer, za motor; za merenje pritiska u gumama; za mjerenje brzine, udaljenosti; za mjerenje potrošnje benzina itd.
  • Vremenske serije se takođe mogu koristiti za praćenje aviona. Svaki avion ima crnu kutiju koja prikuplja vremenske serije za različite parametre zdravstvenog stanja aviona. Vremenske serije se takođe koriste u vazduhoplovnoj industriji.
  • Zdravstvena zaštita je krvni pritisak, puls itd.

Možda postoji još aplikacija na koje sam zaboravio, ali nadam se da razumijete da se vremenske serije aktivno koriste u modernom svijetu. A obim njihove upotrebe raste svake godine.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Zašto vam je potrebna baza podataka vremenskih serija? Zašto ne možete koristiti redovnu relacionu bazu podataka za pohranjivanje vremenskih serija?

Jer vremenske serije obično sadrže veliku količinu informacija, koje je teško pohraniti i obraditi u konvencionalnim bazama podataka. Stoga su se pojavile specijalizirane baze podataka za vremenske serije. Ove baze efikasno čuvaju bodove (timestamp, value) sa datim ključem. Oni pružaju API za čitanje pohranjenih podataka po ključu, po jednom paru ključ-vrijednost, ili po više parova ključ-vrijednost, ili po redovnom izrazu. Na primjer, želite pronaći CPU opterećenje svih vaših usluga u podatkovnom centru u Americi, tada trebate koristiti ovaj pseudo-upit.

Obično baze podataka vremenskih serija pružaju specijalizirane jezike upita jer vremenski niz SQL nije baš pogodan. Iako postoje baze podataka koje podržavaju SQL, on nije baš prikladan. Jezici upita kao što su PromQL, InfluxQL, Tok, Q. Nadam se da je neko čuo barem jedan od ovih jezika. Mnogi ljudi su vjerovatno čuli za PromQL. Ovo je Prometheusov jezik upita.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Ovako izgleda moderna arhitektura baze podataka vremenskih serija koristeći VictoriaMetrics kao primjer.

Sastoji se iz dva dijela. Ovo je skladište za obrnuti indeks i skladište za vrijednosti vremenskih serija. Ova spremišta su odvojena.

Kada novi zapis stigne u bazu podataka, prvo pristupamo invertiranom indeksu da pronađemo identifikator vremenske serije za dati skup label=value za datu metriku. Pronalazimo ovaj identifikator i čuvamo vrijednost u spremištu podataka.

Kada dođe zahtjev za preuzimanje podataka iz TSDB-a, prvo idemo na obrnuti indeks. Hajde da uzmemo sve timeseries_ids zapisi koji odgovaraju ovom skupu label=value. A onda dobijamo sve potrebne podatke iz skladišta podataka, indeksirane od strane timeseries_ids.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pogledajmo primjer kako baza podataka vremenske serije obrađuje dolazni upit za odabir.

  • Prije svega, ona dobija sve timeseries_ids iz obrnutog indeksa koji sadrži date parove label=value, ili zadovoljiti dati regularni izraz.
  • Zatim preuzima sve tačke podataka iz skladišta podataka u datom vremenskom intervalu za pronađene timeseries_ids.
  • Nakon toga, baza podataka vrši neke kalkulacije na tim tačkama podataka, prema zahtjevu korisnika. I nakon toga vraća odgovor.

U ovoj prezentaciji ću vam reći o prvom dijelu. Ovo je pretraga timeseries_ids po obrnutom indeksu. O drugom i trećem dijelu možete pogledati kasnije VictoriaMetrics izvori, ili sacekajte da pripremim druge reportaze :)

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pređimo na obrnuti indeks. Mnogi možda misle da je ovo jednostavno. Ko zna šta je obrnuti indeks i kako funkcioniše? Oh, nema više toliko ljudi. Hajde da pokušamo da shvatimo šta je to.

To je zapravo jednostavno. To je jednostavno rečnik koji preslikava ključ na vrednost. Šta je ključ? Ovaj par label=valuegde label и value - ovo su linije. A vrijednosti su skup timeseries_ids, koji uključuje dati par label=value.

Obrnuti indeks vam omogućava da brzo pronađete sve timeseries_ids, koji su dali label=value.

Takođe vam omogućava da brzo pronađete timeseries_ids vremenske serije za nekoliko parova label=value, ili za parove label=regexp. Kako se to događa? Pronalaženjem presjeka skupa timeseries_ids za svaki par label=value.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pogledajmo različite implementacije invertovanog indeksa. Počnimo s najjednostavnijom naivnom implementacijom. Ona izgleda ovako.

funkcija getMetricIDs dobija listu stringova. Svaki red sadrži label=value. Ova funkcija vraća listu metricIDs.

Kako radi? Ovdje imamo globalnu varijablu pod nazivom invertedIndex. Ovo je običan rečnik (map), koji će mapirati niz u isječke ints. Linija sadrži label=value.

Implementacija funkcije: get metricIDs za prvi label=value, onda idemo kroz sve ostalo label=value, shvatili smo metricIDs za njih. I pozovite funkciju intersectInts, o čemu će biti riječi u nastavku. I ova funkcija vraća sjecište ovih lista.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Kao što vidite, implementacija obrnutog indeksa nije mnogo komplikovana. Ali ovo je naivna implementacija. Koje nedostatke ima? Glavni nedostatak naivne implementacije je taj što se takav obrnuti indeks pohranjuje u RAM-u. Nakon ponovnog pokretanja aplikacije gubimo ovaj indeks. Nema pohranjivanja ovog indeksa na disk. Takav obrnuti indeks vjerovatno neće biti prikladan za bazu podataka.

Drugi nedostatak se također odnosi na memoriju. Obrnuti indeks mora stati u RAM. Ako premašuje veličinu RAM-a, očito ćemo dobiti grešku iz memorije. I program neće raditi.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Ovaj problem se može riješiti korištenjem gotovih rješenja kao npr LevelDB, ili RocksDB.

Ukratko, potrebna nam je baza podataka koja nam omogućava da brzo obavimo tri operacije.

  • Prva operacija je snimanje ключ-значение ovoj bazi podataka. Ona to radi veoma brzo, gde ключ-значение su proizvoljni nizovi.
  • Druga operacija je brza pretraga vrijednosti pomoću datog ključa.
  • I treća operacija je brza pretraga svih vrijednosti po datom prefiksu.

LevelDB i RocksDB - ove baze podataka su razvili Google i Facebook. Prvi je došao LevelDB. Onda su momci iz Facebooka uzeli LevelDB i počeli da ga poboljšavaju, napravili su RocksDB. Sada skoro sve interne baze podataka rade na RocksDB unutar Facebooka, uključujući i one koje su prebačene u RocksDB i MySQL. Dali su mu ime MyRocks.

Obrnuti indeks se može implementirati pomoću LevelDB. Kako uraditi? Čuvamo kao ključ label=value. A vrijednost je identifikator vremenske serije u kojoj je par prisutan label=value.

Ako imamo mnogo vremenskih serija sa datim parom label=value, tada će u ovoj bazi podataka biti mnogo redova sa istim ključem i različitim timeseries_ids. Da dobijem listu svih timeseries_ids, koji počinju ovim label=prefix, vršimo skeniranje opsega za koje je ova baza podataka optimizirana. Odnosno, biramo sve redove koji počinju label=prefix i nabavite potrebno timeseries_ids.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Evo primjera implementacije kako bi to izgledalo u Go. Imamo obrnuti indeks. Ovo je LevelDB.

Funkcija je ista kao i za naivnu implementaciju. Ponavlja naivnu implementaciju gotovo red po red. Jedina stvar je da umjesto da se okrenemo map pristupamo obrnutom indeksu. Dobijamo sve vrijednosti za prvi label=value. Zatim prolazimo kroz sve preostale parove label=value i dobijete odgovarajuće skupove metričkih ID-ova za njih. Zatim nalazimo raskrsnicu.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Čini se da je sve u redu, ali ovo rješenje ima nedostataka. VictoriaMetrics je u početku implementirao obrnuti indeks zasnovan na LevelDB. Ali na kraju sam morao odustati od toga.

Zašto? Zato što je LevelDB sporiji od naivne implementacije. U naivnoj implementaciji, s danim ključem, odmah preuzimamo cijeli isječak metricIDs. Ovo je vrlo brza operacija - cijeli komad je spreman za upotrebu.

U LevelDB, svaki put kada se pozove funkcija GetValues morate proći kroz sve redove koji počinju label=value. I dobijte vrijednost za svaki red timeseries_ids. Od takvih timeseries_ids sakupi parče ovih timeseries_ids. Očigledno, ovo je mnogo sporije od jednostavnog pristupa običnoj mapi pomoću ključa.

Drugi nedostatak je što je LevelDB napisan u C. Pozivanje C funkcija iz Go nije jako brzo. Potrebno je stotine nanosekundi. Ovo nije jako brzo, jer u poređenju sa običnim pozivom funkcije napisanim u go, koji traje 1-5 nanosekundi, razlika u performansama je desetine puta. Za VictoriaMetrics ovo je bila fatalna mana :)

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Tako sam napisao sopstvenu implementaciju obrnutog indeksa. I pozvao ju je mergeset.

Mergeset je baziran na strukturi podataka MergeTree. Ova struktura podataka je posuđena od ClickHouse. Očigledno, mergeset bi trebao biti optimiziran za brzo pretraživanje timeseries_ids prema zadatom ključu. Mergeset je u potpunosti napisan u Go. Mozes da vidis VictoriaMetrics izvori na GitHubu. Implementacija mergeseta je u folderu /lib/mergeset. Možete pokušati da shvatite šta se tamo dešava.

API za spajanje je vrlo sličan LevelDB i RocksDB. Odnosno, omogućava vam da brzo spremite nove zapise tamo i brzo odaberete zapise prema datom prefiksu.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Kasnije ćemo pričati o nedostacima mergeseta. Sada razgovarajmo o tome koji su problemi nastali s VictoriaMetrics u proizvodnji prilikom implementacije obrnutog indeksa.

Zašto su nastali?

Prvi razlog je visoka stopa odliva. Prevedeno na ruski, ovo je česta promjena u vremenskim serijama. Ovo je kada se vremenska serija završava i počinje nova serija ili počinje mnogo novih vremenskih serija. I to se često dešava.

Drugi razlog je veliki broj vremenskih serija. U početku, kada je praćenje postajalo sve popularnije, broj vremenskih serija je bio mali. Na primjer, za svaki računar morate pratiti CPU, memoriju, mrežu i opterećenje diska. 4 vremenske serije po računaru. Recimo da imate 100 računara i 400 vremenskih serija. Ovo je jako malo.

S vremenom su ljudi shvatili da mogu mjeriti detaljnije informacije. Na primjer, izmjerite opterećenje ne cijelog procesora, već posebno svake jezgre procesora. Ako imate 40 procesorskih jezgri, onda imate 40 puta više vremenskih serija za mjerenje opterećenja procesora.

Ali to nije sve. Svako jezgro procesora može imati nekoliko stanja, kao što je mirovanje, kada je neaktivno. I također rad u korisničkom prostoru, rad u prostoru kernela i drugim stanjima. A svako takvo stanje se može mjeriti i kao posebna vremenska serija. Ovo dodatno povećava broj redova za 7-8 puta.

Iz jedne metrike dobili smo 40 x 8 = 320 metrika za samo jedan računar. Pomnožimo sa 100, dobićemo 32 umjesto 000.

Onda je došao Kubernetes. I postalo je gore jer Kubernetes može ugostiti mnogo različitih servisa. Svaka usluga u Kubernetesu sastoji se od mnogo podova. I sve to treba pratiti. Osim toga, imamo stalnu implementaciju novih verzija vaših usluga. Za svaku novu verziju potrebno je kreirati nove vremenske serije. Kao rezultat toga, broj vremenskih serija raste eksponencijalno i suočavamo se s problemom velikog broja vremenskih serija, koji se naziva visokom kardinalnošću. VictoriaMetrics se s tim uspješno nosi u poređenju sa drugim bazama podataka vremenskih serija.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pogledajmo pobliže visoku stopu odljeva. Šta uzrokuje visoku stopu odustajanja u proizvodnji? Zato što se neka značenja oznaka i oznaka stalno mijenjaju.

Na primjer, uzmite Kubernetes, koji ima koncept deployment, tj. kada se pojavi nova verzija vaše aplikacije. Iz nekog razloga, programeri Kubernetesa odlučili su dodati ID implementacije na oznaku.

Do čega je to dovelo? Štaviše, sa svakim novim uvođenjem, sve stare vremenske serije se prekidaju, a umjesto njih počinju nove vremenske serije s novom vrijednošću oznake deployment_id. Takvih redova može biti stotine hiljada, pa čak i milioni.

Ono što je važno u svemu ovome je da ukupan broj vremenskih serija raste, ali broj vremenskih serija koje su trenutno aktivne i koje primaju podatke ostaje konstantan. Ovo stanje se naziva visoka stopa odliva.

Glavni problem visoke stope odljeva je osigurati konstantnu brzinu pretraživanja za sve vremenske serije za dati skup oznaka u određenom vremenskom intervalu. Obično je ovo vremenski interval za posljednji sat ili posljednji dan.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Kako riješiti ovaj problem? Evo prve opcije. Ovo je da se obrnuti indeks podijeli na nezavisne dijelove tokom vremena. Odnosno, prođe neki vremenski interval, završavamo rad sa trenutnim obrnutim indeksom. I kreirajte novi obrnuti indeks. Prolazi još jedan vremenski interval, stvaramo još jedan i još jedan.

I kada uzorkujemo iz ovih invertiranih indeksa, nalazimo skup obrnutih indeksa koji spadaju u zadani interval. I, shodno tome, odatle biramo id vremenske serije.

Ovo štedi resurse jer ne moramo gledati dijelove koji ne spadaju u zadani interval. Odnosno, obično, ako odaberemo podatke za zadnji sat, onda za prethodne vremenske intervale preskačemo zahtjeve.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Postoji još jedna opcija za rješavanje ovog problema. Ovo je da se za svaki dan pohrani posebna lista ID-ova vremenskih serija koje su se dogodile tog dana.

Prednost ovog rješenja u odnosu na prethodno rješenje je u tome što ne dupliramo informacije o vremenskim serijama koje ne nestaju tokom vremena. Oni su stalno prisutni i ne mijenjaju se.

Nedostatak je što je takvo rješenje teže implementirati i teže otkloniti greške. VictoriaMetrics je odabrala ovo rješenje. Ovako se to istorijski desilo. Ovo rješenje također ima dobre rezultate u odnosu na prethodno. Jer ovo rješenje nije implementirano zbog činjenice da je potrebno duplirati podatke u svakoj particiji za vremenske serije koje se ne mijenjaju, odnosno koje ne nestaju tokom vremena. VictoriaMetrics je prvenstveno optimiziran za potrošnju prostora na disku, a prethodna implementacija je pogoršala potrošnju prostora na disku. Ali ova implementacija je prikladnija za minimiziranje potrošnje prostora na disku, pa je odabrana.

Morao sam da se borim sa njom. Borba je bila u tome što u ovoj implementaciji još uvijek morate odabrati mnogo veći broj timeseries_ids za podatke nego kada je obrnuti indeks vremenski particioniran.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Kako smo riješili ovaj problem? Riješili smo to na originalan način - pohranjivanjem nekoliko identifikatora vremenskih serija u svaki invertirani indeksni unos umjesto jednog identifikatora. To jest, imamo ključ label=value, koji se javlja u svakoj vremenskoj seriji. A sada spašavamo nekoliko timeseries_ids u jednom unosu.

Evo primjera. Ranije smo imali N unosa, ali sada imamo jedan unos čiji je prefiks isti kao i svi ostali. Za prethodni unos, vrijednost sadrži ID-ove svih vremenskih serija.

To je omogućilo povećanje brzine skeniranja takvog invertiranog indeksa do 10 puta. I to nam je omogućilo da smanjimo potrošnju memorije za keš, jer sada pohranjujemo string label=value samo jednom u kešu zajedno N puta. A ovaj red može biti velik ako pohranite dugačke redove u svoje oznake i etikete, koje Kubernetes tamo voli ugurati.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Još jedna opcija za ubrzavanje pretraživanja na obrnutom indeksu je razbijanje. Kreiranje nekoliko invertiranih indeksa umjesto jednog i dijeljenje podataka između njih po ključu. Ovo je set key=value pare. Odnosno, dobijamo nekoliko nezavisnih invertiranih indeksa, koje možemo paralelno ispitivati ​​na nekoliko procesora. Prethodne implementacije su dozvoljavale rad samo u jednoprocesorskom režimu, tj. skeniranje podataka samo na jednoj jezgri. Ovo rješenje vam omogućava da skenirate podatke na nekoliko jezgri odjednom, kao što ClickHouse voli da radi. To je ono što planiramo implementirati.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Vratimo se sada na našu ovcu - na funkciju raskrsnice timeseries_ids. Hajde da razmotrimo koje implementacije mogu postojati. Ova funkcija vam omogućava da pronađete timeseries_ids za dati skup label=value.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Prva opcija je naivna implementacija. Dvije ugniježđene petlje. Ovdje dobijamo ulaz funkcije intersectInts dvije kriške - a и b. Na izlazu bi nam trebao vratiti sjecište ovih isječaka.

Naivna implementacija izgleda ovako. Iteriramo sve vrijednosti iz slice-a a, unutar ove petlje prolazimo kroz sve vrijednosti slice b. I poredimo ih. Ako se poklapaju, onda smo pronašli raskrsnicu. I sačuvaj ga result.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Koji su nedostaci? Kvadratna složenost je njegov glavni nedostatak. Na primjer, ako su vaše dimenzije isječene a и b milion po milion, onda vam ova funkcija nikada neće vratiti odgovor. Zato što će morati da napravi trilion iteracija, što je mnogo čak i za moderne računare.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Druga implementacija je bazirana na karti. Izrađujemo mapu. Sve vrijednosti iz sreza stavljamo u ovu mapu a. Zatim prolazimo kroz kriške u zasebnoj petlji b. I provjeravamo da li je ova vrijednost iz sreza b u mapi. Ako postoji, dodajte ga rezultatu.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Koje su prednosti? Prednost je što postoji samo linearna složenost. To jest, funkcija će se izvršavati mnogo brže za veće rezove. Za isečak veličine milion, ova funkcija će se izvršiti u 2 miliona iteracija, za razliku od triliona iteracija prethodne funkcije.

Nedostatak je što ova funkcija zahtijeva više memorije za kreiranje ove karte.

Drugi nedostatak su veliki troškovi za heširanje. Ovaj nedostatak nije baš očigledan. A za nas to također nije bilo baš očigledno, tako da je u VictoriaMetrics u početku implementacija raskrsnice bila preko karte. Ali tada je profiliranje pokazalo da se vrijeme glavnog procesora troši na pisanje na kartu i provjeru prisutnosti vrijednosti u ovoj mapi.

Zašto se na ovim mjestima gubi CPU vrijeme? Jer Go izvodi operaciju heširanja na ovim linijama. To jest, izračunava heš ključa kako bi mu zatim pristupio na datom indeksu u HashMap-u. Operacija izračunavanja heša se završava za desetine nanosekundi. Ovo je sporo za VictoriaMetrics.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Odlučio sam implementirati bitset optimiziran posebno za ovaj slučaj. Ovako sada izgleda presek dva dela. Ovdje kreiramo bitset. U njega dodajemo elemente iz prvog preseka. Zatim provjeravamo prisustvo ovih elemenata u drugom krišku. I dodajte ih rezultatu. Odnosno, gotovo se ne razlikuje od prethodnog primjera. Jedina stvar ovdje je da smo pristup mapi zamijenili prilagođenim funkcijama add и has.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Na prvi pogled se čini da bi ovo trebalo da radi sporije, ako se ranije tu koristila standardna mapa, a zatim se pozivaju neke druge funkcije, ali profilisanje pokazuje da ova stvar radi 10 puta brže od standardne mape u slučaju VictoriaMetrics.

Osim toga, koristi mnogo manje memorije u odnosu na implementaciju mape. Zato što ovdje pohranjujemo bitove umjesto vrijednosti od osam bajtova.

Nedostatak ove implementacije je u tome što nije tako očigledna, niti trivijalna.

Još jedan nedostatak koji mnogi možda ne primjećuju je da ova implementacija možda neće dobro funkcionirati u nekim slučajevima. Odnosno, optimizovan je za konkretan slučaj, za ovaj slučaj preseka ID-ova vremenskih serija VictoriaMetrics. To ne znači da je pogodan za sve slučajeve. Ako se koristi pogrešno, nećemo dobiti povećanje performansi, već grešku u ponestaje memorije i usporavanje performansi.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Razmotrimo implementaciju ove strukture. Ako želite da pogledate, nalazi se u VictoriaMetrics izvorima, u fascikli lib/uint64set. Optimiziran je posebno za VictoriaMetrics slučaj, gdje timeseries_id je 64-bitna vrijednost, gdje su prva 32 bita u osnovi konstantna, a samo posljednja 32 bita se mijenjaju.

Ova struktura podataka nije pohranjena na disku, ona radi samo u memoriji.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Evo njegovog API-ja. Nije mnogo komplikovano. API je posebno prilagođen specifičnom primjeru korištenja VictoriaMetrics. Odnosno, ovdje nema nepotrebnih funkcija. Evo funkcija koje VictoriaMetrics eksplicitno koristi.

Postoje funkcije add, što dodaje nove vrijednosti. Postoji funkcija has, koji provjerava nove vrijednosti. I postoji funkcija del, koji uklanja vrijednosti. Postoji pomoćna funkcija len, koji vraća veličinu skupa. Funkcija clone mnogo klonova. I funkcija appendto pretvara ovaj skup u rez timeseries_ids.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Ovako izgleda implementacija ove strukture podataka. set ima dva elementa:

  • ItemsCount je pomoćno polje za brzo vraćanje broja elemenata u skupu. Bilo bi moguće bez ovog pomoćnog polja, ali moralo se dodati ovdje jer VictoriaMetrics često u svojim algoritmima ispituje dužinu bitova.

  • Drugo polje je buckets. Ovo je dio strukture bucket32. Svaka struktura skladišti hi polje. Ovo su gornja 32 bita. I dve kriške - b16his и buckets из bucket16 strukture.

Ovdje je pohranjeno 16 gornjih bitova drugog dijela 64-bitne strukture. I ovdje se skupovi bitova pohranjuju za nižih 16 bitova svakog bajta.

Bucket64 sastoji se od niza uint64. Dužina se izračunava pomoću ovih konstanti. U jednom bucket16 maksimalno se može pohraniti 2^16=65536 bit. Ako ovo podijelite sa 8, onda je 8 kilobajta. Ako ponovo podijelite sa 8, to je 1000 uint64 značenje. To je Bucket16 – ovo je naša struktura od 8 kilobajta.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pogledajmo kako se implementira jedna od metoda ove strukture za dodavanje nove vrijednosti.

Sve počinje sa uint64 značenja. Izračunavamo gornja 32 bita, izračunavamo donja 32 bita. Hajdemo kroz sve buckets. Upoređujemo gornja 32 bita u svakom segmentu s dodanom vrijednošću. A ako se podudaraju, pozivamo funkciju add u strukturi b32 buckets. I dodajte niža 32 bita tamo. I ako se vrati true, onda to znači da smo tu dodali takvu vrijednost, a nismo imali takvu vrijednost. Ako se vrati false, onda je takvo značenje već postojalo. Zatim povećavamo broj elemenata u strukturi.

Ako nismo pronašli onu koja vam je potrebna bucket sa traženom visokom vrijednošću, tada pozivamo funkciju addAlloc, koji će proizvesti novu bucket, dodajući ga strukturi bucket-a.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Ovo je implementacija funkcije b32.add. Slično je prethodnoj implementaciji. Izračunavamo najznačajnijih 16 bita, najmanje značajnih 16 bita.

Zatim prolazimo kroz svih gornjih 16 bitova. Pronalazimo podudaranja. A ako postoji podudaranje, pozivamo metodu add, koju ćemo razmotriti na sljedećoj stranici bucket16.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

A evo i najnižeg nivoa, koji treba optimizirati što je više moguće. Računamo za uint64 id vrijednost u bitu slice i također bitmask. Ovo je maska ​​za datu 64-bitnu vrijednost, koja se može koristiti za provjeru prisutnosti ovog bita ili za postavljanje. Provjeravamo da li je ovaj bit postavljen i postavljamo ga i vraćamo prisutnost. Ovo je naša implementacija, koja nam je omogućila da ubrzamo rad preseka id-ova vremenskih serija za 10 puta u odnosu na konvencionalne karte.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Pored ove optimizacije, VictoriaMetrics ima mnogo drugih optimizacija. Većina ovih optimizacija dodata je s razlogom, ali nakon profiliranja koda u produkciji.

Ovo je glavno pravilo optimizacije - nemojte dodavati optimizaciju pod pretpostavkom da će ovdje biti usko grlo, jer se može ispostaviti da tu neće biti usko grlo. Optimizacija obično degradira kvalitet koda. Stoga vrijedi optimizirati tek nakon profiliranja i po mogućnosti u proizvodnji, tako da su to stvarni podaci. Ako je neko zainteresiran, može pogledati VictoriaMetrics izvorni kod i istražiti druge optimizacije koje postoje.

Idi optimizacije u VictoriaMetrics. Alexander Valyalkin

Imam pitanje o bitsetu. Vrlo sličan C++ vektorskoj implementaciji boola, optimiziran bitset. Da li ste odatle preuzeli implementaciju?

Ne, ne odatle. Prilikom implementacije ovog bitseta, vodio sam se znanjem o strukturi ovih ID-ova vremenskih serija, koje se koriste u VictoriaMetrics. A njihova struktura je takva da su gornja 32 bita u osnovi konstantna. Donja 32 bita su podložna promjenama. Što je bit niži, to se češće može mijenjati. Stoga je ova implementacija posebno optimizirana za ovu strukturu podataka. Implementacija C++-a, koliko ja znam, je optimizovana za opšti slučaj. Ako optimizirate za opći slučaj, to znači da neće biti najoptimalniji za određeni slučaj.

Takođe vam savetujem da pogledate izveštaj Alekseja Milovida. Prije otprilike mjesec dana govorio je o optimizaciji u ClickHouseu za određene specijalizacije. On samo kaže da je u opštem slučaju implementacija C++ ili neka druga implementacija skrojena da u prosjeku dobro funkcionira u bolnici. Može raditi lošije od implementacije specifične za znanje poput naše, gdje znamo da su gornja 32 bita uglavnom konstantna.

Imam drugo pitanje. Koja je suštinska razlika od InfluxDB-a?

Postoje mnoge fundamentalne razlike. Što se tiče performansi i potrošnje memorije, InfluxDB u testovima pokazuje 10 puta veću potrošnju memorije za vremenske serije visoke kardinalnosti, kada ih imate puno, na primjer, milione. Na primjer, VictoriaMetrics troši 1 GB na milion aktivnih redova, dok InfluxDB troši 10 GB. I to je velika razlika.

Druga fundamentalna razlika je u tome što InfluxDB ima čudne jezike upita - Flux i InfluxQL. Nisu baš zgodne za rad sa vremenskim serijama u poređenju sa PromQL, koju podržava VictoriaMetrics. PromQL je jezik upita iz Prometheusa.

I još jedna razlika je u tome što InfluxDB ima pomalo čudan model podataka, gdje svaka linija može pohraniti nekoliko polja s različitim skupom oznaka. Ovi redovi su dalje podijeljeni u različite tabele. Ove dodatne komplikacije komplikuju kasniji rad sa ovom bazom podataka. Teško je to podržati i razumjeti.

U VictoriaMetrics je sve mnogo jednostavnije. Tu je svaka vremenska serija ključ/vrijednost. Vrijednost je skup bodova - (timestamp, value), a ključ je set label=value. Nema razdvajanja između polja i mjerenja. Omogućava vam da odaberete bilo koji podatak, a zatim kombinujete, dodajete, oduzimate, množite, dijelite, za razliku od InfluxDB-a gdje se proračuni između različitih redova još uvijek ne implementiraju koliko ja znam. Čak i ako se implementiraju, teško je, morate napisati puno koda.

Imam pitanje koje razjašnjava. Da li sam dobro shvatio da je postojao neki problem o kojem ste govorili, da se ovaj invertirani indeks ne uklapa u memoriju, pa postoji particioniranje?

Prvo sam pokazao naivnu implementaciju obrnutog indeksa na standardnoj Go mapi. Ova implementacija nije prikladna za baze podataka jer se ovaj invertirani indeks ne pohranjuje na disk, a baza podataka se mora spremiti na disk tako da ovi podaci ostanu dostupni nakon ponovnog pokretanja. U ovoj implementaciji, kada ponovo pokrenete aplikaciju, vaš obrnuti indeks će nestati. I izgubit ćete pristup svim podacima jer ih nećete moći pronaći.

Zdravo! Hvala na izvještaju! Moje ime je Pavel. Ja sam iz Wildberriesa. Imam nekoliko pitanja za vas. Prvo pitanje. Mislite li da biste, da ste odabrali drugačiji princip prilikom izgradnje arhitekture vaše aplikacije i particionirali podatke tokom vremena, možda biste mogli ukrštati podatke prilikom pretraživanja, samo na osnovu činjenice da jedna particija sadrži podatke za jednu vremenskom periodu, odnosno u jednom vremenskom intervalu i ne biste morali da brinete o činjenici da su vam komadi različito razbacani? Pitanje broj 2 - pošto implementirate sličan algoritam sa bitsetom i svim ostalim, onda ste možda pokušali koristiti instrukcije procesora? Možda ste probali takve optimizacije?

Na drugu ću odmah odgovoriti. Nismo još stigli do te tačke. Ali ako bude potrebno, stići ćemo tamo. A prvo, koje je bilo pitanje?

Razgovarali ste o dva scenarija. I rekli su da su odabrali drugu sa složenijom implementacijom. I nisu preferirali prvi, gdje su podaci raspoređeni po vremenu.

Da. U prvom slučaju, ukupan volumen indeksa bi bio veći, jer bismo u svakoj particiji morali pohraniti duple podatke za one vremenske serije koje se nastavljaju kroz sve te particije. A ako je vaša stopa odljeva vremenskih serija mala, odnosno konstantno se koriste iste serije, onda bismo u prvom slučaju izgubili mnogo više u količini zauzetog prostora na disku u odnosu na drugi slučaj.

I tako - da, podjela vremena je dobra opcija. Prometej ga koristi. Ali Prometej ima još jedan nedostatak. Prilikom spajanja ovih dijelova podataka, potrebno je zadržati u memoriji meta informacije za sve oznake i vremenske serije. Stoga, ako su dijelovi podataka koje spaja veliki, potrošnja memorije se veoma povećava tokom spajanja, za razliku od VictoriaMetrics-a. Prilikom spajanja VictoriaMetrics uopće ne troši memoriju, troši se samo nekoliko kilobajta, bez obzira na veličinu spojenih dijelova podataka.

Algoritam koji koristite koristi memoriju. Označava oznake vremenskih serija koje sadrže vrijednosti. I na ovaj način provjeravate upareno prisustvo u jednom nizu podataka iu drugom. I razumete da li je došlo do ukrštanja ili ne. Obično baze podataka implementiraju kursore i iteratore koji pohranjuju njihov trenutni sadržaj i prolaze kroz sortirane podatke zbog jednostavne složenosti ovih operacija.

Zašto ne koristimo kursore za prelazak podataka?

Da.

Pohranjujemo sortirane redove u LevelDB ili mergeset. Možemo pomjeriti kursor i pronaći raskrsnicu. Zašto ga ne iskoristimo? Jer je sporo. Jer kursori znače da trebate pozvati funkciju za svaki red. Poziv funkcije traje 5 nanosekundi. A ako imate 100 linija, onda se ispostavlja da pola sekunde trošimo samo na pozivanje funkcije.

Postoji tako nešto, da. I moje poslednje pitanje. Pitanje bi moglo zvučati malo čudno. Zašto nije moguće pročitati sve potrebne agregate u trenutku kada podaci stignu i sačuvati ih u traženom obliku? Zašto štediti ogromne količine u nekim sistemima kao što su VictoriaMetrics, ClickHouse, itd., a zatim trošiti mnogo vremena na njih?

Navest ću primjer da bude jasnije. Recimo kako radi mali brzinomjer igračke? On bilježi udaljenost koju ste prešli, cijelo vrijeme dodajući je jednoj vrijednosti, a drugoj - vremenu. I deli. I dobija prosečnu brzinu. Možeš da uradiš otprilike istu stvar. Dodajte sve potrebne činjenice u hodu.

U redu, razumijem pitanje. Vaš primjer ima svoje mjesto. Ako znate koji agregati su vam potrebni, onda je ovo najbolja implementacija. Ali problem je što ljudi spremaju ove metrike, neke podatke u ClickHouse i još ne znaju kako će ih agregirati i filtrirati u budućnosti, tako da moraju pohraniti sve sirove podatke. Ali ako znate da trebate izračunati nešto u prosjeku, zašto to ne biste izračunali umjesto da tamo pohranjujete gomilu sirovih vrijednosti? Ali to je samo ako znate tačno šta vam je potrebno.

Inače, baze podataka za pohranjivanje vremenskih serija podržavaju brojanje agregata. Na primjer, Prometheus podržava pravila snimanja. Odnosno, to se može učiniti ako znate koje jedinice će vam trebati. VictoriaMetrics to još nema, ali mu obično prethodi Prometheus, u kojem se to može učiniti u pravilima za kodiranje.

Na primjer, u prethodnom poslu bilo je potrebno izbrojati broj događaja u kliznom prozoru tokom posljednjeg sata. Problem je u tome što sam morao kreirati prilagođenu implementaciju u Go-u, odnosno servis za brojanje ove stvari. Ova usluga je na kraju bila netrivijalna, jer ju je teško izračunati. Implementacija može biti jednostavna ako trebate prebrojati neke agregate u fiksnim vremenskim intervalima. Ako želite da brojite događaje u kliznom prozoru, onda to nije tako jednostavno kao što se čini. Mislim da ovo još nije implementirano u ClickHouse ili u bazama podataka vremenskih serija, jer je teško implementirati.

I još jedno pitanje. Upravo smo pričali o prosječenju, i sjetio sam se da je nekada postojala takva stvar kao što je Graphite sa karbonskom pozadinom. I znao je da prorijedi stare podatke, odnosno ostavi jedan poen u minuti, jedan poen na sat itd. U principu, ovo je sasvim zgodno ako nam trebaju sirovi podaci, relativno govoreći, za mjesec dana, a sve ostalo može biti razređen. Ali Prometheus i VictoriaMetrics ne podržavaju ovu funkcionalnost. Da li se planira podržati? Ako ne, zašto ne?

Hvala na pitanju. Naši korisnici povremeno postavljaju ovo pitanje. Pitaju kada ćemo dodati podršku za downsampling. Ovdje postoji nekoliko problema. Prvo, svaki korisnik razumije downsampling nešto drugačije: neko želi da dobije bilo koju proizvoljnu tačku na datom intervalu, neko želi maksimalne, minimalne, prosečne vrednosti. Ako mnogi sistemi zapisuju podatke u vašu bazu podataka, onda ne možete sve to zbrojiti. Može se desiti da svaki sistem zahteva drugačije proređivanje. A ovo je teško implementirati.

A druga stvar je da je VictoriaMetrics, kao i ClickHouse, optimiziran za rad na velikim količinama neobrađenih podataka, tako da može prekriti milijardu linija za manje od sekunde ako imate mnogo jezgara u vašem sistemu. Skeniranje tačaka vremenske serije u VictoriaMetrics – 50 poena u sekundi po jezgru. I ove performanse se skaliraju na postojeće jezgre. Odnosno, ako imate 000 jezgara, na primjer, skenirat ćete milijardu tačaka u sekundi. A ovo svojstvo VictoriaMetrics i ClickHouse smanjuje potrebu za smanjenjem broja uzoraka.

Još jedna karakteristika je da VictoriaMetrics efikasno komprimuje ove podatke. Kompresija je u prosjeku u proizvodnji od 0,4 do 0,8 bajtova po točki. Svaka tačka je vremenska oznaka + vrijednost. I u prosjeku je komprimiran u manje od jednog bajta.

Sergej. Imam pitanje. Koliki je kvant minimalnog vremena snimanja?

Jedna milisekunda. Nedavno smo imali razgovor sa drugim programerima baza podataka vremenskih serija. Njihov minimalni vremenski odsječak je jedna sekunda. A u Graphitu, na primjer, to je također jedna sekunda. U OpenTSDB-u je također jedna sekunda. InfluxDB ima nanosekundnu preciznost. U VictoriaMetrics-u je jedna milisekunda, jer je u Prometheusu jedna milisekunda. VictoriaMetrics je prvobitno razvijen kao udaljeno skladište za Prometheus. Ali sada može sačuvati podatke iz drugih sistema.

Osoba s kojom sam razgovarao kaže da imaju tačnost od sekunde do sekunde - to im je dovoljno jer ovisi o vrsti podataka koji se pohranjuju u bazi podataka vremenskih serija. Ako se radi o DevOps podacima ili podacima iz infrastrukture, gdje ih prikupljate u intervalima od 30 sekundi, po minuti, onda je točnost sekunde dovoljna, ne treba vam ništa manje. A ako prikupljate ove podatke iz visokofrekventnih trgovačkih sistema, onda vam je potrebna nanosekundna preciznost.

Preciznost u milisekundi u VictoriaMetrics je takođe pogodna za DevOps slučaj, a može biti i za većinu slučajeva koje sam spomenuo na početku izveštaja. Jedina stvar za koju možda nije prikladan su visokofrekventni sistemi trgovanja.

Hvala ti! I još jedno pitanje. Šta je kompatibilnost u PromQL-u?

Potpuna kompatibilnost unatrag. VictoriaMetrics u potpunosti podržava PromQL. Osim toga, dodaje dodatnu naprednu funkcionalnost u PromQL, koja se zove MetricsQL. Na YouTube-u se priča o ovoj proširenoj funkcionalnosti. Govorio sam na Monitoring Meetup-u u proleće u Sankt Peterburgu.

Telegram kanal VictoriaMetrics.

Samo registrovani korisnici mogu učestvovati u anketi. Prijavite semolim.

Šta vas sprečava da se prebacite na VictoriaMetrics kao dugotrajno skladište za Prometheus? (Pišite u komentarima, dodaću u anketu))

  • 71,4%Ne koristim Prometheus5

  • 28,6%Nisam znao za VictoriaMetrics2

Glasalo je 7 korisnika. Uzdržano je bilo 12 korisnika.

izvor: www.habr.com

Dodajte komentar