Ju ofroj të njihem me interpretimin e raportit të fundit të vitit 2019 nga Aleksandër Valjalkin "Optimizimet e Go në VictoriaMetrics"
â njĂ« DBMS e shpejtĂ« dhe e shkallĂ«zueshme pĂ«r ruajtjen dhe pĂ«rpunimin e tĂ« dhĂ«nave nĂ« formĂ«n e serive temporale (ncifuli pĂ«rbĂ«het nga koha dhe njĂ« grup vlerash pĂ«rkatĂ«se tĂ« kĂ«saj kohe, pĂ«r shembull, tĂ« marra pĂ«rmes njĂ« sondazhi periodik tĂ« gjendjes sĂ« sensorĂ«ve ose mbledhjes sĂ« metrikeve).

Ja lidhja pĂ«r videon e kĂ«tij raporti â

Do të flas pak për veten time. Unë jam Aleksandër Valjalkin. Këtu është . Pasioni im janë Go dhe optimizimi i performancës. Kam shkruar shumë biblioteka të ndryshme, disa të dobishme e disa jo. Ato fillojnë ose me fast, ose me quick prefiksin.
Aktualisht po punoj nĂ« VictoriaMetrics. ĂfarĂ« Ă«shtĂ« kjo dhe çfarĂ« bĂ«j atje? KĂ«tĂ« do ta shpjegoj nĂ« kĂ«tĂ« prezantim.

Plani i raportit është si më poshtë:
- Fillimisht do t'ju tregoj se çfarë është VictoriaMetrics.
- Më pas do të shpjegoj se çfarë janë seritë temporale.
- Më pas do të flas për se si funksionon një bazë të dhënash për seritë temporale.
- Më tej do të flas për arkitekturën e bazës së të dhënave: nga çfarë përbëhet ajo.
- Dhe më pas do të kalojmë te optimizimet që janë në VictoriaMetrics. Këtu përfshihet optimizimi i indeksit të invertuar dhe optimizimi për implementimin e bitset në Go.

Dikush nĂ« audiencĂ« di se çfarĂ« Ă«shtĂ« VictoriaMetrics? Uau, shumĂ« njerĂ«z e dinĂ« kĂ«tĂ«. Kjo Ă«shtĂ« njĂ« lajm i mirĂ«. PĂ«r ata qĂ« nuk e dinĂ« â Ă«shtĂ« njĂ« bazĂ« tĂ« dhĂ«nash pĂ«r seritĂ« temporale. Ajo bazohet nĂ« arkitekturĂ«n ClickHouse, mbi disa detaje tĂ« implementimit tĂ« ClickHouse. PĂ«r shembull, mbi aspekte si: MergeTree, llogaritje tĂ« paralelĂ« nĂ« tĂ« gjitha bĂ«rthamat e disponueshme tĂ« procesorit dhe optimizimi i performancĂ«s pĂ«rmes punĂ«s mbi blloqet e tĂ« dhĂ«nave qĂ« vendosen nĂ« cache-in e procesorit.
VictoriaMetrics ofron kompresimin më të mirë të të dhënave krahasuar me bazat e tjera të të dhënave për seritë temporale.
Ajo shkallĂ«zohet vertikalisht â dmth. mund tĂ« shtoni njĂ« numĂ«r mĂ« tĂ« madh procesorĂ«sh, njĂ« sasi mĂ« tĂ« madhe tĂ« memories operativĂ« nĂ« njĂ« kompjuter. VictoriaMetrics do tĂ« jetĂ« nĂ« gjendje tĂ« shfrytĂ«zojĂ« kĂ«to burime tĂ« disponueshme dhe do tĂ« rrisĂ« performancĂ«n lineare.
Po ashtu, VictoriaMetrics shkallĂ«zohet horizontalisht â dmth. mund tĂ« shtoni node tĂ« tjera nĂ« klasterin e VictoriaMetrics, dhe performanca e saj do tĂ« rritet pothuajse linearisht.
Siç e keni marrë me mend, VictoriaMetrics është një bazë të dhënash e shpejtë, sepse nuk mund të shkruaj për të tjera. Dhe ajo është e shkruar në Go, prandaj po flas për të në këtë takim.

Kush e di se çfarĂ« Ă«shtĂ« njĂ« seri e pĂ«rkohshme? Edhe shumĂ« njerĂ«z e dinĂ« kĂ«tĂ«. NjĂ« seri e pĂ«rkohshme Ă«shtĂ« njĂ« seri çiftesh (timestamp, vlera), ku kĂ«to çifte janĂ« renditur sipas kohĂ«s. Vlera pĂ«rfaqĂ«son njĂ« numĂ«r fluturues â float64.
Ădo seri e pĂ«rkohshme identifikohet unikisht me njĂ« çelĂ«s. ĂfarĂ« pĂ«rbĂ«n ky çelĂ«s? Ai pĂ«rbĂ«het nga njĂ« grup i pandashĂ«m çiftĂ«sh çelĂ«s-vlerĂ«.
Ja njĂ« shembull i njĂ« serie tĂ« pĂ«rkohshme. ĂelĂ«si i kĂ«saj serie Ă«shtĂ« njĂ« listĂ« çiftĂ«sh: __name__="cpu_usage" â ky Ă«shtĂ« emri i metrikĂ«s, instance="my-server" â ky Ă«shtĂ« kompjuteri ku Ă«shtĂ« mbledhur kjo metrikĂ«, datacenter="us-east" â ky Ă«shtĂ« qendra e tĂ« dhĂ«nave ku ndodhet ky kompjuter.
Kemi marrĂ« emrin e njĂ« serie tĂ« pĂ«rkohshme, e cila pĂ«rbĂ«het nga tri çiftĂ« çelĂ«s-vlerĂ«. Ky çelĂ«s i korrespondon njĂ« liste çiftĂ«sh (timestamp, value). t1, t3, t3, ..., tN â kĂ«to janĂ« timestamps, 10, 20, 12, ..., 15 â vlerat pĂ«rkatĂ«se. Kjo Ă«shtĂ« pĂ«rdorimi i CPU nĂ« momentin e caktuar pĂ«r kĂ«tĂ« seri.

Ku mund të përdoren seritë e përkohshme? A ka ndokush ide?
- Në DevOps, mund të masim ngarkesën e CPU, RAM, rrjetit, rps, numrin e gabimeve etj.
- IoT â mund tĂ« masim temperaturĂ«n, presionin, koordinatat gjeografike dhe ndonjĂ« gjĂ« tjetĂ«r.
- Po ashtu financat â mund tĂ« monitorojmĂ« çmimet e aksioneve dhe valutave.
- Përveç kësaj, seritë e përkohshme mund të përdoren për monitorimin e proceseve prodhuese në fabrika. Kemi përdorues që përdorin VictoriaMetrics për monitorimin e turbinave të erës, për robotët.
- Gjithashtu, seritë e përkohshme janë të dobishme për grumbullimin e informacionit nga sensorët e pajisjeve të ndryshme. Për shembull, për motorin; për matjen e presionit në pneun; për matjen e shpejtësisë, distancës; për matjen e konsumit të benzinës etj.
- Po ashtu, seritë e përkohshme mund të përdoren për monitorimin e aeroplanëve. Në çdo aeroplan ka një kuti të zezë që mbledh seritë e përkohshme për parametrat e ndryshëm të shëndetit të aeroplanit. Seritë e përkohshme përdoren gjithashtu në industrinë ajrore.
- ShĂ«ndetĂ«sia â kjo pĂ«rfshin presionin e gjakut, pulsin etj.
Ndoshta ka edhe aplikacione të tjera që kam harruar, por shpresoj se kuptuat që seritë e përkohshme përdoren aktivisht në botën moderne. Dhe volumi i përdorimit të tyre po rritet cdo vit.

Për çfarë është e nevojshme një bazë të dhënash për seritë e përkohshme? Pse nuk mund të përdorim një bazë të dhënash racionale për ruajtjen e serive të përkohshme?
Sepse në seritë e kohës zakonisht ka një sasi të madhe informacioni, e cila është e vështirë të ruhet dhe të përpunohet në databaza të zakonshme. Prandaj, janë shfaqur databaza speciale për seritë e kohës. Këto baza ruajnë në mënyrë efektive pikat (timestamp, value) me një çelës të caktuar. Ato ofrojnë API për të lexuar të dhënat e ruajtura sipas çelësit, për një çift çelës-vlerë, ose për disa çifte të tilla, ose për regexp. Për shembull, nëse dëshironi të gjeni ngarkesën e procesorit të të gjitha shërbimeve tuaja në qendrën e të dhënave në Amerikë, duhet të përdorni një kërkesë të tillë.
Zakonisht, databazat për seritë e kohës paraqesin gjuhë kërkese të specializuara, sepse SQL për seritë e kohës nuk është shumë i përshtatshëm. Edhe pse ka databaza që mbështesin SQL, ai nuk është shumë i përshtatshëm. Gjuha më të përshtatshme janë , , , . Shpresoj që dikush të ketë dëgjuar për së paku një nga këto gjuhë. Për PromQL, ndoshta shumë kanë dëgjuar. Kjo është gjuha e kërkesës për Prometheus.

Këtu është si duket arkitektura e një databaze moderne për seritë e kohës, me shembullin e VictoriaMetrics.
Ajo pĂ«rbĂ«het nga dy pjesĂ«. ĂshtĂ« njĂ« depo pĂ«r indeksin e invertuar dhe njĂ« depo pĂ«r vlerat e serive tĂ« kohĂ«s. KĂ«to depo janĂ« tĂ« ndara.
Kur vjen një shënim i ri në databazë, ne së pari kthehemi në indeksin e invertuar për të gjetur identifikuesin e serisë së kohës sipas një grupi të caktuar label=value për këtë metrikë. Gjejmë këtë identifikues dhe ruajmë vlerën në depo të dhënash.
Kur vjen një kërkesë për tërheqjen e të dhënave nga TSDB, ne së pari shohim në indeksin e invertuar. Nxjerrim të gjitha timeseries_ids shënimet që përputhen me këtë grup label=value. Dhe pastaj nxjerrim të gjitha të dhënat e nevojshme nga depoja e të dhënave, e indeksuar sipas timeseries_ids.

Le të shohim një shembull se si databaza për seritë e kohës përpunon një kërkesë të ardhur select.
- Së pari, ajo nxjerr të gjitha
timeseries_idsnga indeksi i invertuar, që përmbajnë çiftet e caktuaralabel=value, ose përmbushin një shprehje të dhënë të rregullt. - Pastaj ajo nxjerr të gjitha pikat e të dhënave nga depoja e dhënash në një interval të caktuar kohor për të gjeturat
timeseries_ids. - Pas kësaj, databaza kryen disa llogaritje mbi këto pika të të dhënave, sipas kërkesës së përdoruesit. Dhe më pas kthen përgjigjen.
NĂ« kĂ«tĂ« prezantim, do t'ju tregoj pĂ«r pjesĂ«n e parĂ«. Kjo Ă«shtĂ« kĂ«rkimi timeseries_ids pĂ«r indeksin e invertuar. Mund tĂ« shihni pjesĂ«n e dytĂ« dhe tĂ« tretĂ« mĂ« vonĂ«. , ose mund tĂ« prisni derisa tĂ« pĂ«rgatis dokumente tĂ« tjera đ

Le të fillojmë me indeksin e invertuar. Disa njerëz mund të mendojnë se është e thjeshtë. Kush e njeh indeksin e invertuar dhe si funksionon? O, tashmë nuk ka shumë njerëz. Le të përpiqemi të kuptojmë se çfarë është.
NĂ« tĂ« vĂ«rtetĂ«, gjithçka Ă«shtĂ« e thjeshtĂ«. ĂshtĂ« njĂ« fjalor qĂ« lidh çelĂ«sin me vlerĂ«n. ĂfarĂ« Ă«shtĂ« çelĂ«si? Kjo çift label=valueindex label dhe vlera â janĂ« fjalĂ«. NĂ«se vlerat janĂ« njĂ« grup timeseries_ids, i cili pĂ«rmban çiftin e caktuar label=value.
Indeksi i invertuar lejon të gjenden shpejt të gjitha timeseries_ids, që kanë label=value.
Gjithashtu, ai lejon të gjeni shpejt timeseries_ids seritë e kohës për disa çifte label=value, ose për çifte label=regexp. Si ndodh kjo? Përmes gjetjes së prerjes së një grumbulli timeseries_ids për çdo çift label=value.

Le të shqyrtojmë realizimet e ndryshme të indeksit të invertuar. Le të fillojmë me realizimin më të thjeshtë naiv. Ai duket kështu.
Funksioni getMetricIDs merr njĂ« listĂ« fjalish. Ădo fjalĂ« pĂ«rmban label=value. Kjo funksion kthen njĂ« listĂ« metricIDs.
Si funksionon? Këtu kemi një variabël globale, e quajtur invertedIndex. Ky është një fjalor i zakonshëm (map), që lidh fjalinë me slice int-i. Fjalia përmban label=value.
Realizimi i funksionit: marrim metricIDs për të parën label=value, pastaj kalojmë në të tjerat label=value, marrim metricIDs për to. Dhe thërrasim funksionin intersectInts, për të cilin do të flitet më vonë. Dhe ky funksion kthen prerjen e këtyre listave.

Siç e shihni, realizimi i indeksit të invertuar nuk është shumë i komplikuar. Por kjo është një realizim naiv. Cilat janë disavantazhet e saj? Disavantazhi kryesor i realizimit naiv është se indeksi i tillë i invertuar ruhet në memorien operative. Pas rinisjes së aplikacionit, ne humbasim këtë indeks. Nuk ka ruajtje të këtij indeksi në disk. Për një bazë të dhënash, një indeks i tillë i invertuar nuk është i përshtatshëm.
Disavantazhi i dytĂ« gjithashtu lidhet me memorien. Indeksi i invertuar duhet tĂ« vendoset nĂ« memorien operative. NĂ«se ai kalon madhĂ«sinĂ« e memories operative, Ă«shtĂ« e qartĂ« se do tĂ« marrim â gabim 'out of memory'. Dhe programi nuk do tĂ« funksionojĂ«.

Kjo problem mund të zgjidhet përmes zgjidhjeve të gatshme si , ose .
Në përmbledhje, na nevojitet një bazë të dhënash që lejon të bëjmë shpejt tre operacione.
- Operacioni i parë është shënimi
çelës-vlerënë këtë bazë. Ajo e bën këtë shumë shpejt, kuçelës-vlerëjanë vargje të rastësishme. - Operacioni i dytë është kërkimi i shpejtë të vlerës sipas çelësit të caktuar.
- Dhe operacioni i tretë është kërkimi i shpejtë të të gjitha vlerave sipas prefixit të caktuar.
LevelDB dhe RocksDB â kĂ«to baza janĂ« zhvilluar nĂ« Google dhe Facebook. NĂ« fillim doli LevelDB. Pastaj ekipi nga Facebook mori LevelDB dhe filloi ta pĂ«rmirĂ«sojĂ«, duke bĂ«rĂ« RocksDB. Tani, nĂ« RocksDB brenda Facebook-ut punojnĂ« pothuajse tĂ« gjitha databazat e brendshme, pĂ«rfshirĂ« se ata kanĂ« transferuar MySQL nĂ« RocksDB. Ata e quajtĂ«n .
Indeksi i invertuar mund tĂ« realizohet me ndihmĂ«n e LevelDB. Si mund ta bĂ«jmĂ« kĂ«tĂ«? RuajmĂ« si çelĂ«s label=value. NdĂ«rsa si vlerĂ« â identifikuesin e serisĂ« kohore, ku ndodhet çifti label=value.
Nëse kemi shumë seri kohore me këtë çift label=value, do të ketë shumë rreshta në këtë bazë të dhënash me çelësa të njëjtë dhe me vlera të ndryshme. timeseries_idsPër të marrë listën e të gjitha timeseries_ids, që fillojnë me këtë label=prefix, ne bëjmë një skanimin e gamës, për të cilën kjo bazë është optimizuar. Kjo do të thotë, ne zgjedhim të gjithë rreshtat, që fillojnë me label=prefix dhe marrim vlerat e nevojshme. timeseries_ids.

Ja një implementim i përafërt, si do të dukej në Go. Ne kemi një indeks të invertuar. Kjo është LevelDB.
Funksioni është i njëjtë, si për implementimin naive. Ajo pothuajse përsërit rresht për rresht implementimin naive. Momentin e vetëm është që në vend të qasjes në map ne qasemi në indeksin e invertuar. Ne nxjerrim të gjitha vlerat për çiftin e parë. label=valuePastaj kalojmë përmes të gjithë çifteve të mbetura label=value dhe nxjerrim grupe të përkatshme të metricIDs për to. Më pas gjejmë kryqëzimin.

Duket gjithçka mirë, por në këtë zgjidhje ka disavantazhe. VictoriaMetrics fillimisht implementoi indeksin e invertuar mbi bazën e LevelDB. Por në fund, u detyrua të heq dorë nga ai.
Pse? Sepse LevelDB Ă«shtĂ« mĂ« i ngadaltĂ« se implementimi naive. NĂ« implementimin naive sipas çelĂ«sit tĂ« caktuar ne nĂ« menjĂ« nxjerrim tĂ« gjithĂ« slice-in metricIDs. Kjo Ă«shtĂ« njĂ« operacion shumĂ« i shpejtĂ« â tĂ« gjithĂ« slice-in Ă«shtĂ« gati pĂ«r t'u pĂ«rdorur.
NĂ« LevelDB, pĂ«r çdo thirrje tĂ« funksionit GetValues duhet tĂ« kalojmĂ« pĂ«rmes tĂ« gjithĂ« rreshtave, qĂ« fillojnĂ« me label=value. Dhe pĂ«r çdo rresht tĂ« nxjerrim vlerĂ«n timeseries_ids. Nga kĂ«ta timeseries_ids tĂ« mbledhim slice-in e kĂ«tyre timeseries_ids. Ăshte e qartĂ« se kjo Ă«shtĂ« shumĂ« mĂ« e ngadaltĂ« se sa thjesht tĂ« qasemi nĂ« njĂ« map tĂ« zakonshĂ«m sipas çelĂ«sit.
Disavantazhi i dytĂ« Ă«shtĂ« se LevelDB Ă«shtĂ« shkruar nĂ« C. Thirrja e funksioneve C nga Go nuk Ă«shtĂ« shumĂ« e shpejtĂ«. Merr qindra nanosekonda. Kjo nuk Ă«shtĂ« shumĂ« e shpejtĂ«, sepse krahasuar me thirrjen e zakonshme tĂ« funksionit tĂ« shkruar nĂ« Go, e cila merr 1-5 nanosekonda, diferenca nĂ« performancĂ« Ă«shtĂ« disa herĂ« mĂ« e lartĂ«. PĂ«r VictoriaMetrics, ky ishte njĂ« disavantazh fatal đ

Prandaj, unë shkrova një implementim të vetin të indeksit të kthyer. Dhe e quajta .
Mergeset është i bazuar në strukturën e të dhënave MergeTree. Kjo strukturë e të dhënave është huazuar nga ClickHouse. Qartë, mergeset duhet të jetë optimizuar për kërkimin e shpejtë timeseries_ids për një çelës të caktuar. Mergeset është shkruar plotësisht në Go. Ju mund të shihni . Implementimi i mergeset ndodhet në dosjen . Ju mund të provoni të kuptoni se çfarë ndodh atje.
API i mergeset është shumë i ngjashëm me LevelDB dhe RocksDB. Pra, ai lejon ruajtjen e shpejtë të regjistrimeve të reja dhe përzgjedhjen e shpejtë të regjistrimeve sipas një prefiksi të caktuar.

Për disavantazhet e mergeset do të flasim më vonë. Tani, do të diskutojmë se cilat probleme u shfaqën me VictoriaMetrics në prodhim gjatë implementimit të indeksit të kthyer.
Pse ndodhi kjo?
Arsyeja e parë është shkalla e lartë e ndryshimit. Në përkthim në shqip - kjo është ndërrimi i shpeshtë i serive të kohës. Kjo ndodh kur një seri e kohës përfundon dhe nis një seri e re, ose fillojnë shumë seri të reja të kohës. Dhe kjo ndodh shpesh.
Arsyeja e dytë është numri i madh i serive të kohës. Fillimisht, kur monitorimi po fitonte popullaritet, numri i serive të kohës ishte i vogël. Për shembull, për çdo computer duhet të monitorohet ngarkesa e procesorit, memories, rrjetit dhe diskut. 4 seri të kohës për çdo computer. Keni, për shembull, 100 kompjuterë dhe 400 seri të kohës. Kjo është shumë pak.
Me kalimin e kohës, njerëzit e shpikën se mund të masin informacione më të detajuara. Për shembull, të maten ngarkesat e çdo bërthame të veçantë të procesorit, jo thjesht të gjithë procesorit. Nëse keni 40 bërthama procesori, atje keni gjithashtu 40 herë më shumë seri të kohës për të matur ngarkesën e procesorit.
Por megjithatĂ«, kjo nuk Ă«shtĂ« gjithçka. Ădo bĂ«rthamĂ« procesori mund tĂ« ketĂ« disa gjendje si idle, kur Ă«shtĂ« nĂ« pritje. Gjithashtu ka punĂ« nĂ« user space, punĂ« nĂ« kernel space dhe gjendje tĂ« tjera. Ădo gjendje e tillĂ« gjithashtu mund tĂ« matet si njĂ« seri e veçantĂ« e kohĂ«s. Kjo rrit nĂ« mĂ«nyrĂ« tĂ« konsiderueshme numrin e serive nĂ« 7-8 herĂ«.
Nga një metrikë kemi marrë 40 x 8 = 320 metrika vetëm për një kompjuter. Duke e shumëfishuar me 100, marim 32,000 në vend të 400.
Pastaj u shfaq Kubernetes. Dhe akoma pĂ«rkeqĂ«soi situatĂ«n, sepse nĂ« Kubernetes mund tĂ« hostohen shumĂ« shĂ«rbime tĂ« ndryshme. Ădo shĂ«rbim nĂ« Kubernetes pĂ«rbĂ«het nga shumĂ« pod, dhe tĂ« gjitha kĂ«to duhet tĂ« monitorohen. PĂ«rveç kĂ«saj, kemi njĂ« deployment tĂ« vazhdueshĂ«m tĂ« versioneve tĂ« reja tĂ« shĂ«rbimeve tuaja. PĂ«r çdo version tĂ« ri, duhet tĂ« krijohen seria tĂ« reja tĂ« kohĂ«s. Si pĂ«rfundim, numri i serive tĂ« kohĂ«s rritet nĂ« mĂ«nyrĂ« eksponenciale dhe ne pĂ«rballemi me problemin e numrit tĂ« madh tĂ« serive tĂ« kohĂ«s, qĂ« quhet high-cardinality. VictoriaMetrics pĂ«rballet me tĂ« nĂ« mĂ«nyrĂ« efektive krahasuar me bazat e tjera tĂ« tĂ« dhĂ«nave pĂ«r seritĂ« e kohĂ«s.

Le të shqyrtojmë më hollësisht high churn rate. Pse ekziston high churn rate në prodhim? Sepse disa vlera të etiketave dhe etiketave ndryshojnë vazhdimisht.
Për shembull, le të marrim Kubernetes, ku ekziston koncepti deployment, dmth. kur ndodh një version i ri i aplikacionit tuaj. Zhvilluesit e Kubernetes për një arsye të panjohur vendosën të shtojnë id e deployment në etiketë.
ĂfarĂ« ndodhi si rezultat? TĂ« gjithĂ« seritĂ« e vjetra tĂ« kohĂ«s ndĂ«rpriten me çdo deployment tĂ« ri, dhe nĂ« vend tĂ« tyre fillojnĂ« seritĂ« e reja tĂ« kohĂ«s me vlerĂ«n e re tĂ« etiketĂ«s deployment_id. Ka mundĂ«si qĂ« tĂ« ketĂ« qindra mijĂ«ra dhe madje miliona tĂ« tilla serish.
NjĂ« karakteristikĂ« e rĂ«ndĂ«sishme e gjithĂ« kĂ«saj Ă«shtĂ« se numri total i serive tĂ« kohĂ«s rritet, por numri i serive tĂ« kohĂ«s qĂ« janĂ« aktualisht aktive, tĂ« cilat marrin tĂ« dhĂ«na, mbetet konstant. Ky gjendje quhet â high churn rate.
Problemi kryesor i high churn rate është të sigurojë një shpejtësi konstante kërkimi për të gjitha seritë e kohës në një grup të caktuar etiketash për një interval të caktuar kohor. Zakonisht, ky është një interval kohor për orët e fundit ose për ditët e fundit.

Si e zgjidhim këtë problem? Këtu është një variant i parë. Kjo është ndarja e indeksit të përmbysur në pjesë të pavarura sipas kohës. Do thotë, kalon një interval kohe, përfundojmë punën me indeksin e përmbysur aktual. Dhe krijojmë një indeks të ri të përmbysur. Kalon një tjetër interval kohe, krijojmë një tjetër dhe një tjetër.
Dhe gjatë zgjedhjes nga këto indekse të përmbysura ne gjejmë një grup indekse të përmbysura që bien brenda intervalit të caktuar. Dhe, përkatësisht, zgjedhim aty id-të e serive temporale.
Kjo lejon kursimin e burimeve, sepse nuk na nevojitet të shqyrtojmë pjesët që nuk bien brenda intervalit të caktuar. Do thotë, zakonisht, nëse po zgjedhim të dhëna për orën e fundit, atëherë për intervalet e mëparshme të kohës ne anashkalojmë kërkesat.

Ka edhe një variant tjetër për zgjidhjen e këtij problemi. Kjo është ruajtja e një liste të veçantë të id-ve të serive temporale për çdo ditë, të cilat u gjendën gjatë atij ditë.
Avantazhi i kësaj zgjidhjeje në krahasim me zgjidhjen e mëparshme është se ne nuk e ripërsërisim informacionin mbi seritë temporale që nuk zhduken me kalimin e kohës. Ato janë gjithmonë aty dhe nuk ndryshojnë.
Disavantazhi është se një zgjidhje e tillë është më e komplikuar në realizim dhe më e vështirë për debug. Dhe VictoriaMetrics e zgjodhi këtë zgjidhje. Kjo ndodhi historikisht. Kjo zgjidhje gjithashtu tregon rezultate të kënaqshme, në krahasim me të parën. Sepse kjo zgjidhje nuk u realizua për shkak se duhej të ripërsëritnim të dhënat në çdo pjesë për seritë temporale që nuk ndryshojnë, domethënë ato që nuk zhduken me kalimin e kohës. VictoriaMetrics është optimizuar fillimisht për konsumimin e hapësirës në disk, dhe realizimi i mëparshëm e përkeqësoi këtë konsum. Ndërsa kjo realizim është më i përshtatshëm për minimizimin e konsumit të hapësirës në disk, prandaj u zgjodh.
Duhej të përballem me të. Lufta qëndronte në atë se në këtë realizim duhet të përzgjedhësh gjithmonë një numër shumë më të madh timeseries_ids për të dhënat, sesa kur indeksi i përmbysur është i ndarë sipas kohës.

Si e zgjidhĂ«m kĂ«tĂ« problem? E zgjodhĂ«m nĂ« njĂ« mĂ«nyrĂ« origjinale â duke ruajtur disa identifikues tĂ« serive temporale nĂ« secilĂ«n tĂ« dhĂ«nĂ« tĂ« indeksit tĂ« pĂ«rmbysur nĂ« vend tĂ« njĂ« identifikuesi tĂ« vetĂ«m. Do thotĂ«, ne kemi njĂ« çelĂ«s label=value, qĂ« ndodh nĂ« çdo rresht temporal. Dhe tani ne ruajmĂ« disa timeseries_ids nĂ« njĂ« regjistĂ«r.
Ja një shembull. Më parë kishim N regjistra, ndërsa tani kemi një regjistër, me një prefiks të njëjtë si të tjerët. Regjistri i mëparshëm kishte një vlerë që përmbante të gjitha id-të e rreshtave temporale.
Kjo ka lejuar rritjen e shpejtësisë së skanimit të këtij indeksi të përmbysur deri në 10 herë. Dhe ka lejuar reduktimin e konsumit të memories për cache, sepse tani ne ruajmë vargun label=value vetëm një herë në cache së bashku me N herë. Dhe ky varg mund të jetë i madh, nëse keni rreshta të gjatë në etiketa dhe labels që i pëlqen t'i fusë atje Kubernetes.

NjĂ« variant tjetĂ«r pĂ«rshpejtimi i kĂ«rkimit nĂ« indeksin e pĂ«rmbysur Ă«shtĂ« sharding. Krijimi i indekseve tĂ« shumta tĂ« pĂ«rmbysura nĂ« vend tĂ« njĂ« dhe sharding tĂ« tĂ« dhĂ«nave midis tyre sipas njĂ« çelĂ«si. Ky Ă«shtĂ« njĂ« grup key=value çiftash. Pra, ne kemi disa indekse tĂ« pavarura tĂ« pĂ«rmbysura, tĂ« cilat mund tâi marrim nĂ« pyetje paralelisht nĂ« disa procesorĂ«. Realizimet e mĂ«parshme lejonin punĂ«n vetĂ«m nĂ« modin njĂ«-procesor, dmth. skanimin e tĂ« dhĂ«nave vetĂ«m nĂ« njĂ« bĂ«rthamĂ«. Ă solution i jep mundĂ«sinĂ« pĂ«r tĂ« skanuar tĂ« dhĂ«nat menjĂ«herĂ« nĂ« disa bĂ«rthama, siç i pĂ«lqen tĂ« bĂ«jĂ« ClickHouse. KĂ«tĂ« planifikojmĂ« ta implementojmĂ«.

Dhe tani le tĂ« kthehemi te dhitĂ« tona â tek funksioni i ndĂ«rprerjes timeseries_ids. Le tĂ« shohim se cilat mund tĂ« jenĂ« realizimet. Ky funksion lejon tĂ« gjejmĂ« timeseries_ids pĂ«r njĂ« grup tĂ« caktuar label=value.

Varianti i parĂ« â Ă«shtĂ« realizimi ingenuoz. Dy cikle tĂ« ngjitur. Ja ne marrim nĂ« input tĂ« funksionit intersectInts dy slices â a dhe b. NĂ« daljen e saj ajo duhet tĂ« na kthejĂ« ndĂ«rprerjen e kĂ«tyre slices.
Realizimi ingenuoz duket kështu. Ne kalojmë përmes të gjitha vlerave nga slice a, brenda këtij cikli kalojmë përmes të gjitha vlerave të slice b. Dhe i krahasojmë ato. Nëse ata përputhen, atëherë kemi gjetur ndërprerjen. Dhe e ruajmë në rezultati.

Cilat janĂ« disavantazhet? Kompleksiteti katror â Ă«shtĂ« disavantazhi kryesor i saj. PĂ«r shembull, nĂ«se keni dimensionet e slice a dhe b me njĂ« milion, atĂ«herĂ« ky funksion kurrĂ« nuk do t'ju kthejĂ« njĂ« pĂ«rgjigje. Sepse i nevojitet njĂ« trilion iteracionesh, qĂ« Ă«shtĂ« shumĂ« edhe pĂ«r kompjuterĂ«t modernĂ«.

Realizimi i dytĂ« bazohet nĂ« hartĂ«. Ne krijojmĂ« njĂ« hartĂ«. Vendosim nĂ« kĂ«tĂ« hartĂ« tĂ« gjitha vlerat nga slice a. Pastaj kalojmĂ« me njĂ« cikĂ«l tĂ« veçantĂ« nĂ« slice b. Dhe kontrollojmĂ« â a ekziston kjo vlerĂ« nga slice b nĂ« map. NĂ«se Ă«shtĂ« atje, atĂ«herĂ« e shtojmĂ« nĂ« rezultat.

Cilat janë përparësitë? Përparësia është se këtu ka vetëm kompleksitet lineare. Kjo do të thotë se funksioni do të ekzekutohet shumë më shpejt për madhësi të mëdha slices. Për një slice me madhësi një milion, ky funksion do të ekzekutohet në 2 milion iteracione, ndryshe nga një trilion iteracione, si në funksionin e mëparshëm.
Ndërsa një disavantazh është se ky funksion kërkon më shumë memorie për të krijuar këtë map.
Disavantazhi i dytĂ« â Ă«shtĂ« overhead i madh nĂ« hashim. Ky disavantazh nuk Ă«shtĂ« shumĂ« i dukshĂ«m. Dhe pĂ«r ne nuk ishte as shumĂ« i dukshĂ«m, prandaj nĂ« fillim, nĂ« VictoriaMetrics, realizimi i bashkĂ«ngjitjes ishte pĂ«rmes map. Por mĂ« pas profilizimi tregoi se koha kryesore e procesorit shpenzohej pĂ«r tĂ« shkruar nĂ« map dhe pĂ«r tĂ« kontrolluar nĂ«se vlera Ă«shtĂ« nĂ« kĂ«tĂ« map.
Pse në këto vende shpenzohet kohë procesori? Sepse në këto rreshta Go kryen operacionin e hashimit. Kjo do të thotë se ai llogarit hash-in nga çelësi, për t'u referuar më pas në indeksin e caktuar në HashMap. Operacioni i llogaritjes së hash-it kryhet në dhjetra nanosekonda. Kjo është e ngadalshme për VictoriaMetrics.

Unë vendosa të implementoj bitset, optimizuar veçanërisht për këtë rast. Ja si duket tani bashkëngjitja e dy slices. Këtu krijojmë një bitset. Shtojmë elementet nga slice i parë. Më pas kontrollojmë praninë e këtyre elementeve në slice-in e dytë. Dhe i shtojmë në rezultat. Kjo do të thotë se pothuajse nuk ndryshon nga shembulli i mëparshëm. E vetmja gjë që ne këtu zëvendësuam duhet të jetë referimi në map me funksione të personalizuara. add dhe ka.

Nga një shikim të parë duket se kjo duhet të funksionojë më ngadalë, nëse më parë ishte përdorur një map standard, dhe tani thirren ndonjë funksion, por profilizimi tregon se kjo gjë punon 10 herë më shpejt se map standard për rastin me VictoriaMetrics.
Përveç kësaj, ajo përdor shumë më pak memorie në krahasim me realizimin në map. Sepse ne ruajmë këtu bitet në vend të vlerave tetëbyte.
Disavantazhi i kësaj realizimi është se nuk është aq e dukshme, jo triviale.
Një tjetër disavantazh që shumë njerëz mund të mos e vënë re është se kjo zbatim mund të funksionojë keq në disa raste. Domethënë, është optimizuar për një rast të caktuar, për rastin e përputhjes së identiteteve të serive temporale të VictoriaMetrics. Kjo nuk do të thotë se ajo do të jetë e përshtatshme për të gjitha rastet. Nëse përdoret gabimisht, do të kemi jo një përmirësim të performancës, por një gabim out of memory dhe ngadalësim të performancës.

Le të shqyrtojmë zbatimin e kësaj strukture. Nëse doni ta shihni, është në burimet e VictoriaMetrics, në dosjen . Ajo është optimizuar pikërisht për rastin e VictoriaMetrics, ku timeseries_id është një vlerë 64-bit, ku 32 bitët e parë janë kryesisht konstantë dhe ndryshojnë vetëm 32 bitët e fundit.
Kjo strukturë të dhënash nuk ruhet në disk, ajo funksionon vetëm në memorie.

Këtu është API-i i saj. Ai nuk është shumë i komplikuar. API-u është adaptuar pikërisht për shembullin specifik të përdorimit të VictoriaMetrics. Domethënë, këtu nuk ka funksione të tepërta. Këtu janë funksionet që përdoren qartë nga VictoriaMetrics.
Ka funksione add, që shton vlera të reja. Ka një funksion ka, që kontrollon vlerat e reja. Dhe ka një funksion del, që heq vlera. Ka një funksion ndihmës len, që kthen madhësinë e grupit. Funksioni clone klonon grupin. Dhe funksioni appendto e ndryshon këtë grup në slice timeseries_ids.

Ja si duket implementimi i kësaj strukture të dhënash. Në set ka dy elemente:
ItemsCountâ ky Ă«shtĂ« njĂ« fushĂ« ndihmĂ«se, pĂ«r tĂ« kthyer shpejt numrin e elementeve nĂ« set. Mund tĂ« ishte hequr ky fushĂ« ndihmĂ«se, por duhej ta shtonim kĂ«tu, sepse VictoriaMetrics shpesh pyet nĂ« algoritmet e saj pĂ«r gjatĂ«si bitset.Fusha e dytĂ« Ă«shtĂ«
buckets. Ky është një slice i strukturësbucket32. Në çdo strukturë ruhethifusha. Këto janë 32 bitët e sipërm. Dhe dy slice -b16hisdhebucketsngabucket16struktura.
Këtu ruhen 16 bitët e sipërm të pjesës së dytë të strukturës 64-bit. Dhe këtu ruhen bitsets për 16 bitët më të rinj të çdo byte.
Bucket64 është i përbërë nga një array uint64. Gjatesia llogaritet duke përdorur këto konstanta. Në një bucket16 maksimumi mund të ruajë 2^16=65536 bït. Nëse e ndajmë atë me 8, kjo është 8 kilobajt. Nëse e ndajmë përsëri me 8, kjo është 1000 uint64 në vlerë. Domethënë, Bucket16 është një strukturë 8-kilobajtëshe.

Le të shohim se si është implementuar një nga metodat e kësaj strukture për shtimin e një vlerë të re.
Të gjitha fillon me uint64 vlerave. Llogarisim 32 bitët e sipërm, llogarisim 32 bitët e poshtëm. Kalojmë përmes të gjithë buckets. Krahasojmë 32 bitët e sipërm në secilën bucket me vlerën që po shtohet. Dhe nëse ato përputhen, atëherë thërrasim funksionin add në strukturën b32 buckets. Dhe shtojmë aty 32 bitët e poshtëm. Dhe nëse kjo ktheu true, atëherë do të thotë se e kemi shtuar atë vlerë aty dhe nuk e kishim një të tillë. Nëse ajo kthen false, atëherë një vlerë e tillë tashmë ekzistonte. Më pas rrisim numrin e elementëve në strukturë.
Nëse nuk gjetëm të nevojshmen bucket me vlerën hi-përkatëse, atëherë thërrasim funksionin addAlloc, i cili alokon një të ri bucket, duke e shtuar atë në strukturën bucket.

Kjo është implementimi i funksionit b32.add. Ajo është e ngjashme me implementimin e mëparshëm. Ne llogarisim 16 bitët e vjetër, 16 bitët e rinj.
Më pas kalojmë përmes të gjithë 16 bitëve të sipërm. Gjejmë përputhje. Dhe në rast përputhjeje thërrasim metodën add, të cilën do ta shqyrtojmë në faqen e ardhshme për bucket16.

Dhe këtu është niveli më i ulët, i cili duhet të jetë maksimalisht i optimizuar. Ne llogarisim për uint64 id vlerën në slice bit, si dhe bitmask. Kjo është një maskë për këtë vlerë 64-bit, me të cilën mund të kontrollojmë pranishmërinë e këtij biti, ose ta vendosim atë. Ne kontrollojmë nëse ky bit është i vendosur dhe e vendosim, dhe kthejmë pranishmërinë. Kjo është implementimi ynë, i cili ka lejuar të shpejtojë operacionin e ndërprerjes së ids të serive kohore 10 herë krahasuar me maps standarde.

Në VictoriaMetrics përveç kësaj optimizimi ka shumë optimizime të tjera. Shumica e këtyre optimizimeve janë shtuar jo thjesht kështu, por pas profilizimit të kodit në produksion.
Ky Ă«shtĂ« rregulli kryesor i optimizimit â mos shtoni optimizimin, duke supozuar se kĂ«tu do tĂ« ketĂ« njĂ« pikĂ« tĂ« ngushtĂ«, sepse mund tĂ« rezultojĂ« se nuk do tĂ« ketĂ« njĂ« pikĂ« tĂ« ngushtĂ« atje. Optimizimi zakonisht pĂ«rkeqĂ«son cilĂ«sinĂ« e kodit. Prandaj, Ă«shtĂ« e nevojshme tĂ« optimizohet vetĂ«m pas profilizimit dhe preferohet nĂ« produksion, nĂ« mĂ«nyrĂ« qĂ« tĂ« jenĂ« tĂ« dhĂ«na reale. AtĂ«herĂ«, ata qĂ« janĂ« tĂ« interesuar, mund tĂ« shikojnĂ« burimet e VictoriaMetrics dhe tĂ« studiojnĂ« optimizimet e tjera qĂ« ka.

Kam një pyetje në lidhje me bitset. Ka shumë ngjashmëri me implementimin e C++ vector bool, bitset i optimizuar. E keni marrë implementimin nga atje?
Jo, nuk është prej aty. Kur po realizoja këtë bitset, unë u orientova në njohuritë e strukturës së këtyre ids timeseries që përdoren në VictoriaMetrics. Strukturën e tyre është e tillë që 32 bitët e sipërm janë kryesisht të qëndrueshëm. 32 bitët e poshtëm mund të ndryshojnë. Sa më poshtë të jetë bita, aq më shpesh mund të ndodhi ndryshimi. Prandaj, kjo realizim është pikërisht optimizuar për këtë strukturë të dhënash. Realizimi në C++, aq sa di unë, është optimizuar për rastin e përgjithshëm. Nëse bëhet optimizimi për rastin e përgjithshëm, atëherë kjo do të thotë se nuk do të jetë më optimal për rastin specifik.
Ju rekomandoj gjithashtu të shikoni ligjëratën e Aleksej Milovidit. Ai rreth një muaj më parë fliste për optimizimet në ClickHouse për specializime specifike. Ai vërtet tregon se në rastin e përgjithshëm implementimi në C++ ose ndonjë implementim tjetër janë të punuar për të funksionuar mirë në mesatare. Ajo mund të funksionojë më keq se një implementim i specializuar për njohuri specifike, siç jemi ne, kur e dimë se 32 bitët e sipërm janë kryesisht të qëndrueshëm.
Kam një pyetje tjetër. Cila është dallimi i theksuar nga InfluxDB?
Janë shumë dallime të theksuara. Nëse flasim për performancën dhe konsumimin e memories, InfluxDB në testime tregon dhjetë herë më shumë konsumim të memories për seritë temporale me kardinalitet të lartë kur keni shumë, për shembull, miliona. Për shembull, VictoriaMetrics konsumon 1 GB për një milion seri aktive, ndërsa InfluxDB në këtë rast konsumon 10 Gb. Dhe kjo është një ndryshim i madh.
Dallimi i dytĂ« i theksuar Ă«shtĂ« se nĂ« InfluxDB ka gjuhĂ« tĂ« çuditshme pyetjesh â Flux dhe InfluxQL. Ato nuk janĂ« shumĂ« tĂ« pĂ«rshtatshme pĂ«r punĂ« me seritĂ« temporale nĂ« krahasim me , qĂ« mbĂ«shtetet nĂ« VictoriaMetrics. PromQL Ă«shtĂ« gjuha e pyetjeve nga Prometheus.
Dhe njĂ« dallim tjetĂ«r Ă«shtĂ« se InfluxDB ka njĂ« model tĂ« çuditshĂ«m tĂ« tĂ« dhĂ«nave, ku nĂ« çdo rresht mund tĂ« mbahen disa fushĂ« me grupe tĂ« ndryshme tag-esh. KĂ«to rreshta ndahen edhe nĂ« tabela tĂ« ndryshme. KĂ«to komplikime shtesĂ« e bĂ«jnĂ« mĂ« tĂ« vĂ«shtirĂ« punĂ«n e mĂ«vonshme me kĂ«tĂ« bazĂ«. ĂshtĂ« e vĂ«shtirĂ« pĂ«r t'u mbajtur dhe kuptuar.
NĂ« VictoriaMetrics gjithçka Ă«shtĂ« shumĂ« mĂ« e thjeshtĂ«. Atje çdo seri temporale pĂ«rfaqĂ«son njĂ« çelĂ«s-vlerĂ«. Vlera Ă«shtĂ« njĂ« set pikash â (timestamp, value), ndĂ«rsa çelĂ«si Ă«shtĂ« njĂ« set label=value. Nuk ka ndarje mes fushave dhe matjeve. Kjo ju lejon tĂ« zgjidhni tĂ« dhĂ«na tĂ« çfarĂ«do lloji dhe mĂ« pas t'i kombinoni, tĂ« shtoni, tĂ« zbritni, tĂ« shumĂ«zoni, tĂ« ndani, ndryshe nga InfluxDB, ku llogaritjet midis rreshtave tĂ« ndryshĂ«m nuk janĂ« ende realizuar, sa kam dijeni. NĂ«se janĂ« realizuar, ato janĂ« tĂ« komplikuara, duhet tĂ« shkruani shumĂ« kode.
Kam një pyetje sqaruese. A e kuptova saktë që kishte ndonjë problem, për të cilin flisnit, se indeksi i invers nuk i ndalon në memorie, prandaj aty bëhet particionimi?
Fillimisht, tregova një implementim naive të indeksit të invers në standardin Go map. Një implementim i tillë nuk është i përshtatshëm për bazat e të dhënave, sepse ky indeks i invers nuk ruhet në disk, dhe një bazë të dhënash duhet të ruajë në disk, për të siguruar që këto të dhëna të mbeten të aksesueshme pas rinisjes. Në këtë implementim, pasi aplikacioni riniset, indeksi i invers do të humbasë. Dhe do të humbni qasjen në të gjitha të dhënat, sepse nuk do të jeni në gjendje t'i gjeni ato.
Përshëndetje! Faleminderit për prezantimin! Unë quhem Pavel. Jam nga kompania Wildberries. Kam disa pyetje për ju. Pyetja e parë. Si mendoni, nëse do të kishit zgjedhur një parim tjetër në ndërtimin e arkitekturës së aplikacionit tuaj dhe do të ishit partiçionuar të dhënat sipas kohës, ndoshta do të kishit mundur të realizonit ndërhapësira të të dhënave gjatë kërkimit, duke u bazuar vetëm në faktin se në një partiçion ndodhen të dhëna për një periudhë kohore, dmth. për një interval kohor dhe nuk do t'ju duhej të shqetësoheshit për faktin se keni pjesë të shpërndara në mënyrë të ndryshme? Pyetja numër 2 - nëse realizoni një algoritëm të tillë me bitset dhe gjithçka tjetër, ndoshta keni provuar të përdorni instruksionet e procesorit? Mund të keni provuar optimizime të tilla?
Për pyetjen e dytë do t'ju përgjigjem menjëherë. Ne nuk kemi arritur ende në këtë. Por nëse do të jetë e nevojshme, do të arrijmë. A ishte e para, çfarë ishte pyetja?
Diskutuat dy skenarë. Dhe thatë se zgjodhët të dytin me një implementim më të komplikuar. Dhe nuk e përzgjodhët të parin, ku të dhënat janë partiçionuar sipas kohës.
Po, në rastin e parë, volumi total i indeksit do të ishte më i madh, sepse në çdo parti ne do të duhej të ruanim kopje të dhënash për ato seritë kohore që vazhdojnë përmes këtyre të gjitha partive. Dhe nëse ka një shkallë të ulët të ndryshimit në seritë kohore, pra, vazhdimisht përdoren të njëjtat seri, atëherë në rastin e parë do të humbnim ndjeshëm më shumë hapësirë diskore krahasuar me rastin e dytë.
Po, ndarja sipas kohës është një opsion i mirë. Kjo e përdor Prometheus. Por në Prometheus ka një mangësi tjetër. Kur bashkohen këto copa të dhënash, kërkohet të ruajë në memorje metainformacione për të gjitha etiketat dhe seritë kohore. Prandaj, nëse copat e dhënash janë të mëdha që ai i bashkon, përdorimi i memorjes rritet shumë gjatë bashkimit, ndryshe nga VictoriaMetrics. VictoriaMetrics gjatë bashkimit nuk konsumon fare memorje, përdoren disa kilobajtë, pavarësisht nga përmasat e copave të bashkuara të dhënash.
Algoritmi që përdorni konsumon memorje. Aty regjistrohen etiketat e serive kohore që kanë vlera. Dhe kështu kontrolloni praninë e dyfisht në një grup të dhënash dhe në tjetrin. Dhe kuptoni - ndodhi përputhja apo jo. Zakonisht në bazat e të dhënave implemtohen kursore, iterators, që ruajnë gjendjen e tyre aktuale dhe kalojnë nëpër të dhënat e renditura, për shkak të kësaj, keni një kompleksitet të thjeshtë në këto operacione.
Pse nuk përdorim kursore për përputhjen e të dhënave?
Po.
Në LevelDB ose në mergeset ruhen pikërisht rreshtat e renditur. Mund të kalojmë me një kursor dhe të gjejmë përputhjen. Por pse nuk e përdorim? Sepse - është e ngadaltë. Sepse kursoret nënkuptojnë se për çdo rresht duhet të thërrasësh një funksion. Thirrja e funksionit - është 5 nanosekonda. Dhe nëse keni 100,000,000 rreshta, rezulton se ne shpenzojmë një të pestën e sekondës vetëm për thirrjen e funksionit.
Kjo ndodh, po. Dhe pyetja ime e fundit. Pyetja, ndoshta, mund të duket pak e çuditshme. Pse në momentin e pranimit të të dhënave nuk mund të llogariten të gjitha agregatet e nevojshme dhe të ruhen në formën e nevojshme? Pse të ruani volume të mëdha në sisteme si VictoriaMetrics, ClickHouse, etj., në mënyrë që pastaj të shpenzoni shumë kohë për to?
Më lejoni të jap një shembull për ta bërë më të qartë. Supozoni, si funksionon një spidometër i vogël lodër? Ai regjistron distancën që keni kaluar, duke e shtuar atë vazhdimisht në një vlerë, dhe në vlerën tjetër - kohën. Pastaj bën ndarjen për të marrë shpejtësinë mesatare. Mund të bëni diçka të ngjashme. Shtoni në fluturim të gjitha faktet e nevojshme.
Mirë, kuptova pyetjen. Shembulli juaj ka vend në jetë. Nëse e dini se cilat agregate ju duhen, atëherë kjo është zbatimi më i mirë. Por problemi është se njerëzit ruajnë këto metrika, disa të dhëna në ClickHouse dhe ata nuk e dinë ende se si do t'i agregojnë dhe filtrojnë ato në të ardhmen, prandaj duhet të ruajnë të gjitha të dhënat e papërpunuara. Por nëse e dini se ju nevojitet të llogaritni diçka mesatare, atëherë përse të mos e llogaritni atë, në vend që të ruani një sasi të madhe vlerash të papërpunuara? Por kjo është vetëm në rastin kur e dini saktësisht se çfarë ju nevojitet.
Për më tepër, bazat për ruajtjen e serive të kohës mbështesin llogaritjen e agregateve. Për shembull, Prometheus mbështet . Kjo do të thotë se është e mundur ta bëni këtë, nëse e dini se cilat agregate do t'ju nevojiten. Në VictoriaMetrics kjo ende nuk ekziston, por zakonisht para saj vendoset Prometheus, ku mund ta bëni këtë në rregullat e regjistrimit.
Për shembull, në punën time të mëparshme duhej të llogarisja numrin e ngjarjeve në një dritare që lëviz për orën e fundit. Problemi ishte se duhej të përfundoja një implementim të personalizuar në Go, dmth., një shërbim për të llogaritur këtë gjë. Ky shërbim përfundimisht doli të ishte jo triviale, sepse është e vështirë të llogaritet. Një implementim mund të jetë i thjeshtë, nëse keni nevojë të llogaritni disa agregate në intervale fikse kohore. Nëse dëshironi të llogaritni ngjarjet në një dritare lëvizëse, atëherë kjo nuk është aq e lehtë sa duket. Mendoj se kjo nuk është realizuar ende në ClickHouse ose në bazat e të dhënave të serive të kohës, sepse është e komplikuar për t'u realizuar.
Dhe një pyetje tjetër. Tani po flisnim për mesataren, dhe më erdhi në mend që dikur kishte një gjë si Graphite me backend Carbon. Ai dinte të filtrojë të dhënat e vjetra, dmth. të linte një pikë për minutë, një pikë për orë dhe kështu me radhë. Në fakt, është mjaft e përshtatshme, nëse na duhen të dhëna të papërpunuara, thjesht për muajin, ndërsa gjithçka tjetër mund të filtrohet. Por Prometheus dhe VictoriaMetrics nuk e mbështesin këtë funksionalitet. A është planifikuar të mbështetet? Nëse jo, atëherë pse?
Faleminderit për pyetjen. Përdoruesit tanë e bëjnë këtë pyetje herë pas here. Ata kërkojnë të dinë kur do të shtojmë mbështetje për ngjeshjen e të dhënave (downsampling). Këtu ka disa probleme. Së pari, çdo përdorues ka kuptimin e tij për downsampling duke kërkuar një pikë të caktuar në një interval të caktuar, dikush dëshiron vlerat maksimale, minimale ose mesatare. Nëse në bazën tuaj janë duke i shkruar të dhëna shumë sisteme, nuk mund t'i trajtoni ato njëlloj. Mund të ndodhë që për çdo sistem të nevojitet përdorimi i një mënyre të ndryshme ngjeshjeje. Dhe kjo është e vështirë për t'u realizuar.
Dhe e dyta â Ă«shtĂ« qĂ« VictoriaMetrics, ashtu si ClickHouse, Ă«shtĂ« optimizuar pĂ«r tĂ« punuar me njĂ« volum tĂ« madh tĂ« dhĂ«nash tĂ« papĂ«rpunuara, kĂ«shtu qĂ« ajo mund tĂ« analizoje njĂ« miliard rreshta pĂ«r mĂ« pak se njĂ« sekondĂ«, nĂ«se keni shumĂ« bĂ«rthama nĂ« sistemin tuaj. Skarimi i pikave tĂ« serisĂ« temporale nĂ« VictoriaMetrics Ă«shtĂ« 50,000,000 pika nĂ« sekondĂ« pĂ«r njĂ« bĂ«rthamĂ«. Dhe kjo performancĂ« shkallĂ«zohet nĂ« bĂ«rthamat e disponueshme. Pra, nĂ«se keni 20 bĂ«rthama, pĂ«r shembull, do tĂ« arrini tĂ« analizoni njĂ« miliard pikash nĂ« sekondĂ«. Dhe kjo veçori e VictoriaMetrics dhe ClickHouse zvogĂ«lon nevojĂ«n pĂ«r downsampling.
NjĂ« tjetĂ«r veçori Ă«shtĂ« se VictoriaMetrics kompreson kĂ«to tĂ« dhĂ«na nĂ« mĂ«nyrĂ« efikase. Kompresimi mesatar nĂ« prodhim Ă«shtĂ« nga 0.4 deri nĂ« 0.8 byte pĂ«r pikĂ«. Ădo pikĂ« pĂ«rbĂ«het nga timestamp + vlera. Dhe ajo kompresohet mĂ« pak se njĂ« byte nĂ« mesatare.
Sergei. Kam një pyetje. Cili është kvanti minimal i kohës së regjistrimit?
NjĂ« milisekondĂ«. SĂ« fundmi, kishim njĂ« bisedĂ« me zhvillues tĂ« tjerĂ« tĂ« bazave tĂ« dhĂ«nash pĂ«r seritĂ« temporale. Kvanti minimal i tyre kohor Ă«shtĂ« njĂ« sekondĂ«. Edhe nĂ« Graphite, pĂ«r shembull, Ă«shtĂ« njĂ« sekondĂ«. NĂ« OpenTSDB gjithashtu Ă«shtĂ« njĂ« sekondĂ«. NĂ« InfluxDB â pikĂ«sia Ă«shtĂ« nĂ« nanosekonda. NĂ« VictoriaMetrics â Ă«shtĂ« njĂ« milisekondĂ«, sepse nĂ« Prometheus Ă«shtĂ« njĂ« milisekondĂ«. Dhe VictoriaMetrics Ă«shtĂ« zhvilluar si ruajtje e largĂ«t pĂ«r Prometheus fillimisht. Por tani ajo mund tĂ« ruajĂ« tĂ« dhĂ«na edhe nga sisteme tĂ« tjera.
Njeriu me tĂ« cilin bisedova thotĂ« se ndajnĂ« njĂ« pikĂ«si sekondĂ« â kjo Ă«shtĂ« e mjaftueshme pĂ«r ta, sepse varet nga lloji i tĂ« dhĂ«nave qĂ« ruhen nĂ« bazĂ«n e tĂ« dhĂ«nave temporale. NĂ«se janĂ« tĂ« dhĂ«na DevOps ose tĂ« dhĂ«na nga infrastruktura, ku i mbledhni ato me intervale prej 30 sekondash deri nĂ« njĂ« minutĂ«, atĂ«herĂ« njĂ« pikĂ«si sekondĂ« Ă«shtĂ« e mjaftueshme, mĂ« pak nuk Ă«shtĂ« e nevojshme. Por nĂ«se po mbledhni kĂ«to tĂ« dhĂ«na nga sisteme tregtimi me frekuencĂ« tĂ« lartĂ«, atĂ«herĂ« nevojitet pikĂ«si nĂ« nanosekonda.
Saktësia në milisekonda në VictoriaMetrics është e përshtatshme si për rastin e DevOps, ashtu edhe për shumicën e rasteve që përmenda në fillim të raportit. E vetmja gjë për të cilën ajo mund të mos jetë e përshtatshme është për sistemet e tregtisë me frekuencë të lartë.
Faleminderit! Dhe një pyetje tjetër. Cila është përputhshmëria në PromQL?
Përputhshmëria e plotë retroaktive. VictoriaMetrics mbështet plotësisht PromQL. Për më tepër, ajo shton një funksionalitet të zgjeruar mbi PromQL, i cili quhet . Për këtë funksionalitet të zgjeruar ka një raport në YouTube. E kam përshkruar në Monitoring Meetup pranverën e kaluar në Shën Petersburg.
Kanali i Telegramit .
Vetëm përdoruesit e regjistruar mund të marrin pjesë në anketë. , ju lutem.
ĂfarĂ« ju pengon tĂ« kaloni nĂ« VictoriaMetrics si njĂ« magazinĂ« afatgjatĂ« pĂ«r Prometheus? (Shkruani nĂ« komentet, do ta shtoj nĂ« ankete))
71,4%Nuk po përdor Prometheus5
28,6%Nuk e dija për VictoriaMetrics2
7 përdorues kanë votuar. 12 përdorues janë abstenuar.
Burimi: habr.com
