Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Predlažem da pročitate transkript izvješća Alexandera Valyalkina s kraja 2019. „Optimizacije u VictoriaMetrics”

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Ovdje je poveznica na video zapis ovog izvješća - https://youtu.be/MZ5P21j_HLE

Slajdovi

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Reci nam o sebi. Ja sam Aleksandar Valjalkin. Ovdje moj GitHub račun. Strastveni sam za Go i optimizaciju performansi. Napisao sam mnogo korisnih i manje korisnih biblioteka. Počinju s bilo kojim fast, ili sa quick prefiks.

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Kratak pregled izvješća je sljedeći:

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Zna li itko u publici što je VictoriaMetrics? Wow, puno ljudi već zna. To je dobra vijest. Za one koji ne znaju, ovo je baza vremenskih serija. Temelji se na ClickHouse arhitekturi, na nekim detaljima ClickHouse implementacije. Na primjer, na kao što su: MergeTree, paralelni izračun na svim dostupnim jezgrama procesora i optimizacija performansi radom na podatkovnim blokovima koji su smješteni u predmemoriju procesora.

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

Okomito se skalira - to jest, možete dodati više procesora, više RAM-a na jedno računalo. VictoriaMetrics će uspješno iskoristiti ove dostupne resurse i poboljšati linearnu produktivnost.

VictoriaMetrics također skalira horizontalno - to jest, možete dodati dodatne čvorove u klaster VictoriaMetrics, a njegova izvedba će rasti gotovo linearno.

Kao što ste pogodili, VictoriaMetrics je brza baza podataka, jer ne mogu pisati drugima. I to je napisano u Go-u, pa o tome govorim na ovom susretu.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Tko zna što je vremenska serija? Također poznaje mnogo ljudi. Vremenski niz je niz parova (timestamp, значение), gdje su ti parovi poredani po vremenu. Vrijednost je broj s pomičnim zarezom – float64.

Svaki vremenski niz je jedinstveno identificiran ključem. Od čega se sastoji ovaj ključ? Sastoji se od nepraznog skupa parova ključ-vrijednost.

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

Završili smo s nazivom vremenske serije koji se sastoji od tri para ključ-vrijednost. Ovaj ključ odgovara popisu parova (timestamp, value). t1, t3, t3, ..., tN - ovo su vremenske oznake, 10, 20, 12, ..., 15 — odgovarajuće vrijednosti. Ovo je korištenje CPU-a u danom trenutku za danu seriju.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Gdje se mogu koristiti vremenske serije? Ima li netko kakvu ideju?

  • U DevOpsu možete mjeriti CPU, RAM, mrežu, rps, broj pogrešaka itd.
  • IoT - možemo mjeriti temperaturu, tlak, geo koordinate i još ponešto.
  • Također i financije – možemo pratiti cijene za sve vrste dionica i valuta.
  • Osim toga, vremenske serije mogu se koristiti u praćenju proizvodnih procesa u tvornicama. Imamo korisnike koji koriste VictoriaMetrics za nadzor vjetroturbina, za robote.
  • Vremenske serije također su korisne za prikupljanje informacija sa senzora raznih uređaja. Na primjer, za motor; za mjerenje tlaka u gumama; za mjerenje brzine, udaljenosti; za mjerenje potrošnje benzina itd.
  • Vremenske serije također se mogu koristiti za praćenje zrakoplova. Svaki zrakoplov ima crnu kutiju koja prikuplja vremenske serije za različite zdravstvene parametre zrakoplova. Vremenske serije također se koriste u zrakoplovnoj industriji.
  • Zdravstvo je krvni tlak, 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 obujam njihove upotrebe raste svake godine.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Zašto vam je potrebna baza podataka vremenskih serija? Zašto ne možete koristiti običnu relacijsku bazu podataka za pohranjivanje vremenskih serija?

Budući da 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 učinkovito pohranjuju bodove (timestamp, value) s datim ključem. Oni pružaju API za čitanje pohranjenih podataka prema ključu, prema jednom paru ključ-vrijednost ili prema više parova ključ-vrijednost ili prema regularnom 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 upitne jezike jer SQL vremenskih serija nije baš dobro prilagođen. Iako postoje baze podataka koje podržavaju SQL, on nije baš prikladan. Upitni jezici kao što su PromQL, InfluxQL, Tok, Q. Nadam se da je netko čuo barem jedan od ovih jezika. Mnogi su ljudi vjerojatno čuli za PromQL. Ovo je Prometheus upitni jezik.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

Sastoji se od dva dijela. Ovo je pohrana za obrnuti indeks i pohrana za vrijednosti vremenske serije. Ta su spremišta odvojena.

Kada novi zapis stigne u bazu podataka, prvo pristupamo obrnutom indeksu kako bismo pronašli identifikator vremenske serije za dati skup label=value za datu metriku. Pronalazimo ovaj identifikator i spremamo vrijednost u pohranu podataka.

Kada dođe zahtjev za dohvaćanje podataka iz TSDB-a, prvo idemo na obrnuti indeks. Uzmimo sve timeseries_ids zapisa koji odgovaraju ovom skupu label=value. Zatim dobivamo sve potrebne podatke iz skladišta podataka, indeksirane prema timeseries_ids.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Pogledajmo primjer kako baza podataka vremenskog niza obrađuje dolazni upit odabira.

  • Prije svega, ona dobiva sve timeseries_ids iz obrnutog indeksa koji sadrži zadane parove label=value, ili zadovoljiti zadani regularni izraz.
  • Zatim dohvaća sve podatkovne točke iz pohrane podataka u zadanom vremenskom intervalu za pronađene timeseries_ids.
  • Nakon toga baza podataka izvodi neke izračune na tim podatkovnim točkama, prema zahtjevu korisnika. I nakon toga vraća odgovor.

U ovoj prezentaciji ispričat ću vam o prvom dijelu. Ovo je pretraga timeseries_ids obrnutim indeksom. O drugom dijelu i trećem dijelu možete pogledati kasnije Izvori VictoriaMetrics, ili pričekajte dok ne pripremim druge izvještaje :)

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Prijeđimo na obrnuti indeks. Mnogi bi mogli pomisliti da je to jednostavno. Tko zna što je obrnuti indeks i kako funkcionira? Oh, nema više toliko ljudi. Pokušajmo shvatiti što je to.

Zapravo je jednostavno. To je jednostavno rječnik koji preslikava ključ u vrijednost. Što je ključ? Ovaj par label=valueGdje label и value - ovo su linije. A vrijednosti su skup timeseries_ids, koji uključuje navedeni par label=value.

Obrnuti indeks omogućuje brzo pronalaženje svega timeseries_ids, koji su dali label=value.

Također vam omogućuje brzo pronalaženje 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.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

Funkcija getMetricIDs dobiva popis nizova. Svaki red sadrži label=value. Ova funkcija vraća popis metricIDs.

Kako radi? Ovdje imamo globalnu varijablu tzv invertedIndex. Ovo je obični rječnik (map), koji će preslikati niz u rezove int. Linija sadrži label=value.

Implementacija funkcije: get metricIDs za prvi label=value, zatim prolazimo kroz sve ostalo label=value, razumijemo metricIDs za njih. I pozvati funkciju intersectInts, o čemu će biti riječi u nastavku. I ova funkcija vraća sjecište tih popisa.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Ovaj problem se može riješiti pomoću gotovih rješenja kao npr LevelDBIli RocksDB.

Ukratko, potrebna nam je baza podataka koja nam omogućuje brzo obavljanje tri operacije.

  • Prva operacija je snimanje ключ-значение u ovu bazu podataka. Ona to radi vrlo brzo, gdje ключ-значение su proizvoljni nizovi.
  • Druga operacija je brzo traženje vrijednosti pomoću zadanog ključa.
  • I treća operacija je brza pretraga svih vrijednosti po zadanom prefiksu.

LevelDB i RocksDB - ove baze podataka razvili su Google i Facebook. Prvo je došao LevelDB. Onda su dečki iz Facebooka uzeli LevelDB i počeli ga poboljšavati, napravili su RocksDB. Sada gotovo sve interne baze podataka rade na RocksDB unutar Facebooka, uključujući one koje su prebačene u RocksDB i MySQL. Nazvali su ga MyRocks.

Obrnuti indeks može se implementirati korištenjem LevelDB. Kako to učiniti? Spremamo kao ključ label=value. A vrijednost je identifikator vremenske serije u kojoj je par prisutan label=value.

Ako imamo mnogo vremenskih serija s danim parom label=value, tada će u ovoj bazi podataka biti mnogo redaka s istim ključem i različitim timeseries_ids. Da biste dobili popis svih timeseries_ids, koji počinju ovim label=prefix, vršimo skeniranje raspona za koji je ova baza podataka optimizirana. Odnosno, odabiremo sve retke koji počinju s label=prefix i dobiti potrebno timeseries_ids.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

Funkcija je ista kao i za naivnu implementaciju. Ponavlja naivnu implementaciju gotovo red po red. Jedina stvar je u tome što umjesto da se okrenete map pristupamo obrnutom indeksu. Dobivamo sve vrijednosti za prvi label=value. Zatim prolazimo kroz sve preostale parove label=value i dobiti odgovarajuće skupove metricID-ova za njih. Zatim nalazimo raskrižje.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

Zašto? Budući da je LevelDB sporiji od naivne implementacije. U jednostavnoj implementaciji, s obzirom na dani ključ, odmah dohvaćamo cijeli isječak metricIDs. Ovo je vrlo brza operacija - cijela je kriška spremna za upotrebu.

U LevelDB, svaki put kada se pozove funkcija GetValues morate proći kroz sve retke koji počinju s label=value. I dobiti vrijednost za svaki redak timeseries_ids. Od takvih timeseries_ids sakupite krišku ovih timeseries_ids. Očito, ovo je puno sporije od jednostavnog pristupa običnoj karti putem ključa.

Drugi nedostatak je taj što je LevelDB napisan u C-u. Pozivanje C funkcija iz Go nije baš brzo. Traje stotine nanosekundi. To nije jako brzo, jer u usporedbi s običnim pozivom funkcije napisanim u go, koji traje 1-5 nanosekundi, razlika u izvedbi je desetke puta. Za VictoriaMetrics ovo je bila kobna greška :)

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Pa sam napisao vlastitu implementaciju obrnutog indeksa. I nazvao ju je mergeset.

Mergeset se temelji na podatkovnoj strukturi MergeTree. Ova struktura podataka je posuđena od ClickHouse. Očito, mergeset bi trebao biti optimiziran za brzo pretraživanje timeseries_ids prema zadanom ključu. Mergeset je u potpunosti napisan u Go-u. Možeš vidjeti VictoriaMetrics izvori na GitHubu. Implementacija mergeseta je u mapi /lib/mergeset. Možete pokušati shvatiti što se tamo događa.

Mergeset API vrlo je sličan LevelDB-u i RocksDB-u. Odnosno, omogućuje vam brzo spremanje novih zapisa tamo i brz odabir zapisa prema zadanom prefiksu.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Kasnije ćemo govoriti 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 odljeva. Prevedeno na ruski, ovo je česta promjena u vremenskoj seriji. Ovo je vrijeme kada vremenska serija završava i počinje nova serija, ili započinju mnoge nove vremenske serije. A to se često događa.

Drugi razlog je veliki broj vremenskih serija. U početku, kada je praćenje postajalo sve popularnije, broj vremenskih serija bio je mali. Na primjer, za svako računalo morate pratiti CPU, memoriju, mrežu i opterećenje diska. 4 vremenske serije po računalu. Recimo da imate 100 računala 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ć zasebno svake jezgre procesora. Ako imate 40 procesorskih jezgri, tada imate 40 puta više vremenskih serija za mjerenje opterećenja procesora.

Ali to nije sve. Svaka jezgra procesora može imati nekoliko stanja, kao što je mirovanje, kada je u stanju mirovanja. I također rad u korisničkom prostoru, rad u kernel prostoru i drugim stanjima. A svako takvo stanje može se mjeriti i kao zasebna vremenska serija. Time se dodatno povećava broj redova za 7-8 puta.

Iz jedne metrike dobili smo 40 x 8 = 320 metrike za samo jedno računalo. Pomnožimo li sa 100, dobit ćemo 32 000 umjesto 400.

Onda se pojavio Kubernetes. I postalo je gore jer Kubernetes može ugostiti mnogo različitih usluga. 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 eksponencijalno raste i suočavamo se s problemom velikog broja vremenskih serija, što se naziva visokokardinalnost. VictoriaMetrics se s time uspješno nosi u usporedbi s drugim bazama podataka vremenskih serija.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

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

Do čega je to dovelo? Štoviše, svakom novom implementacijom prekidaju se sve stare vremenske serije, a umjesto njih započinju nove vremenske serije s novom vrijednošću oznake deployment_id. Takvih redaka može biti stotine tisuća, pa čak i milijuni.

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

Glavni problem visoke stope odljeva je osigurati stalnu brzinu pretraživanja za sve vremenske serije za dani skup oznaka tijekom određenog vremenskog intervala. Obično je to vremenski interval za zadnji sat ili zadnji dan.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Kako riješiti ovaj problem? Evo prve opcije. Time se obrnuti indeks tijekom vremena dijeli na neovisne dijelove. Odnosno, prođe neki vremenski interval, završimo rad s trenutnim obrnutim indeksom. I stvorite novi obrnuti indeks. Još jedan vremenski interval prolazi, stvaramo još jedan i još jedan.

A prilikom uzorkovanja iz ovih invertiranih indeksa, nalazimo skup invertiranih indeksa koji spadaju unutar zadanog intervala. I, sukladno tome, odatle odabiremo id vremenske serije.

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Postoji još jedna opcija za rješavanje ovog problema. Ovo je za pohranjivanje zasebnog popisa ID-ova vremenskih nizova koji su se dogodili tog dana za svaki dan.

Prednost ovog rješenja u odnosu na prethodno rješenje je u tome što ne umnožavamo informacije o vremenskim serijama koje ne nestaju tijekom vremena. Stalno su prisutni i ne mijenjaju se.

Nedostatak je što je takvo rješenje teže implementirati i teže otkloniti pogreške. I VictoriaMetrics je odabrala ovo rješenje. Tako se to povijesno dogodilo. Ovo rješenje također ima dobre rezultate u usporedbi s prethodnim. Jer ovo rješenje nije implementirano iz razloga što je potrebno duplicirati podatke u svakoj particiji za vremenske serije koje se ne mijenjaju, odnosno koje ne nestaju tijekom vremena. VictoriaMetrics je prvenstveno bio optimiziran za potrošnju prostora na disku, a prethodna implementacija pogoršala je potrošnju prostora na disku. Ali ova je implementacija prikladnija za smanjenje potrošnje prostora na disku, pa je odabrana.

Morao sam se boriti s njom. Problem je bio u tome što u ovoj implementaciji još uvijek morate odabrati puno veći broj timeseries_ids za podatke nego kada je obrnuti indeks vremenski particioniran.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Kako smo riješili ovaj problem? Riješili smo to na originalan način - pohranjivanjem nekoliko identifikatora vremenske serije u svaki invertirani indeksni unos umjesto jednog identifikatora. Odnosno, imamo ključ label=value, koji se pojavljuje u svakoj vremenskoj seriji. A sada smo spasili 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 sve ID-ove vremenske serije.

To je omogućilo povećanje brzine skeniranja takvog obrnutog indeksa do 10 puta. I to nam je omogućilo da smanjimo potrošnju memorije za predmemoriju, jer sada pohranjujemo niz label=value samo jednom u predmemoriju zajedno N puta. A ovaj red može biti velik ako pohranjujete duge retke u svoje oznake i oznake, koje Kubernetes voli gurati tamo.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Druga mogućnost za ubrzavanje pretraživanja na obrnutom indeksu je dijeljenje. Stvaranje nekoliko obrnutih indeksa umjesto jednog i dijeljenje podataka između njih po ključu. Ovo je komplet key=value pare. Odnosno, dobivamo nekoliko neovisnih invertiranih indeksa, koje možemo paralelno postavljati upite na nekoliko procesora. Prethodne implementacije dopuštale su samo rad u jednoprocesorskom načinu rada, tj. skeniranje podataka na samo jednoj jezgri. Ovo rješenje vam omogućuje skeniranje podataka na nekoliko jezgri odjednom, kao što ClickHouse voli raditi. To je ono što planiramo provesti.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Sada se vratimo našim ovcama – funkciji presjeka timeseries_ids. Razmotrimo koje implementacije mogu postojati. Ova vam funkcija omogućuje pronalaženje timeseries_ids za dati skup label=value.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

Naivna implementacija izgleda ovako. Iteriramo sve vrijednosti iz odsječka a, unutar ove petlje prolazimo kroz sve vrijednosti odsječka b. I uspoređujemo ih. Ako se podudaraju, onda smo pronašli raskrižje. I spremite ga unutra result.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Koji su nedostaci? Kvadratna složenost je njegov glavni nedostatak. Na primjer, ako su vaše dimenzije kriške a и b milijun po milijun, tada vam ova funkcija nikada neće vratiti odgovor. Jer trebat će napraviti trilijun iteracija, što je puno čak i za moderna računala.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Druga implementacija temelji se na karti. Mi stvaramo kartu. Stavili smo sve vrijednosti iz isječka u ovu mapu a. Zatim prolazimo kroz krišku u zasebnoj petlji b. I provjeravamo je li ova vrijednost iz odsječka b u karti. Ako postoji, dodajte ga rezultatu.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Koje su prednosti? Prednost je u tome što postoji samo linearna složenost. To jest, funkcija će se izvršiti mnogo brže za veće dijelove. Za isječak veličine milijun, ova funkcija će se izvršiti u 2 milijuna ponavljanja, za razliku od trilijuna ponavljanja prethodne funkcije.

Loša strana je što ova funkcija zahtijeva više memorije za izradu ove karte.

Drugi nedostatak su veliki troškovi za raspršivanje. Ovaj nedostatak nije baš očit. A za nas to također nije bilo previše očito, tako da je isprva u VictoriaMetricsu implementacija raskrižja bila putem karte. Ali tada je profiliranje pokazalo da se vrijeme glavnog procesora troši na pisanje karte i provjeru prisutnosti vrijednosti u ovoj mapi.

Zašto se CPU vrijeme gubi na ovim mjestima? Zato što Go izvodi operaciju raspršivanja na ovim linijama. To jest, izračunava hash ključa kako bi mu zatim pristupio na danom indeksu u HashMapu. Operacija izračuna hashiranja dovršava se u desecima nanosekundi. Ovo je sporo za VictoriaMetrics.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Odlučio sam implementirati bitset optimiziran posebno za ovaj slučaj. Ovako sada izgleda presjek dviju kriški. Ovdje stvaramo bitset. Dodamo joj elemente iz prve kriške. Zatim provjeravamo prisutnost tih elemenata u drugom rezu. I dodajte ih rezultatu. Odnosno, gotovo se ne razlikuje od prethodnog primjera. Jedina stvar ovdje je da smo pristup karti zamijenili prilagođenim funkcijama add и has.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Na prvi pogled se čini da bi ovo trebalo raditi sporije, ako je prethodno tu korištena standardna karta, pa se onda pozivaju neke druge funkcije, ali profiliranje pokazuje da ova stvar radi 10 puta brže od standardne karte u slučaju VictoriaMetrics.

Osim toga, koristi puno manje memorije u usporedbi s implementacijom karte. Zato što ovdje spremamo bitove umjesto vrijednosti od osam bajtova.

Nedostatak ove implementacije je što nije tako očita, nije trivijalna.

Drugi nedostatak koji mnogi možda neće primijetiti je da ova implementacija možda neće dobro funkcionirati u nekim slučajevima. To jest, optimiziran je za određeni slučaj, za ovaj slučaj križanja VictoriaMetrics ID-ova vremenske serije. To ne znači da je prikladan za sve slučajeve. Ako se neispravno koristi, nećemo dobiti povećanje performansi, već pogrešku nedostatka memorije i usporavanje performansi.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Ovdje je njegov API. Nije baš komplicirano. API je posebno prilagođen konkretnom primjeru korištenja VictoriaMetrics. Odnosno, ovdje nema nepotrebnih funkcija. Ovdje su funkcije koje izričito koristi VictoriaMetrics.

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 klonira mnogo. I funkcija appendto pretvara ovaj skup u odsječak timeseries_ids.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

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 učiniti i bez ovog pomoćnog polja, ali moralo se dodati ovdje jer VictoriaMetrics često ispituje duljinu bitseta u svojim algoritmima.

  • Drugo polje je buckets. Ovo je dio strukture bucket32. Svaka struktura pohranjuje hi polje. Ovo su gornja 32 bita. I dvije kriške - b16his и buckets od bucket16 strukture.

Ovdje je pohranjeno prvih 16 bitova drugog dijela 64-bitne strukture. I ovdje su skupovi bitova pohranjeni za nižih 16 bitova svakog bajta.

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Pogledajmo kako je implementirana jedna od metoda ove strukture za dodavanje nove vrijednosti.

Sve počinje sa uint64 značenja. Računamo gornja 32 bita, izračunavamo donja 32 bita. Prođimo kroz sve buckets. Uspoređujemo prva 32 bita u svakoj kanti s dodanom vrijednošću. A ako se podudaraju, tada pozivamo funkciju add u strukturi b32 buckets. I tamo dodajte niža 32 bita. A kad bi se vratilo 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 onaj koji vam je potreban bucket sa traženom hi-vrijednošću, tada pozivamo funkciju addAlloc, koji će proizvesti novi bucket, dodajući ga u strukturu žlice.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Ovo je implementacija funkcije b32.add. Slično je prethodnoj izvedbi. Izračunavamo najvažnijih 16 bitova, najmanje značajnih 16 bitova.

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

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

I ovdje je najniža razina, koju treba optimizirati što je više moguće. Računamo za uint64 id vrijednost u bitu odsječka i također bitmask. Ovo je maska ​​za zadanu 64-bitnu vrijednost, koja se može koristiti za provjeru prisutnosti ovog bita ili njegovo postavljanje. Provjeravamo je li ovaj bit postavljen i postavljamo ga te vraćamo prisutnost. Ovo je naša implementacija koja nam je omogućila da ubrzamo rad ID-ova vremenskih nizova koji se presijecaju za 10 puta u usporedbi s konvencionalnim kartama.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Uz ovu optimizaciju, VictoriaMetrics ima mnoge druge optimizacije. Većina ovih optimizacija dodana je s razlogom, ali nakon profiliranja koda u proizvodnji.

Ovo je glavno pravilo optimizacije - nemojte dodavati optimizaciju pretpostavljajući da će ovdje biti usko grlo, jer se može ispostaviti da tamo neće biti uskog grla. Optimizacija obično degradira kvalitetu koda. Stoga se isplati optimizirati tek nakon profiliranja i po mogućnosti u produkciji, kako bi to bili pravi podaci. Ako je netko zainteresiran, možete pogledati izvorni kod VictoriaMetrics i istražiti druge optimizacije koje su tamo.

Idite na optimizacije u VictoriaMetrics. Aleksandar Valjalkin

Imam pitanje o bitsetu. Vrlo sličan C++ vektorskoj bool implementaciji, optimizirani bitset. Jeste li od tamo preuzeli implementaciju?

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

Također vam savjetujem da pogledate reportažu Alekseja Milovida. Prije otprilike mjesec dana govorio je o optimizaciji u ClickHouseu za određene specijalizacije. On samo kaže da je u općem slučaju C++ implementacija ili neka druga implementacija prilagođena 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 temeljna razlika u odnosu na InfluxDB?

Postoje mnoge temeljne 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, milijune. Na primjer, VictoriaMetrics troši 1 GB na milijun aktivnih redaka, dok InfluxDB troši 10 GB. I to je velika razlika.

Druga temeljna razlika je u tome što InfluxDB ima čudne jezike upita - Flux i InfluxQL. Nisu baš prikladni za rad s vremenskim serijama u usporedbi s PromQL, koji podržava VictoriaMetrics. PromQL je upitni jezik iz Prometheusa.

I još jedna razlika je u tome što InfluxDB ima pomalo čudan podatkovni model, gdje svaki red može pohraniti nekoliko polja s različitim skupom oznaka. Ove linije su dalje podijeljene u različite tablice. Ove dodatne komplikacije kompliciraju kasniji rad s ovom bazom podataka. Teško je podržati i razumjeti.

U VictoriaMetrics sve je puno jednostavnije. Tamo je svaka vremenska serija ključna vrijednost. Vrijednost je skup bodova - (timestamp, value), a ključ je skup label=value. Nema odvajanja između polja i mjerenja. Omogućuje vam odabir bilo kojeg podatka, a zatim kombiniranje, zbrajanje, oduzimanje, množenje, dijeljenje, za razliku od InfluxDB-a gdje izračuni između različitih redaka još uvijek nisu implementirani koliko ja znam. Čak i ako se implementiraju, teško je, morate napisati puno koda.

Imam pitanje za razjašnjenje. Jesam li dobro shvatio da postoji nekakav problem o kojem ste govorili, da ovaj obrnuti indeks ne stane u memoriju, pa postoji particioniranje?

Prvo sam pokazao naivnu implementaciju obrnutog indeksa na standardnoj Go karti. Ova implementacija nije prikladna za baze podataka jer se ovaj obrnuti indeks ne sprema na disk, a baza podataka se mora spremiti na disk tako da ti podaci ostanu dostupni nakon ponovnog pokretanja. U ovoj implementaciji, kada ponovno 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šću! Moje ime je Pavel. Ja sam iz Wildberriesa. Imam nekoliko pitanja za vas. Pitanje jedno. Mislite li da biste, da ste odabrali drugačiji princip pri izgradnji arhitekture svoje aplikacije i particionirali podatke tijekom vremena, možda biste mogli presijecati podatke prilikom pretraživanja, samo na temelju činjenice da jedna particija sadrži podatke za jednu vremenskom razdoblju, odnosno u jednom vremenskom intervalu i ne biste morali brinuti o tome da su vaši komadi različito razbacani? Pitanje broj 2 - budući da implementirate sličan algoritam s bitsetom i svim ostalim, onda ste možda pokušali koristiti instrukcije procesora? Možda ste probali takve optimizacije?

Na drugo ću odgovoriti odmah. Još nismo došli do te točke. Ali ako treba, stići ćemo i tamo. A ono prvo, koje je bilo pitanje?

Razgovarali ste o dva scenarija. I rekli su da su odabrali drugu sa složenijom izvedbom. I nije im se više svidio prvi, gdje su podaci podijeljeni prema vremenu.

Da. U prvom slučaju, ukupni volumen indeksa bio bi 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 vremenske serije mala, tj. stalno se koriste iste serije, tada bismo u prvom slučaju izgubili puno više u količini zauzetog prostora na disku u usporedbi s drugim slučajem.

I tako - da, raspodjela 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, tada se potrošnja memorije jako povećava tijekom spajanja, za razliku od VictoriaMetrics. Prilikom spajanja VictoriaMetrics uopće ne troši memoriju, troši se samo par kilobajta, bez obzira na veličinu spojenih podataka.

Algoritam koji koristite koristi memoriju. Označava oznake vremenske serije koje sadrže vrijednosti. I na ovaj način provjeravate uparenu prisutnost u jednom nizu podataka iu drugom. I vi razumijete je li došlo do presjeka ili ne. Tipič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 pokazivače za kretanje kroz podatke?

Da.

Pohranjujemo sortirane retke u LevelDB ili mergeset. Možemo pomaknuti kursor i pronaći raskrižje. Zašto ga ne koristimo? Jer je sporo. Budući da kursori znače da trebate pozvati funkciju za svaki redak. Poziv funkcije traje 5 nanosekundi. A ako imate 100 redaka, ispada da potrošimo pola sekunde samo pozivajući funkciju.

Postoji takva stvar, da. I moje zadnje pitanje. Pitanje može zvučati malo čudno. Zašto nije moguće očitati sve potrebne agregate u trenutku prispijeća podataka i pohraniti ih u traženom obliku? Zašto štedjeti ogromne količine u nekim sustavima kao što su VictoriaMetrics, ClickHouse itd., a zatim trošiti puno vremena na njih?

Navest ću primjer da bude jasnije. Recimo kako radi mala igračka brzinomjer? Bilježi udaljenost koju ste prešli, cijelo vrijeme dodajući je jednoj vrijednosti, a drugoj - vremenu. I dijeli. I dobiva prosječnu brzinu. Možete učiniti otprilike istu stvar. Zbrojite sve potrebne činjenice u hodu.

U redu, razumijem pitanje. Vaš primjer ima svoje mjesto. Ako znate koji su vam agregati potrebni, onda je ovo najbolja izvedba. Ali problem je u tome što ljudi spremaju te metrike, neke podatke u ClickHouse i još ne znaju kako će ih agregirati i filtrirati u budućnosti, pa moraju spremati sve neobrađene podatke. Ali ako znate da nešto trebate izračunati u prosjeku, zašto to ne biste izračunali umjesto da tamo pohranjujete hrpu sirovih vrijednosti? Ali to je samo ako točno znate što trebate.

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

Na primjer, na svom prethodnom poslu trebao sam prebrojati broj događaja u kliznom prozoru tijekom prošlog sata. Problem je što sam morao napraviti custom implementaciju u Go-u, tj. servis za brojanje ove stvari. Ova usluga je u konačnici bila netrivijalna, jer ju je teško izračunati. Implementacija može biti jednostavna ako trebate brojati neke agregate u fiksnim vremenskim intervalima. Ako želite brojati događaje u kliznom prozoru, onda to nije tako jednostavno kao što se čini. Mislim da ovo još nije implementirano u ClickHouse ili u baze podataka timeseries, jer je to teško implementirati.

I još jedno pitanje. Baš smo razgovarali o prosjeku, a ja sam se sjetio da je jednom postojao nešto poput Graphitea s karbonskim pozadinom. I znao je prorijediti stare podatke, odnosno ostaviti jednu točku u minuti, jednu točku u satu itd. U principu, to je dosta zgodno ako nam trebaju sirovi podaci, relativno govoreći, za mjesec dana, a sve ostalo može biti razrijeđen . Ali Prometheus i VictoriaMetrics ne podržavaju ovu funkcionalnost. Planira li se to 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: netko želi dobiti proizvoljnu točku na zadanom intervalu, netko želi maksimalne, minimalne, prosječne vrijednosti. Ako mnogi sustavi zapisuju podatke u vašu bazu podataka, ne možete ih sve skupiti. Može se dogoditi da svaki sustav zahtijeva drugačije stanjivanje. A ovo je teško provesti.

A druga stvar je da je VictoriaMetrics, kao i ClickHouse, optimiziran za rad na velikim količinama neobrađenih podataka, tako da može očistiti milijardu redaka u manje od sekunde ako imate mnogo jezgri u svom sustavu. Skeniranje točaka vremenske serije u VictoriaMetrics – 50 točaka u sekundi po jezgri. A ova se izvedba skalira na postojeće jezgre. To jest, ako imate 000 jezgri, na primjer, skenirat ćete milijardu točaka u sekundi. A ovo svojstvo VictoriaMetrics i ClickHousea smanjuje potrebu za smanjenjem uzorka.

Još jedna značajka je da VictoriaMetrics učinkovito sažima te podatke. Kompresija u prosjeku u proizvodnji je od 0,4 do 0,8 bajta po točki. Svaka točka je vremenska oznaka + vrijednost. I u prosjeku je komprimiran u manje od jednog bajta.

Sergej. Imam pitanje. Koji je minimalni kvantum vremena snimanja?

Jedna milisekunda. Nedavno smo razgovarali s drugim programerima baza podataka vremenskih serija. Njihov minimalni vremenski odsječak je jedna sekunda. A u Graphiteu, na primjer, također je jedna sekunda. U OpenTSDB-u je također jedna sekunda. InfluxDB ima nanosekundnu preciznost. U VictoriaMetrics je jedna milisekunda, jer je u Prometheusu jedna milisekunda. A VictoriaMetrics je izvorno razvijen kao udaljena pohrana za Prometheus. Ali sada može spremati podatke iz drugih sustava.

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

Točnost milisekunde u VictoriaMetrics također je prikladna za slučaj DevOps, a može biti prikladna i za većinu slučajeva koje sam spomenuo na početku izvješća. Jedina stvar za koju možda nije prikladna su visokofrekventni sustavi trgovanja.

Hvala vam! I još jedno pitanje. Što je kompatibilnost u PromQL-u?

Potpuna kompatibilnost unazad. VictoriaMetrics u potpunosti podržava PromQL. Osim toga, dodaje dodatnu naprednu funkcionalnost u PromQL, koja se zove MetricsQL. Na YouTubeu se priča o ovoj proširenoj funkcionalnosti. Govorio sam na Monitoring Meetupu u proljeće u St.

Telegram kanal VictoriaMetrics.

U anketi mogu sudjelovati samo registrirani korisnici. Prijaviti se, molim.

Što vas sprječava da prijeđete na VictoriaMetrics kao svoju dugoročnu pohranu za Prometheus? (Pišite u komentarima, dodat ću u anketu))

  • 71,4%Ne koristim Prometheus5

  • 28,6%Nisam znao za VictoriaMetrics2

Glasovalo je 7 korisnika. Suzdržano je bilo 12 korisnika.

Izvor: www.habr.com

Dodajte komentar