Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Ek stel voor om die transkripsie van die verslag van die einde van 2019 deur Alexander Valyalkin "Go optimizations in VictoriaMetrics" te lees

VictoriaMetrics - 'n vinnige en skaalbare DBBS vir die stoor en verwerking van data in die vorm van 'n tydreeks ('n rekord vorm 'n tyd en 'n stel waardes wat ooreenstem met hierdie tyd, byvoorbeeld verkry deur 'n periodieke peiling van die status van sensors of die versameling van metrieke).

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hier is 'n skakel na die video van hierdie verslag - https://youtu.be/MZ5P21j_HLE

Skyfies

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Vertel ons oor jouself. Ek is Alexander Valyalkin. Hier my github-rekening. Ek is passievol oor Go en prestasieoptimalisering. Het baie van allerhande nuttige en nie baie biblioteke geskryf nie. Hulle begin óf met fast, of met quick voorvoegsel.

Ek werk tans aan VictoriaMetrics. Wat is dit en wat doen ek daar? Ek sal hieroor in hierdie aanbieding praat.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Die uiteensetting van die verslag is soos volg:

  • Eerstens sal ek jou vertel wat VictoriaMetrics is.
  • Dan sal ek jou vertel wat tydreekse is.
  • Dan sal ek jou vertel hoe die tydreeksdatabasis werk.
  • Vervolgens sal ek praat oor die argitektuur van die databasis: waaruit dit bestaan.
  • En kom ons gaan dan oor na die optimaliserings wat in VictoriaMetrics is. Dit is 'n optimalisering vir die omgekeerde indeks en 'n optimalisering vir die bitset-implementering in Go.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Wat is VictoriaMetrics, weet iemand in die gehoor? Sjoe, baie mense weet al. Dit is goeie nuus. Vir die wat nie weet nie, hierdie is 'n databasis vir tydreekse. Dit is gebaseer op die ClickHouse-argitektuur, op sommige ClickHouse-implementeringsbesonderhede. Byvoorbeeld, op soos: MergeTree, parallelle berekening op alle beskikbare verwerkerkerne en prestasieoptimering deur te werk aan datablokke wat in die verwerkerkas geplaas word.

VictoriaMetrics bied die beste datakompressie in vergelyking met ander tydreeksdatabasisse.

Dit skaal vertikaal - dit wil sê, jy kan meer verwerkers, meer RAM op een rekenaar byvoeg. VictoriaMetrics sal hierdie beskikbare hulpbronne suksesvol benut en lineêre werkverrigting verbeter.

VictoriaMetrics skaal ook horisontaal - dit wil sê, jy kan bykomende nodusse by die VictoriaMetrics-kluster voeg, en die werkverrigting daarvan sal byna lineêr groei.

Soos jy geraai het, is VictoriaMetrics 'n vinnige databasis omdat ek nie ander kan skryf nie. En dit is in Go geskryf, so ek praat daaroor by hierdie ontmoeting.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Wie weet wat 'n tydreeks is? Hy ken ook baie mense. 'n Tydreeks is 'n reeks pare (timestamp, значение)waar hierdie pare volgens tyd gesorteer word. Die waarde is 'n drywende puntgetal - float64.

Elke tydreeks word uniek deur 'n sleutel geïdentifiseer. Wat is hierdie sleutel? Dit bestaan ​​uit 'n nie-leë stel sleutel-waarde-pare.

Hier is 'n voorbeeld van 'n tydreeks. Die sleutel van hierdie reeks is die lys van pare: __name__="cpu_usage" is die naam van die metrieke, instance="my-server" is die rekenaar waarop hierdie maatstaf versamel word, datacenter="us-east" - dit is die datasentrum waar hierdie rekenaar geleë is.

Ons het die naam van die tydreeks gekry, wat uit drie sleutel-waarde-pare bestaan. Hierdie sleutel stem ooreen met 'n lys pare (timestamp, value). t1, t3, t3, ..., tN is tydstempels, 10, 20, 12, ..., 15 is die ooreenstemmende waardes. Dit is die huidige CPU-gebruik vir die gegewe ry.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Waar kan tydreekse gebruik word? Het iemand enige idee?

  • In DevOps kan u SVE, RAM, netwerk, rps, foute, ens.
  • IoT - ons kan temperatuur, druk, geo-koördinate en iets anders meet.
  • Ook finansies - ons kan pryse monitor vir allerhande aandele en geldeenhede.
  • Boonop kan tydreekse gebruik word om produksieprosesse in fabrieke te monitor. Ons het gebruikers wat VictoriaMetrics gebruik om windturbines te monitor, vir robotte.
  • Tydreekse is ook nuttig om inligting van sensors van verskeie toestelle in te samel. Byvoorbeeld, vir die enjin; om banddruk te meet; om spoed, afstand te meet; vir die meting van petrolverbruik, ens.
  • Tydreekse kan ook gebruik word om vliegtuie te monitor. Elke vliegtuig het 'n swart boks wat tydreekse vir verskillende vliegtuiggesondheidsparameters versamel. Tydreekse word ook in die lugvaartbedryf gebruik.
  • Gesondheidsorg is bloeddruk, pols, ens.

Miskien is daar nog toepassings waarvan ek vergeet het, maar ek hoop jy verstaan ​​dat tydreekse aktief in die moderne wêreld gebruik word. En die gebruik daarvan neem elke jaar toe.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Waarvoor is 'n tydreeksdatabasis? Hoekom kan jy nie 'n gereelde relasionele databasis gebruik om tydreekse te stoor nie?

Omdat tydreekse gewoonlik 'n groot hoeveelheid inligting bevat wat moeilik is om in konvensionele databasisse te stoor en te verwerk. Daarom het gespesialiseerde databasisse vir tydreekse verskyn. Hierdie basisse stoor punte effektief (timestamp, value) met die gegewe sleutel. Hulle bied 'n API vir die lees van gestoorde data per sleutel, deur een sleutel-waarde-paar, of deur verskeie sulke pare, of deur regexp. Byvoorbeeld, jy wil die SVE-lading van al jou dienste in 'n datasentrum in Amerika vind, dan moet jy hierdie pseudo-navraag gebruik.

Tipies is tydreeksdatabasisse gespesialiseerde navraagtale omdat SQL vir tydreekse nie goed geskik is nie. Alhoewel daar databasisse is wat SQL ondersteun, pas dit nie baie goed nie. Beter geskikte navraagtale soos PromQL, InfluxQL, Flux, Q. Ek hoop iemand het ten minste een van hierdie tale gehoor. Baie van julle het waarskynlik van PromQL gehoor. Dit is die Prometheus-navraagtaal.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hier is hoe die argitektuur van 'n moderne databasis vir tydreekse lyk deur VictoriaMetrics as 'n voorbeeld te gebruik.

Dit bestaan ​​uit twee dele. Dit is die stoor vir die omgekeerde indeks en die stoor vir die tydreekswaardes. Hierdie bewaarplekke is geskei.

Wanneer 'n nuwe rekord in die databasis aankom, kry ons eers toegang tot die omgekeerde indeks om die tydreeks identifiseerder vir die gegewe stel te vind label=value vir hierdie maatstaf. Ons vind hierdie identifiseerder en stoor die waarde in die datastoor.

Wanneer een of ander versoek inkom om data van TSDB te gaan haal, klim ons eerstens in die omgekeerde indeks. Ons kry alles timeseries_ids rekords wat by die gegewe stel pas label=value. En dan kry ons al die nodige data van die datawinkel wat geïndekseer is deur timeseries_ids.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Kom ons kyk na 'n voorbeeld van hoe 'n tydreeksdatabasis 'n inkomende kiesnavraag hanteer.

  • Eerstens kry sy alles timeseries_ids van die omgekeerde indeks wat die gegewe pare bevat label=value, of voldoen aan die gegewe gereelde uitdrukking.
  • Dan kry sy al die datapunte van die datastoor op 'n gegewe tydinterval vir die gevind timeseries_ids.
  • Daarna doen die databasis 'n paar berekeninge op hierdie datapunte, volgens die gebruiker se versoek. En daarna gee die antwoord terug.

In hierdie aanbieding sal ek jou vertel van die eerste deel. Dit is 'n soektog timeseries_ids deur omgekeerde indeks. Oor die tweede deel en die derde deel kan jy later sien VictoriaMetrics bronne, of wag totdat ek ander verslae voorberei 🙂

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Kom ons begin met die omgekeerde indeks. Vir baie lyk dit dalk eenvoudig. Wie weet wat 'n omgekeerde indeks is en hoe dit werk? Ag, nie meer so baie mense nie. Kom ons probeer verstaan ​​wat dit is.

Trouens, alles is eenvoudig. Dit is net 'n woordeboek wat 'n sleutel tot 'n waarde karteer. Wat is 'n sleutel? Hierdie paartjie label=valueWaar label и value is lyne. En die waardes is 'n stel timeseries_ids, wat die gegewe paar insluit label=value.

Omgekeerde indeks laat jou toe om alles vinnig te vind timeseries_ids, wat gegee het label=value.

Dit laat jou ook toe om vinnig te vind timeseries_ids tydreekse vir veelvuldige pare label=value, of vir paartjies label=regexp. Hoe gebeur dit? Deur die kruising van die stel te vind timeseries_ids vir elke paar label=value.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Oorweeg verskeie implementerings van die omgekeerde indeks. Kom ons begin met die eenvoudigste naïewe implementering. Sy lyk so.

Funksie getMetricIDs kry 'n lys van snare. Elke reël bevat label=value. Hierdie funksie gee 'n lys terug metricIDs.

Hoe dit werk? Hier het ons 'n globale veranderlike genoem invertedIndex. Dit is 'n gewone woordeboekmap) wat die string na sny ints sal karteer. Die lyn bevat label=value.

Implementering van die funksie: kry metricIDs vir die eerste label=value, gaan dan deur al die res label=value, ons kry metricIDs vir hulle. En bel die funksie intersectInts, wat hieronder bespreek sal word. En hierdie funksie gee die snypunt van hierdie lyste terug.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Soos u kan sien, is die implementering van die omgekeerde indeks nie baie ingewikkeld nie. Maar dit is 'n naïewe implementering. Wat is haar tekortkominge? Die grootste nadeel van die naïewe implementering is dat ons so 'n omgekeerde indeks in RAM stoor. Nadat ons die toepassing herbegin het, verloor ons hierdie indeks. Daar word nie hierdie indeks op skyf gestoor nie. Vir 'n databasis is so 'n omgekeerde indeks kwalik geskik.

Die tweede nadeel hou ook verband met geheue. Die omgekeerde indeks moet in RAM pas. As dit die grootte van RAM oorskry, is dit duidelik dat ons 'n geheuefout sal kry. En die program sal nie werk nie.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hierdie probleem kan opgelos word met behulp van klaargemaakte oplossings soos Vlak DBOf RocksDB.

Kortom, ons het 'n databasis nodig wat ons in staat stel om drie dinge vinnig te doen.

  • Die eerste operasie is skryf ключ-значение na hierdie basis. Sy doen dit baie vinnig. ключ-значение is arbitrêre snare.
  • Die tweede bewerking is 'n vinnige soektog na 'n waarde deur 'n gegewe sleutel.
  • En die derde operasie is 'n vinnige soektog na alle waardes deur 'n gegewe voorvoegsel.

LevelDB en RocksDB - Hierdie databasisse is ontwikkel deur Google en Facebook. Eerste het LevelDB gekom. Toe het die ouens van Facebook LevelDB geneem en dit begin verbeter, RocksDB gemaak. Nou werk byna alle interne databasisse op RocksDB binne Facebook, insluitend hulle is oorgedra na RocksDB en MySQL. Hulle het hom genoem myrocks.

'n Omgekeerde indeks kan geïmplementeer word met behulp van LevelDB. Hoe om dit te doen? Ons stoor as 'n sleutel label=value. En as 'n waarde - die identifiseerder van die tydreeks, waar daar 'n paar is label=value.

As ons baie tydreekse met hierdie paar het label=value, dan sal daar baie rye in hierdie databasis wees met dieselfde sleutel en verskillende timeseries_ids. Om 'n lys van almal te kry timeseries_idswat hiermee begin label=prefix, doen ons 'n reeksskandering waarvoor hierdie databasis geoptimaliseer is. Dit wil sê, ons kies alle lyne wat begin met label=prefix en kry die nodige timeseries_ids.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hier is 'n voorbeeldimplementering van hoe dit in Go sal lyk. Ons het 'n omgekeerde indeks. Dit is LevelDB.

Die funksie is dieselfde as vir die naïewe implementering. Dit herhaal amper reël vir reël die naïewe implementering. Die enigste punt is dat in plaas daarvan om na te verwys map ons verwys na die omgekeerde indeks. Ons kry al die waardes vir die eerste label=value. Dan gaan ons deur al die oorblywende pare label=value en kry die ooreenstemmende stelle metrieke ID's daarvoor. Dan vind ons die kruising.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Alles blyk in orde te wees, maar hierdie oplossing het nadele. VictoriaMetrics het aanvanklik 'n omgekeerde indeks geïmplementeer gebaseer op LevelDB. Maar op die ou end moes ek dit prysgee.

Hoekom? Omdat LevelDB stadiger is as die naïewe implementering. In 'n naïewe implementering, vir 'n gegewe sleutel, kry ons dadelik die hele sny metricIDs. Dit is 'n baie vinnige operasie - die hele sny is gereed vir gebruik.

In LevelDB, op elke funksie oproep GetValues jy moet deur al die lyne gaan wat mee begin label=value. En vir elke reël kry die waarde timeseries_ids. Van so 'n timeseries_ids versamel 'n sny hiervan timeseries_ids. Dit is duidelik dat dit baie stadiger is as om net met 'n sleutel toegang tot 'n gewone kaart te kry.

Die tweede nadeel is dat LevelDB in C geskryf is. Toegang tot C-funksies vanaf Go is nie baie vinnig nie. Dit neem honderde nanosekondes. Dit is nie baie vinnig nie, want in vergelyking met 'n gewone funksie-oproep wat in go geskryf is, wat 1-5 nanosekondes neem, is die prestasieverskil dosyne kere. Vir VictoriaMetrics was dit 'n noodlottige fout 🙂

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

So ek het my eie implementering van die omgekeerde indeks geskryf. En haar gebel saamsmelt.

Mergeset is gebaseer op die MergeTree-datastruktuur. Hierdie datastruktuur is by ClickHouse geleen. Natuurlik moet die samesmelting geoptimaliseer word vir vinnige herwinning timeseries_ids deur die gegewe sleutel. Mergeset is geheel en al in Go geskryf. Jy kan sien VictoriaMetrics-bronne op GitHub. Die samesmeltingsimplementering is in die gids /lib/mergeset. Jy kan probeer uitvind wat daar aangaan.

Die samesmeltings-API is baie soortgelyk aan LevelDB en RocksDB. Dit wil sê, dit laat jou toe om vinnig nuwe rekords daar te stoor en vinnig rekords met 'n gegewe voorvoegsel te kies.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Ons sal later oor die nadele van mergeset praat. Kom ons praat nou oor die probleme met VictoriaMetrics in produksie by die implementering van die omgekeerde indeks.

Hoekom het hulle opgestaan?

Die eerste rede is die hoë omsetkoers. In Russies vertaal, is dit 'n gereelde verandering in tydreekse. Dit is wanneer die tydreeks eindig en 'n nuwe reeks begin, of baie nuwe tydreekse begin. En dit gebeur dikwels.

Die tweede rede is die groot aantal tydreekse. In die begin, toe monitering gewild geword het, was die aantal tydreekse klein. Byvoorbeeld, op elke rekenaar moet jy die las van die verwerker, geheue, netwerk en skyf monitor. 4 tydreekse per rekenaar. Kom ons sê jy het 100 rekenaars en 400 tydreekse. Dit is baie min.

Met verloop van tyd het mense met die idee vorendag gekom dat meer gedetailleerde inligting gemeet kan word. Om byvoorbeeld die las nie van die hele verwerker te meet nie, maar afsonderlik van elke verwerkerkern. As jy 40 verwerkerkerne het, het jy dus 40 keer meer tydreekse om verwerkerlading te meet.

Maar dit is nie al nie. Elke verwerkerkern kan verskeie toestande hê, soos ledig wanneer dit ledig is. Sowel as werk in gebruikersruimte, werk in kernspasie en ander toestande. En elke so 'n toestand kan ook as 'n aparte tydreeks gemeet word. Dit verhoog ook die aantal rye met 7-8 keer.

Van een maatstaf het ons 40 x 8 = 320 maatstawwe vir net een rekenaar gekry. Ons vermenigvuldig met 100, ons kry 32 000 in plaas van 400.

Toe kom Kubernetes. En dit het dit steeds erger gemaak, want baie verskillende dienste kan in Kubernetes aangebied word. Elke diens in Kubernetes bestaan ​​uit baie peule. En dit alles moet gemonitor word. Daarbenewens het ons 'n konstante ontplooiing van nuwe weergawes van jou dienste. Vir elke nuwe weergawe moet jy nuwe tydreekse skep. Gevolglik groei die aantal tydreekse eksponensieel en word ons gekonfronteer met die probleem van 'n groot aantal tydreekse, wat hoë-kardinaliteit genoem word. VictoriaMetrics doen dit goed in vergelyking met ander tydreeksdatabasisse.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Kom ons kyk van naderby na die hoë afloopkoers. Wat veroorsaak 'n hoë omlooptempo in produksie? Omdat sommige betekenisse van etikette en etikette voortdurend verander.

Kom ons neem byvoorbeeld Kubernetes, wat die konsep het deployment, dit wil sê wanneer 'n nuwe weergawe van jou toepassing ontplooi word. Om een ​​of ander rede het die Kubernetes-ontwikkelaars besluit om die implementering-ID by die etiket te voeg.

Waartoe het dit gelei? Vir die feit dat met elke nuwe ontplooiing al die ou tydreekse onderbreek word, en in plaas daarvan, begin nuwe tydreekse met 'n nuwe etiketwaarde deployment_id. Daar kan honderdduisende en selfs miljoene sulke rye wees.

'n Belangrike kenmerk van dit alles is dat die totale aantal tydreekse groei, maar die aantal tydreekse wat tans aktief is, waarop data kom, bly konstant. Hierdie toestand word 'n hoë churn rate genoem.

Die hoofprobleem van 'n hoë churn rate is om 'n konstante soekspoed vir alle tydreekse vir 'n gegewe stel etikette vir 'n geruime tydsinterval te verskaf. Dit is gewoonlik die tydinterval vir die laaste uur of die laaste dag.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hoe om hierdie probleem op te los? Hier is die eerste opsie. Dit is om die omgekeerde indeks in onafhanklike tyddele te verdeel. Dit wil sê, 'n tydsinterval gaan verby, ons werk klaar met die omgekeerde stroomindeks. En ons skep 'n nuwe omgekeerde indeks. Nog 'n tydinterval gaan verby, ons skep nog een en nog een.

En wanneer steekproefneming van hierdie omgekeerde indekse geneem word, vind ons 'n stel omgekeerde indekse wat binne die gegewe interval val. En dienooreenkomstig kies ons die ID van die tydreeks van daar af.

Dit spaar hulpbronne omdat ons nie hoef te kyk na dele wat nie binne die gegewe interval val nie. Dit is, gewoonlik, as ons data vir die laaste uur kies, dan slaan ons versoeke vir vorige tydintervalle oor.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Daar is 'n ander oplossing vir hierdie probleem. Dit is om vir elke dag 'n aparte lys van ID's van die tydreekse wat op daardie dag plaasgevind het, te stoor.

Die voordeel van hierdie oplossing bo die vorige oplossing is dat ons nie tydreeksinligting dupliseer wat nie mettertyd verdwyn nie. Hulle is permanent en verander nie.

Die nadeel is dat so 'n oplossing moeiliker is om te implementeer en moeiliker om te ontfout. En VictoriaMetrics het hierdie oplossing gekies. Dit is hoe dit histories gebeur het. Hierdie oplossing vertoon hom ook goed, in vergelyking met die vorige een. Omdat hierdie oplossing nie geïmplementeer is nie as gevolg van die feit dat jy data in elke partisie moet dupliseer vir tydreekse wat nie verander nie, dit wil sê wat nie mettertyd verdwyn nie. VictoriaMetrics is hoofsaaklik geoptimaliseer vir skyfspasieverbruik, en die vorige implementering het skyfspasieverbruik vererger. Maar hierdie implementering is beter geskik om skyfspasieverbruik te verminder, so dit is gekies.

Ek moes teen haar veg. Die stryd was dat jy in hierdie implementering steeds 'n veel groter aantal moet kies timeseries_ids vir data as wanneer die omgekeerde indeks deur tyd gedeel word.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hoe het ons hierdie probleem opgelos? Ons het dit op 'n oorspronklike manier opgelos - deur verskeie tydreeksidentifiseerders in elke rekord van die omgekeerde indeks te stoor in plaas van een identifiseerder. dit wil sê ons het die sleutel label=value, wat in elke tydreeks voorkom. En nou spaar ons 'n paar timeseries_ids in een inskrywing.

Hier is 'n voorbeeld. Ons het voorheen N inskrywings gehad, en nou het ons een inskrywing wat dieselfde voorvoegsel as al die ander het. Vir die vorige inskrywing bevat die waarde al die ID's van die tydreeks.

Dit het dit moontlik gemaak om die skanderingspoed van so 'n omgekeerde indeks tot 10 keer te verhoog. En toegelaat om geheueverbruik vir die kas te verminder, want nou stoor ons die string label=value net een keer in die kas saam N keer. En hierdie lyn kan groot wees as jy lang lyne gestoor het in etikette en etikette wat Kubernetes daarvan hou om daarheen te stoot.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Nog 'n opsie om die soektog op die omgekeerde indeks te bespoedig, is sharding. Skep verskeie omgekeerde indekse in plaas van een en verdeel data tussen hulle per sleutel. Hierdie is 'n stel key=value stoom. Dit wil sê, ons kry verskeie onafhanklike omgekeerde indekse wat ons parallel op verskeie verwerkers kan poll. Vorige implementerings het slegs enkele verwerkermodus toegelaat, dit wil sê die skandering van data op slegs een kern. Hierdie oplossing laat jou toe om data op verskeie kerne gelyktydig te skandeer, soos ClickHouse daarvan hou om te doen. Dit is wat ons beplan om te implementeer.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

En nou terug na ons ramme – na die kruisingsfunksie timeseries_ids. Kom ons kyk na wat implementerings kan wees. Hierdie funksie laat jou toe om te vind timeseries_ids vir 'n gegewe stel label=value.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Die eerste opsie is 'n naïewe implementering. Twee geneste lusse. Hier kom ons by die insette van die funksie intersectInts twee snye - a и b. By die uitset moet dit die kruising van hierdie skywe aan ons terugstuur.

Die naïewe implementering lyk so. Ons herhaal oor alle waardes van sny a, binne hierdie lus gaan ons deur al die waardes van sny b. En ons vergelyk hulle. As hulle ooreenstem, dan het ons 'n kruising gevind. En stoor dit na result.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Wat is die nadele? Kwadratiese kompleksiteit is die grootste nadeel daarvan. Byvoorbeeld, as jy sny afmetings het a и b een miljoen elk, dan sal hierdie funksie nooit 'n antwoord aan jou gee nie. Omdat dit een triljoen iterasies sal moet doen, wat selfs vir moderne rekenaars baie is.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Die tweede implementering is gebaseer op kaart. Ons skep 'n kaart. Ons plaas alle waardes van sny in hierdie kaart a. Dan hardloop ons deur 'n aparte lus deur sny b. En kyk of hierdie waarde van sny is b in kaart. As dit bestaan, voeg dit dan by die resultaat.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Wat is die voordele? Die voordeel is dat daar slegs lineêre kompleksiteit is. Dit wil sê, die funksie sal baie vinniger uitgevoer word vir groot skywe. Vir 'n miljoenste sny sal hierdie funksie in 2 miljoen iterasies loop, in teenstelling met 'n triljoen iterasies, soos in die vorige funksie.

En die nadeel is dat hierdie funksie meer geheue benodig om hierdie kaart te skep.

Die tweede nadeel is 'n groot oorhoofse koste vir hashing. Hierdie tekortkoming is nie baie duidelik nie. En vir ons was dit ook nie baie voor die hand liggend nie, so aanvanklik in VictoriaMetrics was die implementering van kruising deur kaart. Maar toe het profilering gewys dat die hoofverwerker tyd bestee word om na die kaart te skryf en te kyk vir die teenwoordigheid van 'n waarde in hierdie kaart.

Hoekom mors hierdie plekke CPU-tyd? Want in hierdie reëls voer Go 'n hashing-operasie uit. Dit wil sê, dit bereken die hash vanaf die sleutel, om dit later by die gegewe indeks in die HashMap te verkry. Die hash-berekeningsbewerking neem tientalle nanosekondes. Dit is stadig vir VictoriaMetrics.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Ek het besluit om 'n bitset te implementeer wat spesifiek vir hierdie geval geoptimaliseer is. Dit is hoe die kruising van die twee skywe nou lyk. Hier skep ons 'n bitset. Ons voeg elemente van die eerste sny daarby. Dan kyk ons ​​na die teenwoordigheid van hierdie elemente in die tweede sny. En voeg hulle by die resultaat. Dit wil sê, dit verskil amper nie van die vorige voorbeeld nie. Die enigste ding is dat ons die oproep om te kaarteer met persoonlike funksies hier vervang het add и has.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Met die eerste oogopslag blyk dit dat dit stadiger moet werk as die standaardkaart voorheen daar gebruik is, en dan word 'n paar ander funksies genoem, maar profilering wys dat hierdie ding 10 keer vinniger is as die standaardkaart vir die VictoriaMetrics-geval.

Daarbenewens gebruik dit baie minder geheue in vergelyking met die kaartimplementering. Omdat ons bisse hier berg in plaas van agt-grepe waardes.

Die nadeel van so 'n implementering is dat dit nie so ooglopend, nie triviaal is nie.

Nog 'n nadeel wat baie dalk nie raaksien nie, is dat hierdie implementering in sommige gevalle dalk nie goed werk nie. Dit wil sê, dit is geoptimaliseer vir 'n spesifieke geval, vir hierdie geval van die kruising van ID's van die VictoriaMetrics-tydreeks. Dit beteken nie dat dit vir alle gevalle geskik is nie. As dit verkeerd gebruik word, sal ons nie 'n prestasieverhoging kry nie, maar 'n uit geheue fout en 'n prestasie verlangsaming.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Oorweeg die implementering van hierdie struktuur. As jy dit wil sien, is dit in die VictoriaMetrics-bronne, in die gids lib/uint64set. Dit is spesifiek geoptimaliseer vir die VictoriaMetrics-geval, waar timeseries_id is 'n 64-bis waarde, waar die eerste 32 bisse basies konstant is en slegs die laaste 32 bisse verander.

Hierdie datastruktuur word nie op skyf gestoor nie, dit werk net in die geheue.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hier is sy API. Dit is nie baie ingewikkeld nie. Die API is spesifiek aangepas vir die spesifieke gebruiksgeval van VictoriaMetrics. Dit wil sê, daar is geen ekstra funksies nie. Hier is die funksies wat uitdruklik deur VictoriaMetrics gebruik word.

Daar is funksies add, wat nuwe waardes byvoeg. Het 'n funksie has, wat na nuwe waardes kyk. En daar is 'n funksie del, wat waardes verwyder. Daar is 'n helperfunksie len, wat die grootte van die stel teruggee. Funksie clone kloon die stel. En funksie appendto skakel hierdie stel om na sny timeseries_ids.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Hier is hoe die implementering van hierdie datastruktuur lyk. Die stel het twee elemente:

  • ItemsCount is 'n hulpveld om die aantal elemente in die stel vinnig terug te gee. Ons kon sonder hierdie hulpveld klaargekom het, maar ons moes dit hier byvoeg, want VictoriaMetrics ondervra dikwels die lengte van die bitset in sy algoritmes.

  • Die tweede veld is buckets. Dit is 'n sny uit 'n struktuur bucket32. Elke struktuur bevat hi veld. Dit is die top 32 bisse. En twee snye - b16his и buckets van bucket16 strukture.

Die boonste 16 bisse van die tweede deel van die 64-bis-struktuur word hier gestoor. En bitsets word hier gestoor vir die onderste 16 bisse van elke greep.

Bucket64 bestaan ​​uit 'n skikking uint64. Die lengte word bereken deur hierdie konstantes te gebruik. In een bucket16 maksimum gestoor kan word 2^16=65536 bietjie. As dit deur 8 gedeel word, dan is dit 8 kilogrepe. Deel weer deur 8, dit is 1000 uint64 betekenis. d.w.s. Bucket16 - dit is ons 8-kilogreep-struktuur.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Kom ons kyk hoe een van die metodes van hierdie struktuur om 'n nuwe waarde toe te voeg, geïmplementeer word.

Alles begin met uint64 waardes. Ons bereken die boonste 32 bisse, ons bereken die onderste 32 bisse. Ons gaan deur alles buckets. Vergelyk die boonste 32 stukkies in elke emmer met die waarde wat bygevoeg word. En as hulle ooreenstem, dan roep ons die funksie add in struktuur b32 buckets. En voeg die onderste 32 stukkies daar by. En as dit terugkom true, dan beteken dit dat ons so 'n waarde daar bygevoeg het en ons het nie so 'n waarde gehad nie. As dit terugkeer false, dan het so 'n waarde reeds bestaan. Dan vermeerder ons die aantal elemente in die struktuur.

As ons nie die regte een gekry het nie bucket met die verlangde hi-waarde, dan roep ons die funksie addAlloc, wat 'n nuwe sal maak bucket, voeg dit by die emmerstruktuur.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Dit is die implementering van die funksie b32.add. Dit is soortgelyk aan die vorige implementering. Ons bereken hoë 16 bisse, lae 16 bisse.

Dan gaan ons deur al die top 16 stukkies. Ons vind wedstryde. En as daar 'n passing is, noem ons die add-metode, waarvoor ons op die volgende bladsy sal oorweeg bucket16.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

En hier is die laagste vlak, wat soveel as moontlik geoptimaliseer moet word. Ons bereken vir uint64 id waarde in sny bietjie ook bitmask. Dit is 'n masker vir hierdie 64-bis waarde, waardeur jy kan kyk vir die teenwoordigheid van hierdie bietjie, of stel dit. Ons kyk of hierdie bietjie gestel is en stel dit en gee die teenwoordigheid terug. Hier is ons implementering, wat ons in staat gestel het om die werking van kruis-ID's van tydreekse met 10 keer te versnel in vergelyking met konvensionele kaarte.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Benewens hierdie optimalisering, het VictoriaMetrics baie ander optimaliserings. Die meeste van hierdie optimaliserings is vir 'n rede bygevoeg, maar na kodeprofilering in produksie.

Dit is die hoofreël van optimalisering - moenie optimalisering byvoeg nie, met die veronderstelling dat daar 'n bottelnek sal wees, want dit kan blyk dat daar nie 'n bottelnek sal wees nie. Optimalisering verswak gewoonlik die kwaliteit van die kode. Daarom is dit die moeite werd om eers na profilering te optimaliseer, en verkieslik in produksie, sodat dit werklike data is. Vir diegene wat belangstel, kan u na die VictoriaMetrics-bronkode kyk en ander optimaliserings wat daar is, verken.

Gaan optimaliserings in VictoriaMetrics. Alexander Valyalkin

Ek het 'n vraag oor bitset. Baie soortgelyk aan die C++ implementering van vektor bool, bitset geoptimaliseer. Het jy die implementering daarvandaan geneem?

Nee, nie van daar af nie. By die implementering van hierdie bitset, is ek gelei deur die kennis van die struktuur van hierdie ids-tydreekse, wat in VictoriaMetrics gebruik word. En hul struktuur is so dat die boonste 32 bisse basies konstant is. Die onderste 32 bisse is onderhewig aan verandering. Hoe laer die bietjie, hoe meer dikwels kan dit verander. Daarom is hierdie implementering geoptimaliseer vir hierdie datastruktuur. Die C++ implementering, sover ek weet, is geoptimaliseer vir die algemene geval. As jy optimalisering vir die algemene geval doen, beteken dit dat dit nie die mees optimale vir 'n spesifieke geval sal wees nie.

Ek raai u aan om na die verslag van Alexei Milovid te kyk. Sowat 'n maand gelede het hy gepraat oor optimaliserings in ClickHouse vir spesifieke spesialisasies. Hy sê net in die algemene geval is die C++-implementering of een of ander implementering aangepas vir gemiddeld goeie werk in 'n hospitaal. Dit kan slegter vaar as 'n gespesialiseerde implementering vir spesifieke kennis, soos ons s'n, wanneer ons weet dat die boonste 32 bisse meestal konstant is.

Ek het 'n tweede vraag. Wat is die fundamentele verskil van InfluxDB?

Daar is baie kardinale verskille. As in terme van werkverrigting en geheueverbruik, dan toon InfluxDB in toetse 10 keer meer geheueverbruik vir hoë kardinaliteit tydreekse, wanneer jy baie van hulle het, byvoorbeeld miljoene. Byvoorbeeld, VictoriaMetrics verbruik 1 GB per miljoen aktiewe rye, terwyl InfluxDB 10 GB verbruik. En dit is 'n groot verskil.

Die tweede kardinale verskil is dat InfluxDB vreemde navraagtale het - Flux en InfluxQL. Hulle is nie baie gerieflik om met tydreekse te werk in vergelyking met PromQL, wat deur VictoriaMetrics ondersteun word. PromQL is 'n navraagtaal van Prometheus.

En nog 'n verskil is dat InfluxDB 'n effens vreemde datamodel het, waar elke lyn verskeie velde met 'n ander stel etikette kan stoor. Hierdie reëls word verder in allerlei tabelle verdeel. Hierdie bykomende komplikasies bemoeilik die daaropvolgende werk met hierdie basis. Dit is moeilik om te onderhou en te verstaan.

Alles is baie eenvoudiger in VictoriaMetrics. Daar is elke tydreeks 'n sleutelwaarde. 'n Waarde is 'n stel punte − (timestamp, value), en die sleutel is die stel label=value. Daar is geen skeiding tussen velde en afmetings nie. Dit laat jou toe om enige data te kies en dan te kombineer, optel, aftrek, vermenigvuldig, deel dit, anders as InfluxDB, waar berekeninge tussen verskillende rye steeds nie geïmplementeer word nie, sover ek weet. Selfs as dit geïmplementeer is, is dit moeilik, jy moet 'n klomp kode skryf.

Ek het 'n verduidelikende vraag. Ek het reg verstaan ​​dat daar 'n soort probleem was waaroor jy gepraat het, dat hierdie omgekeerde indeks nie in die geheue pas nie, so daar is partisies daar aan die gang?

Eerstens het ek 'n naïewe implementering van 'n omgekeerde indeks op 'n standaard Go's-kaart gewys. Hierdie implementering is nie geskik vir databasisse nie, want hierdie omgekeerde indeks word nie op skyf gestoor nie, en die databasis moet op skyf stoor sodat hierdie data beskikbaar bly met herbegin. In hierdie implementering, wanneer jy die toepassing herbegin, sal jou omgekeerde indeks verdwyn. En jy sal toegang tot al die data verloor omdat jy dit nie sal kan vind nie.

Hallo! Dankie vir die verslag! My naam is Pavel. Ek is van Wildberries. Ek het 'n paar vrae vir jou. Vraag een. Dink jy dat as jy 'n ander beginsel gekies het toe jy die argitektuur van jou toepassing gebou het en data volgens tyd gepartisioneer het, dan dalk die interseksie van data kon doen wanneer jy soek, slegs gebaseer op die feit dat een partisie data bevat vir een tydperk van tyd , dit wil sê in een tydsinterval en jy hoef nie bekommerd te wees oor die feit dat jou stukke anders versprei is nie? Vraag nommer 2 - aangesien jy 'n soortgelyke algoritme met bitset en al die ander implementeer, dan het jy dalk probeer om verwerkerinstruksies te gebruik? Miskien het jy sulke optimaliserings probeer?

Ek sal die tweede een beantwoord. Ons het nog nie daar uitgekom nie. Maar as dit nodig is, sal ons. Wat was die eerste vraag?

Jy het twee scenario's bespreek. En hulle het gesê dat hulle die tweede een met 'n meer komplekse implementering gekies het. En hulle het nie die eerste een verkies nie, waar die data volgens tyd verdeel word.

Ja. In die eerste geval sal die totale volume van die indeks groter wees, want in elke partisie sal ons duplikaatdata moet stoor vir daardie tydreekse wat deur al hierdie partisies voortgaan. En as jy 'n klein churn rate vir tydreekse het, dit wil sê dieselfde reekse word voortdurend gebruik, dan sal ons in die eerste geval baie meer verloor in terme van skyfspasie wat beset is in vergelyking met die tweede geval.

En so - ja, partisie volgens tyd is 'n goeie opsie. Prometheus gebruik dit. Maar Prometheus het nog 'n nadeel. Wanneer hierdie stukke data saamgevoeg word, moet dit meta-inligting vir alle etikette en tydreekse in die geheue hou. Daarom, as die stukke data groot is, wat dit saamsmelt, dan groei die geheueverbruik baie sterk wanneer dit saamgevoeg word, anders as VictoriaMetrics. By samesmelting verbruik VictoriaMetrics glad nie geheue nie, 'n paar kilogrepe word daar verbruik, ongeag die grootte van die datastukke wat saamgevoeg word.

Die algoritme wat jy het, gebruik geheue. Dit merk tydreeksmerke wat waardes het. En op hierdie manier kontroleer jy die gepaarde teenwoordigheid in een data-skikking en in 'n ander. En jy verstaan ​​of kruising gebeur het of nie. Tipies implementeer databasisse wysers, iterators wat hul huidige waarde stoor en deur gesorteerde data loop, as gevolg van die eenvoudige kompleksiteit van hierdie bewerkings.

Hoekom gebruik ons ​​nie wysers om data te deurkruis nie?

Ja.

In ons LevelDB of in samesmelting word net gesorteerde lyne gestoor. Ons kan met die wyser loop en die kruising vind. Hoekom gebruik ons ​​dit nie? Want dit is stadig. Omdat wysers impliseer dat jy 'n funksie vir elke reël moet oproep. 'n Funksie-oproep is 5 nanosekondes. En as jy 100 000 000 reëls het, dan blyk dit dat ons 'n halwe sekonde net aan 'n funksie-oproep spandeer.

Daar is so iets, ja. En my laaste vraag. Die vraag klink dalk 'n bietjie vreemd. Waarom, ten tyde van die aankoms van data, is dit onmoontlik om al die nodige aggregate te tel en dit in die vereiste vorm te stoor? Waarom groot volumes spaar aan sommige stelsels soos VictoriaMetrics, ClickHouse, ens., om later baie tyd daaraan te spandeer?

Ek sal 'n voorbeeld gee om dit duideliker te maak. Kom ons sê hoe 'n klein speelgoed spoedmeter werk? Dit teken die afstand aan wat jy afgelê het, en voeg dit heeltyd by een waarde, tot die tweede keer. En verdeel. En kry gemiddelde spoed. Jy kan omtrent dieselfde doen. Tel al die nodige feite op die vlieg bymekaar.

Goed, ek verstaan ​​die vraag. Jou voorbeeld het 'n plek om te woon. As jy weet watter aggregate jy nodig het, dan is dit die beste implementering. Maar die probleem is dat mense hierdie statistieke stoor, sommige data in ClickHouse, en hulle weet steeds nie hoe hulle dit in die toekoms sal saamvoeg en filtreer nie, so jy moet al die rou data stoor. Maar as jy weet dat jy iets moet bereken, hoekom moet jy dit dan nie bereken in plaas daarvan om 'n klomp rou waardes daar te stoor nie? Maar dit is net as jy presies weet wat jy nodig het.

Terloops, databasisse vir die stoor van tydreekse ondersteun telaggregate. Byvoorbeeld, Prometheus ondersteun opname reëls. Dit wil sê, dit kan gedoen word as jy weet watter eenhede jy nodig het. Dit is nog nie in VictoriaMetrics beskikbaar nie, maar dit word gewoonlik voorafgegaan deur Prometheus, waarin jy dit in herkoderingsreëls kan doen.

Byvoorbeeld, in 'n vorige werk moes jy die aantal gebeurtenisse in 'n skuifvenster in die laaste uur tel. Die probleem is dat ek 'n pasgemaakte implementering in Go moes maak, dit wil sê 'n diens om hierdie ding te tel. Hierdie diens was uiteindelik nie-triviaal, want dit is moeilik om te tel. Die implementering kan eenvoudig wees as jy 'n paar aggregate met vaste tydintervalle moet tel. As jy gebeurtenisse in 'n skuifvenster wil tel, is dit nie so maklik soos dit lyk nie. Ek dink dit is nog nie in ClickHouse of in tydreeksdatabasisse geïmplementeer nie, want dit is moeilik om te implementeer.

En nog 'n vraag. Ons het net gepraat oor gemiddeldes, en ek het onthou dat daar eens iets soos Grafiet met 'n koolstof-agterkant was. En hy het geweet hoe om ou data uit te dun, dit wil sê om een ​​punt per minuut, een punt per uur te laat, ens. In beginsel is dit baie gerieflik as ons rou data, relatief gesproke, vir 'n maand nodig het, en alles anders kan uitgedun word uit. Maar Prometheus, VictoriaMetrics ondersteun nie hierdie funksionaliteit nie. Is dit beplan om ondersteun te word? Indien nie, hoekom nie?

Dankie vir die vraag. Ons gebruikers vra dit gereeld. Hulle vra wanneer ons ondersteuning vir uitdunning (afsteekproefneming) sal byvoeg. Hier is verskeie probleme. Eerstens, elke gebruiker verstaan downsampling iets van hul eie: iemand wil enige arbitrêre punt op 'n gegewe interval kry, iemand wil die maksimum, minimum, gemiddelde waardes hê. As baie stelsels data na jou databasis skryf, kan jy hulle nie een grootte pas by almal ry nie. Jy sal dalk vind dat jy 'n ander desimasie vir elke stelsel moet gebruik. En dit is moeilik om te implementeer.

En die tweede ding is dat VictoriaMetrics, soos ClickHouse, geoptimaliseer is om met 'n groot hoeveelheid rou data te werk, sodat dit in minder as 'n sekonde deur 'n miljard rye kan skuif as jy baie kerne in jou stelsel het. Tydreekspuntskandering in VictoriaMetrics - 50 000 000 punte per sekonde per kern. En daardie prestasie skaal tot die beskikbare kerns. Dit wil sê, as jy byvoorbeeld 20 kerne het, sal jy 'n miljard punte per sekonde skandeer. En hierdie eiendom van VictoriaMetrics en ClickHouse verminder die behoefte aan downsamling.

Nog 'n voordeel is dat VictoriaMetrics hierdie data doeltreffend saampers. Kompressie gemiddeld vir produksie is van 0,4 tot 0,8 grepe per punt. Elke punt is 'n tydstempel + waarde. En dit komprimeer gemiddeld minder as een greep.

Sergey. Ek het 'n vraag. Wat is die minimum opname tyd sny?

Een millisekonde. Ons het onlangs 'n gesprek gehad met ander tydreeksdatabasisontwikkelaars. Hul minimum hoeveelheid tyd is een sekonde. En in Graphite is dit byvoorbeeld ook een sekonde. OpenTSDB het ook een sekonde. In InfluxDB, nanosekonde-presisie. VictoriaMetrics het een millisekonde omdat Prometheus een millisekonde het. En VictoriaMetrics is oorspronklik ontwikkel as afstandberging vir Prometheus. Maar nou kan dit ook data van ander stelsels stoor.

Die persoon met wie ek gepraat het, sê dat hulle 'n tweede akkuraatheid het - dit is genoeg vir hulle, want dit hang af van die tipe data wat in die tydreeksdatabasis gestoor word. As dit DevOps-data of data van die infrastruktuur is waar jy dit met intervalle van 30 sekondes per minuut insamel, dan is daar genoeg sekonde-akkuraatheid, jy het nie minder nodig nie. En as jy hierdie data van hoëfrekwensie-handelstelsels insamel, benodig jy nanosekonde-presisie.

Millisekonde akkuraatheid in VictoriaMetrics is ook geskik vir 'n DevOps-geval, en kan geskik wees vir die meeste van die gevalle wat ek aan die begin van die verslag genoem het. Die enigste ding waarvoor dit dalk nie geskik is nie, is hoëfrekwensiehandelstelsels.

Dankie! En nog 'n vraag. Wat is verenigbaarheid in PromQL?

Volle terugwaartse versoenbaarheid. VictoriaMetrics ondersteun PromQL ten volle. Daarbenewens voeg dit nog 'n gevorderde funksionaliteit by PromQL, wat genoem word MetricsQL. Daar is 'n praatjie op YouTube oor hierdie uitgebreide funksionaliteit. Ek het in die lente by die Monitoring Meetup in St. Petersburg gepraat.

Telegramkanaal VictoriaMetrics.

Slegs geregistreerde gebruikers kan aan die opname deelneem. Meld aan, asseblief.

Wat keer jou om oor te skakel na VictoriaMetrics as 'n langtermynberging vir Prometheus? (Skryf in die kommentaar, ek sal by die meningspeiling voeg))

  • 71,4%Gebruik nie Prometheus5 nie

  • 28,6%Het nie geweet van VictoriaMetrics2 nie

7 gebruikers het gestem. 12 gebruikers het buite stemming gebly.

Bron: will.com

Voeg 'n opmerking