Soovitan tutvuda 2019. aasta lÔpudokumendi Aleksandr Valjalkini "Go optimeerimised VictoriaMetricsis" tÔlgendusega.
â kiire ja skaleeritav andmebaas ajaseeriaandmete salvestamiseks ja töötlemiseks (salvestus loob aja ja vastava ajaga seotud vÀÀrtuste kogumi, nĂ€iteks sensorite oleku perioodilise kĂŒsitluse vĂ”i statistikate kogumise kaudu saadud vÀÀrtused).

Siin on link selle dokumendi video juurde â

RÀÀgin natuke endast. Mina olen Aleksandr Valjalkin. Siin on . Mulle meeldib Go ja jÔudluse optimeerimine. Olen kirjutanud palju erinevaid kasulikke ja vÀhem kasulikke teeke. Need algavad kas kiire, or with vÀga kiire eelvormiga.
Hetkel töötan VictoriaMetricsi kallal. Mis see on ja mida ma seal teen? Sellest rÀÀgin oma esitlusel.

Dokumendi plaan on jÀrgmine:
- Esmalt rÀÀgin teile, mis on VictoriaMetrics.
- Siis rÀÀgin, mis on ajaseeriate andmed.
- SeejÀrel rÀÀgin, kuidas ajaseeria andmebaas töötab.
- JÀrgmisena rÀÀgin andmebaasi arhitektuurist: millest see koosneb.
- Ja lÔpuks rÀÀgin VictoriaMetricsi optimeerimisest. Need on optimeerimine pöördindeksile ja bitseti rakenduse optimeerimine Go-s.

Mis on VictoriaMetrics? Kas keegi publikust teab? Vau, juba palju inimesi teab. See on hea uudis. Neile, kes ei tea - see on ajasĂŒsteemide andmebaas. See pĂ”hineb ClickHouse arhitektuuril ja mĂ”ningatel ClickHouse'i rakenduse detailidel. NĂ€iteks sellistel nagu: MergeTree, paralleelse töötlemise kasutamine kĂ”igil saadaval olevatel protsessorituumadel ja jĂ”udluse optimeerimine andmeplokkide töötlemise kaudu, mis salvestatakse protsessori vahemĂ€lu.
VictoriaMetrics pakub andmete parimat tihendust vĂ”rreldes teiste ajasĂŒsteemide andmebaasidega.
See skaleerub vertikaalselt - st saate lisada rohkem protsessoreid, rohkem mĂ€lu ĂŒhte arvutisse. VictoriaMetrics suudab neid ressursse tĂ”husalt kasutada ja parandada lineaarselt jĂ”udlust.
Samuti skaleerub VictoriaMetrics horitontaalselt - st saate lisada VictoriaMetricsi klastrisse tÀiendavaid sÔlmi ja selle jÔudlus kasvab peaaegu lineaarselt.
Kuidas te arvate, VictoriaMetrics on kiire andmebaas, sest ma ei saa rÀÀkida teistest. Ja see on kirjutatud Go keeles, seetÔttu rÀÀgin sellest kohtumisel.

Kes teab, mis on ajavarras? Paljud inimesed teavad. Ajavarras on paaride seeria (timestamp, vÀÀrtus), kus need paarid on jĂ€rjestatud aja jĂ€rgi. VÀÀrtus esindab end floating-point numbrit â float64.
Iga ajavarras identifitseeritakse ainulaadse vĂ”tmega. Mis sellest vĂ”tmetest koosneb? See koosneb mitte-tĂŒhjast paaride komplektist vĂ”tme-vÀÀrtuse.
Siin on ajavarra nĂ€ide. Selle varda vĂ”ti on paaride nimekiri: __name__="cpu_usage" â see on mÔÔtme nimi, instance="my-server" â see on arvuti, kus see mÔÔtmine on kogutud, datacenter="us-east" â see on andmekeskus, kus see arvuti asub.
Saime ajavarra nime, mis koosneb kolmest vĂ”tme-vÀÀrtuse paarist. Selle vĂ”tme juurde kuulub paaride nimekiri (timestamp, value). t1, t3, t3, ..., tN â need on ajatemperatuurid, 10, 20, 12, ..., 15 â vastavad vÀÀrtused. See on cpu kasutus praeguses ajahetkes selle vara jaoks.

Kus ajavarrasid saab kasutada? Kas kellegil on ideid?
- DevOps'is saab mÔÔta CPU, RAM, vÔrgu, rps, vigade arvu jne koormust.
- IoT â saame mÔÔta temperatuuri, rĂ”hku, geo koorde ja muid nĂ€itajaid.
- Samuti finantssektor â saame jĂ€lgida aktsiate ja valuutade hindu.
- Lisaks sellele saab ajasirne jÀlgimiseks kasutada tootmisprotsesside jÀlgimiseks tehastes. Meil on kasutajaid, kes kasutavad VictoriaMetrics tuulegeneraatoreid jÀlgimiseks, ka robotite jÀlgimiseks.
- Ajandirad on samuti kasulikud teabe kogumiseks erinevatelt seadmete sensoritelt. NĂ€iteks mootorile; rehvirĂ”hu mÔÔtmiseks; kiirus, kaugus; kĂŒtusekulu mÔÔtmiseks jne.
- Ajandirad vÔivad sama moodi olla kasutusel lennukite jÀlgimiseks. Igas lennukis on must kast, mis kogub ajandiread erinevate lennuki tervise parameetrite kohta. Ajandirad on samuti kasutusel kosmosetööstuses.
- Tervishoid â see hĂ”lmab vererĂ”hku, pulssi jne.
VÔib-olla on veel rakendusi, millele ma ei ole mÔelnud, kuid loodan, et te mÔistate, et ajandirad on kaasaegses maailmas aktiivselt kasutusel. Ja nende kasutuse ulatus kasvab igal aastal.

Miks on ajareadade andmebaas vajalik? Miks ei saa tavapÀrast relatsioonilist andmebaasi ajareadade salvestamiseks kasutada?
Sest ajareadades on tavaliselt suur andmemaht, mida on keeruline tavapĂ€rastes andmebaasides salvestada ja töödelda. SeetĂ”ttu on tekkinud spetsialiseeritud andmebaasid ajareadade jaoks. Need andmebaasid salvestavad tĂ”husalt punkte (timestamp, value) mÀÀratud vĂ”tme jĂ€rgi. Need pakuvad API-d salvestatud andmete lugemiseks vĂ”tme jĂ€rgi, kas ĂŒhe vĂ”tme-vÀÀrtuse paari pĂ”hjal, mitme paari pĂ”hjal vĂ”i regexp jĂ€rgi. NĂ€iteks, kui soovite leida kĂ”igi oma teenuste protsessori koormust Ameerika andmekeskuses, tuleks kasutada jĂ€rgmist pseudokĂŒsimust.
Tavaliselt esindavad ajareadade andmebaasid spetsialiseeritud pĂ€ringute keeli, kuna SQL ei sobi ajareadade jaoks eriti hĂ€sti. Kuigi on ka andmebaase, mis toetavad SQL-i, ei ole see eriti sobiv. Paremini sobivad sellised pĂ€ringute keeled nagu , , , . Loodan, et keegi on vĂ€hemalt ĂŒhe neist keeltest kuulnud. PromQL-i on ilmselt paljud kuulnud. See on Prometheuse pĂ€ringute keel.

Nii nÀeb vÀlja kaasaegse ajajoonedatabaasi arhitektuur VictoriaMetrics nÀitel.
See koosneb kahest osast. Ăks on pööratud indeks ja teine ajajoonde vÀÀrtuste hoidmine. Need hoidlade on eraldatud.
Kui uus kirje tuleb andmebaasi, pöördume esmalt pööratud indeksi poole, et leida ajajoonde ID antud komplekti jaoks. label=value selle mÔÔtme jaoks. Leiame selle ID ja salvestame vÀÀrtuse andmehoidlasse.
Kui tuleb pÀring andmete toomiseks TSDB-st, uurime esmalt pööratud indeksi. Saame kÔik timeseries_ids kirjed, mis vastavad antud komplektile. label=valueJa siis toome kÔik vajalikud andmed andmehoidlast, mis on indekseeritud vastavalt. timeseries_ids.

Vaatame nÀidet, kuidas ajajoonde andmebaas töötleb sissetulevat select-pÀringut.
- Esmalt toob ta kÔik
timeseries_idspööratud indeksest, mis sisaldavad antud paarelabel=value, vÔi vastavad antud regulaaravaldusele. - SeejÀrel toob ta kÔik andmepunktid andmehoidlast antud ajavahemikus leitud tulemuste jaoks.
timeseries_ids. - PĂ€rast seda teostab andmebaas kasutaja pĂ€ringu pĂ”hjal arvutusi nende andmepunktide ĂŒle. Ja seejĂ€rel tagastab vastuse.
Selles esituses rÀÀgin teile esimesest osast. See on otsing timeseries_ids pööratud indeksis. Teise ja kolmanda osa vĂ”ite hiljem vaadata , vĂ”i oodata, kuni ma valmistan ette teised ettekanded đ

Alustame pööratud indeksist. Paljudele vÔib tunduda, et see on lihtne. Kes teab, mis on pööratud indeks ja kuidas see töötab? Oh, inimesi ei olegi nii palju. Proovime aru saada, mis see on.
Tegelikult on kĂ”ik lihtne. See on lihtsalt sĂ”nastik, mis seob vĂ”tmed vÀÀrtustega. Mis on vĂ”ti? See paar label=value, kus silt ja value â need on stringid. Ja vÀÀrtused on hulk timeseries_ids, mis sisaldab mÀÀratud paari label=value.
Pööratud indeks vÔimaldab kiiresti leida kÔik timeseries_ids, millel on antud label=value.
Ja see vÔimaldab kiiresti leida timeseries_ids aja rea jaoks mitme paari label=value, vÔi paaride label=regexp. Kuidas see toimub? Kasutades hulgade lÔike leidmist timeseries_ids iga paari jaoks label=value.

Vaatame erinevaid pöördindeksi rakendusi. Alustame kÔige lihtsamast naiivsest rakendusest. See nÀeb vÀlja umbes nii.
Function getMetricIDs saab ridade loendi. Iga rida sisaldab label=value. See funktsioon tagastab loendi metricIDs.
Kuidas see töötab? Siin on meil globaalne muutuja, mida nimetatakse invertedIndex. See on tavaline sÔnastik (map), mis seob stringi slice int-idega. String sisaldab label=value.
Funktsiooni rakendus: saame kĂ€tte metricIDs esimese label=value, seejĂ€rel kĂ€ime ĂŒle kĂ”ik teised label=value, saame nende jaoks kĂ€tte metricIDs . Ja kutsume vĂ€lja funktsiooni intersectInts, millest rÀÀgitakse hiljem. Ja see funktsioon tagastab nende loendite ristumise.

Nagu nÀete, ei ole pöördindeksi rakendus eriti keeruline. Kuid see on naivne rakendus. Millised on selle puudused? Peamine puudus naivsest rakendusest on see, et selline pöördindeks hoitakse meie pÔh mÀlu. PÀrast rakenduse taaskÀivitamist kaotame selle indeksi. Selle indeksi salvestamist kettale ei toimu. Andmebaasi jaoks on selline pöördindeks tÔenÀoliselt sobimatu.
Teine puudus on samuti seotud mĂ€luga. Inverteeritud indeks peab olema mahtunud töömĂ€llu. Kui see ĂŒletab töömĂ€lus arvutatud suurust, siis on ilmselge, et saame â out of memory error. Ja programm ei tööta.

Seda probleemi saab lahendada selliste valmis lahenduste abil nagu , vÔi .
KokkuvĂ”ttes on meil vaja andmebaasi, mis vĂ”imaldab kiiresti teostada kolmeæäœă
- Esimene operatsioon â vÀÀrtuse kirjutamine
vĂ”ti-vÀÀrtussellesse andmebaasi. Seda teeb ta vĂ€ga kiiresti, kusvĂ”ti-vÀÀrtusâ need on suvalised stringid. - Teine operatsioon â see on kiire vÀÀrtuse otsimine antud vĂ”tme jĂ€rgi.
- Ja kolmas operatsioon â see on kiire kĂ”ikide vÀÀrtuste otsimine antud prefiksi jĂ€rgi.
LevelDB ja RocksDB â need andmebaasid on vĂ€lja töötatud Google'is ja Facebookis. Esmalt ilmus LevelDB. Siis Facebooki inimesed vĂ”tsid LevelDB ja hakkasid seda arendama, tehes RocksDB. Praegu töötab Facebookis peaaegu kĂ”ikides sisemistes andmebaasides RocksDB-l, sealhulgas nad on ĂŒle viinud RocksDB-le ka MySQL-i. Nad nimetasid seda .
Inverteeritud indeksit saab realiseerida LevelDB abil. Kuidas seda teha? Me salvestame vĂ”tmena label=value. Ja vÀÀrtusena â ajaread, kus paar on olemas. label=value.
Kui meil on selle paari kohta palju ajaseeriaid label=value, siis on selle andmebaasi puhul palju ridu sama vÔtmega ja erinevatega timeseries_ids. Et saada loend kÔigist timeseries_ids, mis algavad antud label=prefix, teeme vahemikus skaneerimise, mille jaoks see andmebaas on optimeeritud. St valime kÔik read, mis algavad label=prefix ja saame vajalikud timeseries_ids.

Siin on ligikaudne rakendus, kuidas see Go-s vÀlja nÀeks. Meil on pööratud indeks. See on LevelDB.
Funktsioon on sama, mis naiivses rakenduses. See kordab enam-vĂ€hem rida reaga naiivset rakendust. Ainus asi on see, et asemel, et pöörduda map pöördume pööratud indeksi poole. Saame kĂ”ik vÀÀrtused esimese jaoks label=value. Siis lĂ€heme lĂ€bi kĂ”ikidest ĂŒlejÀÀnud paaridest label=value ja saame nende jaoks vastavad metricID komplektid. Siis leiame ristumise.

KÔik tundub hea, kuid selles lahenduses on puudusi. VictoriaMetrics rakendas alguses pööratud indeksit LevelDB pÔhjal. Kuid lÔpuks tuli sellest loobuda.
Miks? Sest LevelDB on aeglasem kui naivne rakendus. Naivses rakenduses saame antud vĂ”tme jĂ€rgi kohe terve viilu metricIDs. See on vĂ€ga kiire tegevus â kogu slice on valmis kasutamiseks.
LevelDB-s tuleb igal funktsiooni kutsumisel GetValues lĂ€bida kĂ”ik read, mis algavad label=value. Ja iga rea jaoks tuleb leida vÀÀrtus timeseries_ids. Sellest tuleb timeseries_ids kokku panna slice neid timeseries_ids. Selgelt on see palju aeglasem kui tavalisest mapâist pĂ€ringute tegemine vĂ”tme jĂ€rgi.
Teine puudus on see, et LevelDB on kirjutatud C-s. C-funktsioonide kutsumine Go-st ei ole vĂ€ga kiire. Selleks kulub sadu nanosekundi. See ei ole just kiire, kuna tavalise Go-s kirjutatud funktsiooni kutse vĂ”tab 1-5 nanosekundit, mis tĂ€hendab, et jĂ”udluse erinevus on kĂŒmneid kordi. VictoriaMetricsi jaoks oli see surmav puudus đ

SeetÔttu kirjutasin omaenda pööratud indeksi seadistuse. Nimetasin selle .
Mergeset pÔhineb MergeTree andmestruktuuril. See andmestruktuur on laenatud ClickHouse'ist. Selgelt peaks mergeset olema optimeeritud kiire otsingu jaoks timeseries_ids antud vÔtme pÔhjal. Mergeset on kirjutatud tÀielikult Go-s. Saate vaadata . Mergeseti implementatsioon asub kaustas . VÔite proovida aru saada, mis seal toimub.
API mergeset on vÀga sarnane LevelDB ja RocksDB-ga. S.t. see vÔimaldab kiiresti salvestada uusi kirjeid ja kiiresti leida kirjeid antud prefiksi jÀrgi.

Mergeseti puudustest rÀÀgime hiljem. Praegu rÀÀgime probleemidest, mis tekkisid VictoriaMetrics'i kasutamisel tootmises pöördindeksi rakendamisel.
Kust need probleemid tulenesid?
Esimene pÔhjus on kÔrge churn rate. Ehk siis sagedane ajajoonte vahetus. See tÀhendab, et ajajoon lÔppeb ja algab uus vÔi algab palju uusi ajajooni. Ja see juhtub sageli.
Teine pÔhjus on suur ajajoonte arv. Alguses, kui jÀlgimine hakkas populaarsust koguma, oli ajajoonte arv vÀike. NÀiteks, iga arvuti puhul tuleb jÀlgida protsessori, mÀlu, vÔrgu ja ketta koormust. 4 ajajoont iga arvuti kohta. Kui teil on nÀiteks 100 arvutit, on see 400 ajajoonte. See on vÀga vÀhe.
Aja jooksul on inimesed vÀlja mÔelnud, et on vÔimalik mÔÔta detailsemat teavet. NÀiteks mÔÔta mitte ainult kogu protsessori koormust, vaid ka iga protsessorituuma eraldi. Kui teil on 40 protsessorituuma, siis on teil vastavalt 40 korda rohkem ajaseeriaid protsessori koormuse mÔÔtmiseks.
Aga see ei ole veel kĂ”ik. Igal protsessorituumal vĂ”ivad olla mitu olekut, nagu idle, kui see ootab. Samuti töö kasutaja ruumis, töö kernel ruumis ja teised olekud. Iga sellist olekut saab samuti mÔÔta eraldi ajaseeria kui ĂŒksik ajaseeria. See suurendab ridade arvu veel 7-8 korda.
Meie ĂŒhest mÔÔtmest saime 40 x 8 = 320 mÔÔdet ainult ĂŒhe arvuti jaoks. Korrutame 100-ga, saame 32 000 asemel 400.
See ilmus Kubernetes. Ja see halvenes veelgi, sest Kubernetesis saab hostida palju erinevaid teenuseid. Iga teenus Kuberneteses koosneb paljusid podidest. Ja seda tuleb pidevalt jÀlgida. Lisaks on meil pidev uute versioonide juurutamine teie teenustes. Iga uue versiooni puhul tuleb luua uusi ajakavasid. LÔppkokkuvÔttes kasvab ajakava hulk eksponentsiaalselt ja seisame silmitsi suure ajakava hulgaga, mida nimetatakse high-cardinality. VictoriaMetrics suudab sellega edukalt toime tulla vÔrreldes teiste ajakavade andmebaasidega.

Uurime lÀhemalt high churn rate'i. Miks tekib productionis high churn rate? Sest mÔned labeling ja tagging vÀÀrtused muutuvad pidevalt.
NÀiteks vÔetakse Kubernetes, kus on kontseptsioon deployment, st kui teie rakenduse uus versioon kÀivitatakse. Kubernetes arendajad otsustasid mingil pÔhjusel lisada deployment'i id label'isse.
Millele see viis? Sellele, et iga uue deployment'i korral katkestatakse kÔik vanad ajakavad ja nende asemel algavad uued ajakavad uue label'i vÀÀrtusega deployment_id. Selliseid ajakavale vÔib olla sadu tuhandeid ja isegi miljoneid.
Oluline omadus on see, et ajakava koguhulk kasvab, kuid aktiivsete ajakavade arv, kuhu andmed voolavad, jÀÀb konstantseks. Seda olukorda nimetatakse high churn rate'iks.
Peamine probleem high churn rate'i korral on tagada pidev kÔigi ajakavade otsingu kiirus mÀÀratud sildihulga jooksul teatud ajavahemikus. Kui tavaliselt on see ajavahemik viimase tunni vÔi viimase pÀeva jooksul.

Kuidas seda probleemi lahendada? Siin on esimene variant: jagada pöördindeks sĂ”ltumatuteks osadeks ajas. St. möödub teatud ajavahemik, lĂ”petame töö pöördindeksi praegusega ja loome uue pöördindeksi. Möödub veel ĂŒks ajavahemik, loome veel ĂŒhe ja veel ĂŒhe.
Ja nende pöördindeksite valimisel leiame pöördindeksite hulga, mis langeb mÀÀratud ajavahemikku. Seega valime sealt ajakava id-d.
See aitab ressursse sÀÀsta, kuna me ei pea vaatama osi, mis ei jÀÀ mÀÀratud vahemikku. St, et tavaliselt, kui valime andmed viimasest tunnist, siis eelnevaid ajavahemikke jÀtame pÀringud vahele.

On veel ĂŒks vĂ”imalus selle probleemi lahendamiseks. See on hoida iga pĂ€eva jaoks eraldi nimekiri ajaseerude id-dest, mis esinesid sellel pĂ€eval.
Selle lahenduse eelis varasema lahenduse ees on see, et me ei dubleeri teavet ajaseerude kohta, mis ajaga ei kao. Need on pidevalt olemas ja ei muutu.
Puuduseks on see, et selline lahendus on keerulisem rakendada ja keerulisem debugida. Ja VictoriaMetrics valis selle lahenduse. See on kĂ”igepealt ajalooliselt nii kujunenud. Selline lahendus töötab ka ĂŒsna hĂ€sti vĂ”rreldes varasemaga. Seda seetĂ”ttu, et seda lahendust ei olnud rakendatud, kuna andmete kopeerimine on vajalik igas partitionâis ajaseriate jaoks, mis ei muutu, st mis ajas ei kao. VictoriaMetrics on peamiselt optimeeritud kettaruumi tarbimise jaoks ja eelmine rakendus halvendab kettaruumi tarbimist. See aga sobib paremini kettaruumi tarbimise minimiseerimiseks, seetĂ”ttu see valiti.
Pidime sellega vÔitlema. VÔitlus seisnes selles, et antud rakenduses tuleb ikkagi valida palju suurem hulk timeseries_ids andmete jaoks, kui siis, kui pöördindeks on jagatud ajas.

Kuidas me selle probleemi lahendasime? Me lahendasime selle originaalsel viisil â salvestades igas pöördindeksi kirjas mitu ajaseeriade identifikaatorit, mitte ĂŒhte. St meil on vĂ”tme label=value, mis on iga ajareal olemas. Ja nĂŒĂŒd salvestame mĂ”ned timeseries_ids ĂŒhes kirjes.
Siin on nĂ€ide. Varem oli meil N kirjet, aga nĂŒĂŒd on meil ĂŒks kiri, mille prefiks on sama mis kĂ”igil teistel. Eelneva kirje vÀÀrtus sisaldab kĂ”iki ajaread id'sid.
See on vĂ”imaldanud suurendada sellise pöördindeksi skaneerimise kiirus kuni 10 korda. Ja on vĂ€hendanud mĂ€lutarbimist puhverdamise jaoks, kuna nĂŒĂŒd salvestame rea label=value ainult kord puhverdamiseks koos N korda. Ja see rida vĂ”ib olla suur, kui sul on sisendites ja etiketides pikki ridu, mida Kubernetes sinna meeldib toppida.

Veel ĂŒks vĂ”imalus pöördindeksi otsimise kiirusel kiirus - see on shardimine. Mitme pöördindeksi loomine ĂŒhe asemel ja andmete shardimine nende vahel vĂ”ti jĂ€rgi. See on komplekt key=value See, meil on mitu sĂ”ltumatut pööratud indeksit, mida saame samaaegselt kĂŒsida mitmel protsessoril. Varasemad teostused lubasid töötada ainult ĂŒhes protsessorireĆŸiimis, st skaneerida andmeid vaid ĂŒhel tuumal. See lahendus vĂ”imaldab skaneerida andmeid kohe mitmel tuumal, nagu ClickHouse armastab teha. Just seda me plaanime rakendada.

Ja nĂŒĂŒd naaseme meie teemade juurde â ristsĂ”idufunktsiooni juurde. timeseries_ids. Uurime, millised teostused vĂ”ivad olla vĂ”imalikud. See funktsioon vĂ”imaldab leida timeseries_ids antud komplekti jaoks. label=value.

Esimene variant on naiivne teostus. Kaks sisemist tsĂŒklit. Saame funktsiooni sisendiks intersectInts kaks slices â a ja b. VĂ€ljundina peab see meile tagastama nende slices'i ristumise.
Naiivne teostus nĂ€eb vĂ€lja nii. KĂ€ime kĂ”ik vÀÀrtused lĂ€bi slice'st a, selle tsĂŒkli sees kĂ€ime kĂ”ik lĂ€bi slice'st b. Ja vĂ”rdleme neid. Kui need kattuvad, siis oleme leidnud ristumise. Ja salvestame selle tulemus.

Millised on puudused? Ruudukompleksus â see on selle peamine puudus. NĂ€iteks, kui teie slice 'id on suured. a ja b kui ĂŒhe miljoni, siis see funktsioon ei naas tuttavat vastust. Kuna tal on vaja teha ĂŒhe triljoni iteratsiooni, mis on vĂ€ga palju isegi tĂ€napĂ€evaste arvutite jaoks.

Teine rakendus pĂ”hineb map'il. Loome map'i. Paneme sellesse map'i kĂ”ik vÀÀrtused slice'ist. a. SeejĂ€rel kĂ€ime slice'i kaudu eraldi tsĂŒkliga. b. Ja kontrollime â kas see vÀÀrtus on slice'is. b map'is. Kui see on olemas, lisame selle tulemustesse.

Millised on eelised? Eelis on see, et siin on ainult lineaarsed kulud. St. funktsioon töötab palju kiiremini suurte slice'ide suuruste korral. Miljoni suuruse slice'i puhul töötab see funktsioon 2 miljoni iteratsiooni jooksul, erinevalt triljonist iteratsioonist, nagu eelnevas funktsioonis.
Ja puuduseks on see, et see funktsioon vajab rohkem mÀlu selle map'i loomiseks.
Teine puudus â see on suur overhead hashimisele. See puudus ei ole vĂ€ga ilmne. Ja meie jaoks polnud see samuti vĂ€ga ilmne, mistĂ”ttu alguses VictoriaMetrics'is oli intersection'i rakendus lĂ€bi map'i. Aga hiljem nĂ€itas profileerimine, et peamine protsessori aeg kulub map'i kirjutamiseks ja vÀÀrtuse olemasolu kontrollimiseks selles map'is.
Miks kulub CPU aeg nendes kohtades? Sest Go teostab seal hashimisoperatsiooni. See tĂ€hendab, et ta arvutab vĂ”tme hash'i, et pÀÀseda seejĂ€rel antud indeksi kaudu HashMap'ile. Hash'i arvutamise operatsioon toimub mĂ”ne kĂŒmnendiku nanosekundiga. See on VictoriaMetricsi jaoks aeglane.

Otsustasin rakendada bitset'i, mis on spetsiaalselt selle juhtumi jaoks optimeeritud. Nii nĂ€eb nĂŒĂŒd kahe slice'i ĂŒhisosa vĂ€lja. Siin loome bitset'i. Lisame sinna esimesest slice'ist elemendid. Siis kontrollime nende elementide olemasolu teises slice'is. Ja lisame need tulemusse. See tĂ€hendab, et see ei erine peaaegu eelmise nĂ€ite puhul. Ainsad asjad, mida oleme siin teinud, on kaardile juurdepÀÀsu asendamine kohandatud funktsioonidega. add ja has.

Esmapilgul tundub, et see peaks töötama aeglasemalt, kui varem kasutati tavalist kaarti ja nĂŒĂŒd kutsutakse veel mingid funktsioonid vĂ€lja, kuid profilleerimine nĂ€itab, et see asi töötab 10 korda kiiremini kui tavaline kaart VictoriaMetricsi jaoks.
Lisaks sellele kasutab see vÔrreldes kaardi rakendusega oluliselt vÀhem mÀlu. Sest me hoiame siin bitte, mitte kaheksabaidiseid vÀÀrtusi.
Sellise rakenduse puuduseks on see, et see ei ole nii ilmne, mitte triviaalne.
Veel ĂŒks puudus, mida paljud ei pruugi mĂ€rgata, on see, et see rakendus vĂ”ib teatud olukordades halvasti töötada. See tĂ€hendab, et see on optimeeritud konkreetse juhtumi jaoks, nimelt VictoriaMetrics ajasarjade id-de ĂŒlekandmise jaoks. See ei tĂ€henda, et see sobib igas olukorras. Kui seda valesti kasutada, siis saavutame mitte ainult jĂ”udluse kasvu, vaid ka out of memory error ja jĂ”udluse vĂ€henemise.

Vaatame selle andmestruktuuri rakendust. Kui soovite, siis see asub VictoriaMetricsi allikates, kaustas . See on optimeeritud just VictoriaMetricsi juhtumi jaoks, kus timeseries_id esindab 64-bitist vÀÀrtust, kus esimesed 32 bitti on peaaegu konstantsetena ja muutuvad ainult viimased 32 bitti.
See andmestruktuur ei salvestata kettale, vaid töötab ainult mÀlus.

Siin on selle API. See ei ole vĂ€ga keeruline. API on kohandatud just VictoriaMetricsi konkreetse kasutusnĂ€ite jaoks. Siin ei ole ĂŒleliigseid funktsioone. Siin on funktsioonid, mida VictoriaMetrics selgelt kasutab.
On funktsioon add, mis lisab uusi vÀÀrtusi. On olemas funktsioon has, mis kontrollib uusi vÀÀrtusi. Ja on funktsioon del, mis eemaldab vÀÀrtused. On abifunktsioon len, mis tagastab kogumi suuruse. Funktsioon clone kloneerib kogumi. Ja funktsioon appendto töötluses tÀiendatakse see lÔikesse timeseries_ids.

Nii nÀeb selle andmestruktuuri rakendus vÀlja. Kogumil on kaks elementi:
ItemsCountâ see on abivĂ€li, et kiiresti tagastada kogumi elementide arv. Selle abivĂ€lja oleks vĂ”inud Ă€ra jĂ€tta, kuid see tuli siia lisada, kuna VictoriaMetrics kĂŒsitleb sageli oma algoritmides bitseti pikkust.Teine vĂ€li on
buckets. See on lĂ”ik struktuuristbucket32. Igas struktuuris on salvestatudhivĂ€li. Need on kĂ”ige kĂ”rgemad 32 bitti. Ja kaks lĂ”iku âb16hisjabucketskohastbucket16struktuurid.
Siin hoitakse 64-bitise struktuuri teise osa ĂŒlemisi 16 bitti. Ja siin hoitakse bitsets'i iga bait jÀÀb 16 bitiga.
Bucket64 koosneb massiivist uint64. Pikkus arvutatakse nende constatsioonide abil. Ăhes bucket16 max vĂ”ib hoida 2^16=65536 bititi. Kui see jagada 8-ga, siis on see 8 kilobaiti. Kui jagada veel kord 8-ga, siis on see 1000 uint64 vÀÀrtuses. St. Bucket16 â see on meil 8-kilobaitine struktuur.

Vaatame, kuidas on rakendatud ĂŒks meetod uut vÀÀrtust lisava struktuuri.
KĂ”ik algab uint64 vÀÀrtusest. Arvutame ĂŒlemised 32 bitti, arvutame alumised 32 bitti. LĂ€bime kĂ”ik buckets. VĂ”rdleme ĂŒlemisi 32 bitti igas baketis lisatava vÀÀrtusega. Ja kui need kattuvad, siis kutsume ĂŒles funktsiooni add b32 struktuuris buckets. Ja lisame sinna alumised 32 bitti. Ja kui see tagastab true, siis tĂ€hendab, et me lisasime sellise vÀÀrtuse sinna ja meil polnud sellist vÀÀrtust. Kui see tagastab false, siis selline vÀÀrtus oli juba olemas. Siis suurendame struktuuri elementide arvu.
Kui me ei leidnud vajalikku baketi vajaliku hi-vÀÀrtusega, siis kutsume ĂŒles funktsiooni addAlloc, mis eraldab uue baketi, lisades selle baketi struktuuri.

See on funktsiooni rakendus b32.add. See on sarnane eelmise rakendusega. Me arvutame vanemad 16 bitti, nooremad 16 bitti.
Siis lĂ€bime kĂ”ik ĂŒlemised 16 bitti. Leiame kattuvused. Ja kattumise korral kutsume ĂŒles meetodi add, mida uurime jĂ€rgmises lehes bucket16.

Ja siin on kÔige madalam tase, mis peab olema maksimaalselt optimeeritud. Me arvutame uint64 id vÀÀrtuse slice bit, samuti bitmask. See on mask selle 64-bitise vÀÀrtuse jaoks, mille jÀrgi saab kontrollida selle bit'i olemasolu vÔi seada selle. Kontrollime, kas see bit on seatud, ja seame selle, ning tagastame, kas see on olemas. Selline rakendus vÔimaldas meil kiirendada aegrea id-de ristumist 10 korda vÔrreldes tavapÀraste map'idega.

VictoriaMetrics'is on lisaks sellele optimeerimisele palju teisi optimeerimisi. Enamik neist optimeerimistest on lisatud mitte niisama, vaid pÀrast koodi profileerimist tootmises.
See on optimeerimise peamine reegel â mitte lisada optimeerimist, oletades, et siin on kitsaskoht, kuna see vĂ”ib osutuda vale oletuseks. Optimeerimine halvendab tavaliselt koodi kvaliteeti. SeetĂ”ttu tasub optimeerida ainult pĂ€rast profileerimist ja soovitatavalt tootmises, et need oleksid tegelikud andmed. Kellele huvi pakub, vĂ”ite vaadata VictoriaMetrics'i lĂ€htekoodi ja Ă”ppida tundma teisi optimeerimisi, mis seal on.

Mul on kĂŒsimus bitset'i kohta. See sarnaneb vĂ€ga C++ vector bool'i rakenduse, optimeeritud bitset'iga. Kas te vĂ”tsite selle rakenduse sealt?
Ei, see ei ole se. Selle bitseti rakendamisel pĂ”hinesin ma teadmistele nende id-aegrea struktuurist, mida kasutatakse VictoriaMetrics'is. Nende struktuuri kohaselt on ĂŒlemised 32 bitti enamasti pĂŒsivad. Alumised 32 bitti vĂ”ivad muutuda. Mida madalam on bitt, seda sagedamini vĂ”ib see muutuda. Seega on see rakendus optimeeritud just selle andmestruktuuri jaoks. C++ rakendus, kui ma Ă”igesti tean, on optimeeritud ĂŒldiste juhtumite jaoks. Kui optimeerida ĂŒldiste juhtumite jĂ€rgi, tĂ€hendab see, et see ei ole konkreetses juhtumis kĂ”ige optimaalsem.
Soovitan teil vaadata ka Alexei Milovidi ettekannet. Ta rÀÀkis kuskil kuu aega tagasi ClickHouse'i optimeerimisest konkreetsete erialade jaoks. Ta rÀÀgib nimelt, et C++ rakendus vĂ”i mĂ”ni muu rakendus on tavaliselt kohandatud keskmiselt hea töö jaoks. See vĂ”ib töötada halvemini kui spetsialiseeritud rakendus konkreetsete teadlike teadmiste jaoks, nagu meil on, kui me teame, et ĂŒlemised 32 bitti on enamasti pĂŒsivad.
Mul on teine kĂŒsimus. Mis on radikaalne erinevus InfluxDB-st?
Olulisi erinevusi on palju. Kui rÀÀkida seadistustest ja mÀlutarbimisest, siis InfluxDB nÀitab testides 10 korda suuremat mÀlutarbimist kÔrge kardinaalsusega ajajoonte jaoks, kui neid on palju, nÀiteks miljoneid. NÀiteks VictoriaMetrics tarbib 1 GB miljoni aktiivse joone kohta, samas kui InfluxDB tarbib 10 GB. Ja see on suur erinevus.
Teine oluline erinevus on see, et InfluxDB-s on kummalised pĂ€ringu keeled â Flux ja InfluxQL. Need ei ole ajajoontes töötamiseks eriti mugavad vĂ”rreldes , mida VictoriaMetrics toetab. PromQL on pĂ€ringute keel, mis pĂ€rineb Prometheusest.
Ja veel ĂŒks erinevus on see, et InfluxDB-l on veidi kummaline andmemudel, kus igas reas vĂ”ib olla mitu fields erinevate mĂ€rgendite kogudega. Need read jagunevad veel igasugusteks tabeliteks. Need tĂ€iendavad keerukused muudavad selle andmebaasi hilisema töö keeruliseks. Seda on raske toetada ja mĂ”ista.
VictoriaMetricsis on kĂ”ik palju lihtsam. Iga ajajoon on vĂ”tme-vÀÀrtuse paar. VÀÀrtus on kogum punkte â (timestamp, value), samas kui vĂ”ti on kogum label=value. Puudub lahknevus fields'i ja measurements'i vahel. See vĂ”imaldab valida ĂŒkskĂ”ik milliseid andmeid ning seejĂ€rel neid kombineerida, liita, lahutada, korrutada, jagada, erinevalt InfluxDB-st, kus arvutused erinevate ridade vahel ei ole endiselt rakendatud, kui ma Ă”igesti tean. Kui need on isegi rakendatud, on see keeruline, tuleb kirjutada palju koodi.
Mul on tĂ€psustav kĂŒsimus. Kas ma sain Ă”igesti aru, et oli mingi probleem, millest te rÀÀkisite, et see pööratud indeks ei mahu mĂ€lu, mistĂ”ttu seal toimub partitsioneerimine?
Alguses nÀitasin ma pööratud indeksi naiivset rakendust tavapÀrasel Go kaardil. Selline rakendus ei sobi andmebaaside jaoks, sest see pööratud indeks ei salvestu kettale, kuid andmebaas peab salvestama kettale, et peale taaskÀivitamist oleksid need andmed endiselt kÀttesaadavad. Antud rakenduses kaob pööratud indeks, kui rakendus taaskÀivitada. Ja te kaotate ligipÀÀsu kÔikidele andmetele, sest te ei suuda neid enam leida.
Tere! AitĂ€h ettekande eest! Minu nimi on Pavel. Olen ettevĂ”ttest Wildberries. Mul on paar kĂŒsimust teie jaoks. Esimene kĂŒsimus. Kuidas te arvate, kui valiksite oma rakenduse arhitektuuri ĂŒlesehitamisel teise pĂ”himĂ”tte ja jaotaksite andmed ajaliselt, kas siis saaksite andmete otsimise ajal ristuda, tuginedes ainult sellele, et ĂŒhes jaotuses on andmed ĂŒhe ajavahemiku kohta, st ĂŒhes ajavahemikus, ja ei peaks muretsema, et teie tĂŒkid on eri kohtades laiali? Teine kĂŒsimus â kuna te rakendate sarnast algoritmi bitsetide ja muu juurde, kas olete ehk proovinud kasutada protsessori juhiseid? VĂ”ib-olla olete katsetanud selliseid optimeerimisi?
Teisele kĂŒsimusele vastan kohe. Me ei ole sinna veel jĂ”udnud. Kuid kui see vajalik on, siis me jĂ”uame. Aga esimene, mis kĂŒsimus oligi?
Te arutasite kahte stsenaariumit. Ja ĂŒtlesite, et valisite teise, millel on keerulisem rakendus. Ja ei eelistanud esimest, kus andmed on ajaliselt jaotatud.
Jah. Esimesel juhul oleks indeksi kogumaht suurem, kuna peame igas partitsioonis hoidma andmete koopiaid ajaseeriate jaoks, mis ulatuvad ĂŒle kĂ”ik need partitsioonid. Ja kui teil on ajaseeriate churn rate madal, st pidevalt kasutatakse samu seeriaid, siis esimesel juhul kaotaksime diskiruumi kasutuses palju rohkem vĂ”rreldes teise variandiga.
Nii et jah, ajakohane partitsioonimine on hea lahendus. Seda kasutab Prometheus. Kuid Prometheusel on teine puudus. Andmeosade ĂŒhendamisel peab ta hoidma mĂ€lu kĂ”igi label'ite ja ajaseeriate metaandmete jaoks. SeetĂ”ttu, kui ĂŒhendatavad andmeosad on suured, suureneb mĂ€lu tarbimine oluliselt, erinevalt VictoriaMetricsist. VictoriaMetrics ei tarbi ĂŒhendamisel ĂŒldse mĂ€lu, seal tarbitakse paari kilobaiti sĂ”ltumata ĂŒhendatavate andmeosade suurusest.
Teie kasutatav algoritm kasutab mĂ€lu. Selles registreeritakse ajatsĂŒklite mĂ€rgid, millel on vÀÀrtused. Nii kontrollite paari olemasolu ĂŒhes andmehulgas ja teises. Ja saate aru, kas toimus ĂŒhisosa vĂ”i mitte. TĂŒĂŒpiliselt rakendavad andmebaasid kursoreid, iteraatoreid, mis hoiavad oma praegust olekut ja liikuvad sorteeritud andmete kaudu, mistĂ”ttu on teil need operatsioonid lihtsamad.
Miks me ei kasuta kursoreid andmete ristumisteks?
Jah.
Meil on LevelDB-s vÔi mergeset-s just sorteeritud read. Saame kursori abil lÀbi minna ja leida ristumise. Aga miks me ei kasuta? Sest see on aeglane. Sest kursorid eeldavad, et iga rea jaoks tuleb funktsiooni kutsuda. Funktsiooni kutsumine vÔtab aega 5 nanosekundit. Ja kui teil on 100 000 000 rida, siis kulutame poole sekundi ainult funktsiooni vÀlja kutsumiseks.
Jah, see on olemas. Ja viimase kĂŒsimus, millel on vĂ”ib-olla veidi kummaline kĂ”la. Miks ei saa andmete laekumise hetkel arvutada kĂ”iki vajalikke aggregaatide ja salvestada neid vajalikul viisil? Miks hoida suuri andmemahte sĂŒsteemides nagu VictoriaMetrics, ClickHouse jne, et siis kulutada nende kĂ€sitlemiseks vĂ€ga palju aega?
Toon nĂ€ite, et oleks arusaadavam. Oletame, kuidas töötab vĂ€ike mĂ€nguasi spidomeeter? See salvestab vahemaa, mille olete lĂ€binud, pidevalt lisades selle ĂŒhte suurusesse, teise - aja. Ja jagab. Ja saab keskmise kiirus. Saame teha enam-vĂ€hem sama. Koguda jooksvalt kĂ”ik vajalikud faktid.
HĂ€sti, olen kĂŒsimusest aru saanud. Teie nĂ€ide on elujĂ”uline. Kui te teate, milliseid agregaatfunktsioone vajate, siis on see parim lahendus. Probleem on aga selles, et inimesed sĂ€ilitavad need mÔÔdikud, mingid andmed ClickHouse'is ja nad ei tea veel, kuidas nad neid tulevikus agregoorida vĂ”i filtreerida, mistĂ”ttu peavad nad sĂ€ilitama kĂ”ik toored andmed. Aga kui te teate, et peate midagi keskmist arvutama, siis miks mitte arvutada seda, selle asemel et hoida seal hunnikut tooreid vÀÀrtusi? Kuid see ainult siis, kui te tĂ€pselt teate, mida vajate.
Muide, ajaliselt jÀrjestatud andmebaasid toetavad agregaatide arvestamist. NÀiteks toetab Prometheus . See tÀhendab, et seda on vÔimalik teha, kui te teate, milliseid agregaatfunktsioone vajate. VictoriaMetrics'il seda veel ei ole, kuid tavaliselt paigaldatakse sellele ette Prometheus, kus seda saab teha salvestusreeglites.
NĂ€iteks eelmisel töökohal tuli kasutada liugakna abil viimase tunni jooksul toimunud sĂŒndmuste arvu arvestamist. Probleemiks oli, et tuli teha kohandatud implementatsioon Go keeles, st teenus selle arvestamiseks. See teenus osutus lĂ”puks keeruliseks, kuna selle arvestamine on keeruline. Implementatsioon vĂ”ib olla lihtne, kui peate arvestama mĂ”ningaid koguseid fikseeritud ajavahemike jooksul. Kui aga soovite arvestada sĂŒndmusi liugaknas, pole see nii lihtne, kui see tundub. Arvan, et ClickHouse'is vĂ”i ajaseeria andmebaasides pole see ikka veel rakendatud, kuna rakendamine on keeruline.
Ja veel ĂŒks kĂŒsimus. RÀÀkisime praegu keskmistest vÀÀrtustest ja mulle tuli meelde, et kunagi oli selline asi nagu Graphite, millel oli Carboni taust. See suutis vanu andmeid kĂ€rpida, st jĂ€ttis iga minuti, iga tunni jne jaoks ĂŒhe punkti. Ăldiselt on see ĂŒsna mugav, kui me vajame toorandmeid, ĂŒtleme, kuu kohta, samas kui kĂ”ik muu saab kĂ€rpida. Kuid Prometheus ja VictoriaMetrics ei toeta seda funktsionaalsust. Kas selle toetamine on plaanis? Kui ei, siis miks mitte?
AitĂ€h kĂŒsimuse eest. Meie kasutajad kĂŒsivad seda perioodiliselt. KĂŒsitakse, millal me lisame toetalvise (downsampling) toe. Siin on mitu probleemi. Esiteks, iga kasutaja mĂ”istab selle all downsampling midagi erinevat: mĂ”ned soovivad saada mingit suvalist punkti mÀÀratud intervallis, mĂ”ned soovivad maksimaalseid, minimaalseid vĂ”i keskmisi vÀÀrtusi. Kui teie andmebaasi kirjutavad andmeid mitmed sĂŒsteemid, siis ei saa neid ĂŒhtsalt kokku korjata. VĂ”ib juhtuda, et iga sĂŒsteemi jaoks on vajalik kasutada erinevat downsampling'i. Ja see on keeruline ellu viia.
Teiseks, VictoriaMetrics, nagu ka ClickHouse, on optimeeritud töötama suurte tooredate andmete kogustega, seetĂ”ttu suudab ta töödelda miljardit rida vĂ€hem kui sekundiga, kui teie sĂŒsteemis on palju tuuma. Aja rea punkte skaneerimine VictoriaMetrics'is on 50 000 000 punkti sekundis ĂŒhe tuuma kohta. Ja see jĂ”udlus skaleerub olemasolevate tuumadega. St. kui teil on nĂ€iteks 20 tuuma, siis saab skaneerida miljardi punkti sekundis. Ja see omadus VictoriaMetrics'is ja ClickHouse'is vĂ€hendab vajadust downsampling'i jĂ€rele.
Teine omadus on see, et VictoriaMetrics kompresseerib need andmed tĂ”husalt. Keskmine tihedus productionis on 0,4 kuni 0,8 bitti punktide kohta. Iga punkt on ajatempleid + vÀÀrtused. Ja keskmiselt kompressitakse see alla ĂŒhe bitti.
Sergei. Mul on kĂŒsimus. Mis on minimaalne salvestamise aeg?
Ăks millisekund. Hiljuti arutasime teiste ajarealiste andmebaaside arendajatega. Nende minimaalne ajakvant on ĂŒks sekund. NĂ€iteks Graphites on ka ĂŒks sekund. OpenTSDB-s samuti ĂŒks sekund. InfluxDB-l on nanosekundi tĂ€psus. VictoriaMetrics-il on ĂŒks millisekund, kuna Prometheuses on ĂŒks millisekund. VictoriaMetrics loodi algselt Prometheuse kaugandmete salvestuseks. Kuid nĂŒĂŒd suudab see salvestada andmeid ka teistest sĂŒsteemidest.
Inimene, kellega ma rÀÀkisin, ĂŒtles, et nende tĂ€psus on sekundiline â see on piisav, kuna see sĂ”ltub andmete tĂŒĂŒbist, mida ajajoonte andmebaasi salvestatakse. Kui need on DevOps-andmed vĂ”i andmed infrastruktuurist, kus te kogute neid 30 sekundi vĂ”i minuti intervalliga, siis piisab sekundilisest tĂ€psusest, vĂ€hem ei ole enam vajalik. Kuid kui te kogute neid suure sagedusega kauplemise sĂŒsteemidest, siis vajate nanoskeedi tĂ€psust.
Millisekundiline tĂ€psus VictoriaMetricsis sobib nii DevOps-i juhtumi jaoks kui ka enamikeks juhtudeks, mida ma oma ettekande alguses mainisin. Ainus, mille jaoks see ei pruugi sobida, on kĂ”rgsageduslikud kauplemissĂŒsteemid.
AitĂ€h! Ja veel ĂŒks kĂŒsimus. Milline on ĂŒhilduvus PromQL-iga?
TĂ€ielik tagurpidi ĂŒhilduvus. VictoriaMetrics toetab tĂ€ielikult PromQL-i. Lisaks sellele lisab see veel tĂ€iustatud funktsionaalsust PromQL-i, mis nimetatakse . Selle tĂ€iustatud funktsionaalsuse kohta on YouTube'is ettekande. RÀÀkisin Monitoring Meetup'il sel kevadel Peterburis.
Telegrami kanali .
Ainult registreeritud kasutajad saavad kĂŒsitluses osaleda. , palun.
Mis takistab teid ĂŒleminekule VictoriaMetricsile kui pikaajalisele salvestuslahendusele Prometheusele? (Kirjutage kommentaaridesse, lisan kĂŒsitlusse))
71,4%Ei kasuta Prometheus5
28,6%Ei teadnud VictoriaMetrics2-st
HÀÀletas 7 kasutajat. 12 kasutajat olid erapooletud.
Allikas: habr.com
