Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Azt javaslom, olvassa el Alexander Valyalkin 2019 végén készült jelentésének átiratát: „Optimalizálás a VictoriaMetricsben”

VictoriaMetrics — egy gyors és méretezhető DBMS adatok idősorok formájában történő tárolására és feldolgozására (a rekord időt és ennek az időnek megfelelő értékkészletet alkot, például az érzékelők állapotának időszakos lekérdezésével vagy az adatok összegyűjtésével mérőszámok).

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Itt egy link a riportról készült videóhoz - https://youtu.be/MZ5P21j_HLE

Diák

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Mondj valamit magadról. Alexander Valyalkin vagyok. Itt a GitHub-fiókomat. Szenvedélyesen érdekel a Go és a teljesítményoptimalizálás. Nagyon sok hasznos és nem túl hasznos könyvtárat írtam. Bármelyikkel kezdik fast, vagy azzal quick előtag.

Jelenleg a VictoriaMetrics-en dolgozom. Mi ez és mit csinálok ott? Ebben az előadásban erről fogok beszélni.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

A jelentés vázlata a következő:

  • Először is elmondom, mi az a VictoriaMetrics.
  • Akkor elmondom, hogy mik az idősorok.
  • Aztán elmondom, hogyan működik egy idősoros adatbázis.
  • Ezután az adatbázis architektúráról fogok beszélni: miből áll.
  • És akkor térjünk át a VictoriaMetrics optimalizálására. Ez az invertált index optimalizálása és a bitkészlet megvalósításának optimalizálása a Go-ban.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Tudja valaki a közönségből, hogy mi az a VictoriaMetrics? Hú, sokan tudják már. Ez jó hír. Azok számára, akik nem ismerik, ez egy idősoros adatbázis. A ClickHouse architektúrán, a ClickHouse megvalósítás néhány részletén alapul. Például: MergeTree, párhuzamos számítás az összes elérhető processzormagon és a teljesítmény optimalizálása a processzor gyorsítótárában elhelyezett adatblokkok segítségével.

A VictoriaMetrics jobb adattömörítést biztosít, mint más idősoros adatbázisok.

Függőlegesen skálázódik - vagyis több processzort, több RAM-ot adhat hozzá egy számítógéphez. A VictoriaMetrics sikeresen fogja használni ezeket a rendelkezésre álló erőforrásokat, és javítani fogja a lineáris termelékenységet.

A VictoriaMetrics vízszintesen is skálázódik - vagyis további csomópontokat adhat hozzá a VictoriaMetrics fürthöz, és teljesítménye szinte lineárisan nő.

Ahogy sejtetted, a VictoriaMetrics egy gyors adatbázis, mert másokat nem tudok írni. És a Go-ban van megírva, szóval erről beszélek ezen a találkozón.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Ki tudja, mi az az idősor? Ő is sok embert ismer. Az idősor párok sorozata (timestamp, значение), ahol ezek a párok idő szerint vannak rendezve. Az érték egy lebegőpontos szám – float64.

Minden idősort egyedileg azonosít egy kulcs. Miből áll ez a kulcs? Kulcs-érték párok nem üres halmazából áll.

Itt van egy példa egy idősorra. Ennek a sorozatnak a kulcsa a párok listája: __name__="cpu_usage" a mérőszám neve, instance="my-server" - ez az a számítógép, amelyen ezt a mérőszámot gyűjtik, datacenter="us-east" - ez az adatközpont, ahol ez a számítógép található.

Végül egy három kulcs-érték párból álló idősornévhez jutottunk. Ez a kulcs a párok listájának felel meg (timestamp, value). t1, t3, t3, ..., tN - ezek időbélyegek, 10, 20, 12, ..., 15 — a megfelelő értékeket. Ez a cpu-használat egy adott időpontban egy adott sorhoz.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Hol használhatók az idősorok? Van valakinek valami ötlete?

  • A DevOps-ban mérheti a CPU-t, a RAM-ot, a hálózatot, az rps-t, a hibák számát stb.
  • IoT - mérhetünk hőmérsékletet, nyomást, földrajzi koordinátákat és még valamit.
  • Pénzügy is – mindenféle részvény és valuta árait nyomon követhetjük.
  • Emellett az idősorok felhasználhatók a gyári gyártási folyamatok nyomon követésére. Vannak olyan felhasználók, akik a VictoriaMetrics segítségével figyelik a szélturbinákat a robotok számára.
  • Az idősorok hasznosak a különféle eszközök érzékelőitől való információgyűjtéshez is. Például egy motorhoz; abroncsnyomás mérésére; sebesség, távolság mérésére; benzinfogyasztás mérésére stb.
  • Az idősorok a repülőgépek megfigyelésére is használhatók. Minden repülőgépnek van egy fekete doboza, amely idősorokat gyűjt a repülőgép állapotának különböző paramétereihez. Az idősorokat a repülőgépiparban is használják.
  • Az egészségügy a vérnyomás, a pulzus stb.

Lehet, hogy vannak még olyan alkalmazások, amelyekről megfeledkeztem, de remélem, megérti, hogy az idősorokat aktívan használják a modern világban. És használatuk mennyisége évről évre nő.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Miért van szüksége idősoros adatbázisra? Miért nem lehet egy szabályos relációs adatbázist használni idősorok tárolására?

Ugyanis az idősorok általában nagy mennyiségű információt tartalmaznak, amit a hagyományos adatbázisokban nehéz tárolni és feldolgozni. Ezért megjelentek az idősorokhoz speciális adatbázisok. Ezek az alapok hatékonyan tárolják a pontokat (timestamp, value) a megadott kulccsal. API-t biztosítanak a tárolt adatok kulcsonkénti, egyetlen kulcs-értékpáronkénti, több kulcs-értékpáronkénti vagy reguláris kifejezés szerinti olvasásához. Például, ha Amerikában egy adatközpontban szeretné megkeresni az összes szolgáltatás CPU-terhelését, akkor ezt az ál-lekérdezést kell használnia.

Az idősoros adatbázisok jellemzően speciális lekérdezési nyelveket biztosítanak, mivel az idősoros SQL nem nagyon alkalmas. Bár vannak olyan adatbázisok, amelyek támogatják az SQL-t, ez nem túl alkalmas. Lekérdezési nyelvek, mint pl PromQL, InfluxQL, Fényáram, Q. Remélem, hogy valaki hallott legalább egy ilyen nyelvet. Valószínűleg sokan hallottak a PromQL-ről. Ez a Prometheus lekérdezési nyelv.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Így néz ki egy modern idősoros adatbázis-architektúra a VictoriaMetrics példaként.

Két részből áll. Ez az invertált index tárolása és az idősorok értékeinek tárolása. Ezek a tárolók el vannak különítve.

Amikor új rekord érkezik az adatbázisba, először elérjük az invertált indexet, hogy megtaláljuk az adott halmaz idősor azonosítóját label=value egy adott mérőszámhoz. Megkeressük ezt az azonosítót, és elmentjük az értéket az adattárba.

Amikor egy kérés érkezik adatok lekérésére a TSDB-ből, először az invertált indexre lépünk. Szerezzünk meg mindent timeseries_ids a készletnek megfelelő rekordokat label=value. Ezután megkapjuk az összes szükséges adatot az adattárházból, indexelve timeseries_ids.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Nézzünk egy példát arra, hogy egy idősoros adatbázis hogyan dolgozza fel a bejövő kiválasztási lekérdezést.

  • Először is mindent megkap timeseries_ids az adott párokat tartalmazó fordított indexből label=value, vagy megfelel egy adott reguláris kifejezésnek.
  • Ezután az összes adatpontot lekéri az adattárról adott időintervallumban a találtak számára timeseries_ids.
  • Ezek után az adatbázis a felhasználó kérésének megfelelően számításokat végez ezeken az adatpontokon. És utána visszaadja a választ.

Ebben az előadásban az első részről fogok mesélni. Ez egy keresés timeseries_ids fordított index szerint. A második és a harmadik részt később megtekinthetitek VictoriaMetrics források, vagy várja meg, míg más jelentéseket készítek :)

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Térjünk át a fordított indexre. Sokan azt gondolhatják, hogy ez egyszerű. Ki tudja, mi az invertált index és hogyan működik? Ó, már nem olyan sokan. Próbáljuk megérteni, mi az.

Valójában egyszerű. Ez egyszerűen egy szótár, amely leképez egy kulcsot egy értékhez. Mi az a kulcs? Ez a pár label=valueAhol label и value - ezek a vonalak. Az értékek pedig egy halmaz timeseries_ids, amely tartalmazza az adott párt label=value.

A fordított index lehetővé teszi, hogy mindent gyorsan megtaláljon timeseries_ids, amelyek adtak label=value.

Azt is lehetővé teszi, hogy gyorsan megtalálja timeseries_ids idősorok több párhoz label=value, vagy pároknak label=regexp. Hogyan történik ez? A halmaz metszéspontjának megtalálásával timeseries_ids minden párhoz label=value.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Nézzük meg a fordított index különféle megvalósításait. Kezdjük a legegyszerűbb naiv megvalósítással. Így néz ki.

Funkció getMetricIDs kap egy listát a karakterláncokról. Minden sor tartalmaz label=value. Ez a függvény egy listát ad vissza metricIDs.

Hogyan működik? Itt van egy globális változó, az úgynevezett invertedIndex. Ez egy szokásos szótár (map), amely leképezi a karakterláncot az ints szeletekre. A sor tartalmazza label=value.

Funkció megvalósítása: get metricIDs elsőre label=value, akkor minden máson keresztül megyünk label=value, értjük metricIDs nekik. És hívja meg a függvényt intersectInts, amelyről az alábbiakban lesz szó. Ez a függvény pedig ezeknek a listáknak a metszetét adja vissza.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Mint látható, az inverz index megvalósítása nem túl bonyolult. De ez naiv megvalósítás. Milyen hátrányai vannak? A naiv megvalósítás fő hátránya, hogy egy ilyen fordított indexet a RAM-ban tárolnak. Az alkalmazás újraindítása után elveszítjük ezt az indexet. Ezt az indexet nem lehet lemezre menteni. Egy ilyen fordított index valószínűleg nem alkalmas adatbázishoz.

A második hátrány szintén a memóriához kapcsolódik. Az invertált indexnek bele kell férnie a RAM-ba. Ha ez meghaladja a RAM méretét, akkor nyilvánvalóan kikapunk - memóriahiba. És a program nem fog működni.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Ezt a problémát kész megoldásokkal lehet megoldani, mint pl LevelDBVagy RocksDB.

Röviden, szükségünk van egy adatbázisra, amely lehetővé teszi három művelet gyors elvégzését.

  • Az első művelet a felvétel ключ-значение ehhez az adatbázishoz. Ezt nagyon gyorsan csinálja, hol ключ-значение tetszőleges karakterláncok.
  • A második művelet egy érték gyors keresése egy adott kulcs segítségével.
  • A harmadik művelet pedig az összes érték gyors keresése egy adott előtag alapján.

LevelDB és RocksDB – ezeket az adatbázisokat a Google és a Facebook fejlesztette ki. Először jött a LevelDB. Aztán a Facebook srácai felvették a LevelDB-t és elkezdték javítani, megcsinálták a RocksDB-t. Mostantól szinte minden belső adatbázis működik a RocksDB-n a Facebookon belül, beleértve azokat is, amelyeket átvittek a RocksDB-be és a MySQL-be. Elnevezték MyRocks.

Az invertált index a LevelDB segítségével valósítható meg. Hogyan kell csinálni? Kulcsként mentjük el label=value. Az érték pedig annak az idősornak az azonosítója, ahol a pár jelen van label=value.

Ha sok idősorunk van egy adott párral label=value, akkor ebben az adatbázisban sok sor lesz ugyanazzal a kulccsal és különböző timeseries_ids. Hogy kapjon egy listát az összesről timeseries_ids, amelyek ezzel kezdődnek label=prefix, tartományvizsgálatot végzünk, amelyre ez az adatbázis optimalizálva van. Vagyis kijelöljük az összes olyan sort, amely ezzel kezdődik label=prefix és megszerezze a szükségeset timeseries_ids.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Íme egy példa megvalósítás arra vonatkozóan, hogyan nézne ki a Go-ban. Fordított indexünk van. Ez a LevelDB.

A funkció ugyanaz, mint a naiv megvalósításnál. Szinte soronként megismétli a naiv megvalósítást. Az egyetlen lényeg az, hogy ahelyett, hogy felé fordulna map elérjük a fordított indexet. Minden értéket először megkapunk label=value. Ezután végigmegyünk az összes megmaradt páron label=value és lekérjük a megfelelő metrikaazonosító-készleteket. Aztán megtaláljuk a kereszteződést.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Úgy tűnik, minden rendben van, de ennek a megoldásnak vannak hátrányai. A VictoriaMetrics kezdetben fordított indexet vezetett be a LevelDB alapján. De végül fel kellett adnom.

Miért? Mert a LevelDB lassabb, mint a naiv megvalósítás. Egy naiv megvalósításban adott kulcs megadásával azonnal lekérjük a teljes szeletet metricIDs. Ez egy nagyon gyors művelet – az egész szelet használatra kész.

A LevelDB-ben minden egyes függvény meghívásakor GetValues végig kell menned az összes ezzel kezdődő soron label=value. És kapja meg az egyes sorok értékét timeseries_ids. Az ilyen timeseries_ids gyűjts egy szeletet ezekből timeseries_ids. Nyilvánvalóan ez sokkal lassabb, mint egyszerűen kulcs segítségével hozzáférni egy normál térképhez.

A második hátrány az, hogy a LevelDB C-ben van írva. A C függvények hívása Go-ból nem túl gyors. Ez több száz nanoszekundumot vesz igénybe. Ez nem túl gyors, mert a go-ban írt normál függvényhíváshoz képest, ami 1-5 nanoszekundumot vesz igénybe, több tízszeres a teljesítménybeli különbség. A VictoriaMetrics számára ez végzetes hiba volt :)

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Tehát megírtam a saját megvalósításomat az invertált indexről. És felhívta mergeset.

A Mergeset a MergeTree adatstruktúrán alapul. Ez az adatstruktúra a ClickHouse-tól származik. Nyilvánvaló, hogy a mergeset-et a gyors keresésre kell optimalizálni timeseries_ids a megadott kulcs szerint. A Mergeset teljes egészében Go nyelven íródott. Láthatod VictoriaMetrics források a GitHubon. A mergeset megvalósítása a mappában található /lib/mergeset. Megpróbálhatod kitalálni, mi folyik ott.

A mergeset API nagyon hasonlít a LevelDB-hez és a RocksDB-hez. Vagyis lehetővé teszi az új rekordok gyors mentését oda, és gyorsan kiválaszthatja a rekordokat egy adott előtag alapján.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

A mergeset hátrányairól később fogunk beszélni. Most beszéljünk arról, hogy milyen problémák merültek fel a VictoriaMetrics termelésben az inverz index alkalmazásakor.

Miért keletkeztek?

Az első ok a magas lemorzsolódási arány. Oroszra lefordítva ez az idősorok gyakori változása. Ekkor ér véget egy idősor és egy új sorozat kezdődik, vagy sok új idősor kezdődik. És ez gyakran megtörténik.

A második ok az idősorok nagy száma. Kezdetben, amikor a monitorozás egyre népszerűbb volt, az idősorok száma kicsi volt. Például minden számítógép esetében figyelnie kell a CPU, a memória, a hálózat és a lemez terhelését. Számítógépenként 4 idősor. Tegyük fel, hogy 100 számítógépe és 400 idősora van. Ez nagyon kevés.

Idővel az emberek rájöttek, hogy részletesebb információkat is mérhetnek. Például ne a teljes processzor terhelését mérje meg, hanem minden processzormagot külön-külön. Ha 40 processzormagja van, akkor 40-szer több idősor áll rendelkezésére a processzorterhelés mérésére.

De ez még nem minden. Minden processzormagnak több állapota lehet, például tétlen, amikor tétlen. És dolgozzon a felhasználói térben, a kerneltérben és más állapotokban. És minden ilyen állapot külön idősorként is mérhető. Ez ráadásul 7-8-szorosára növeli a sorok számát.

Egy metrikából 40 x 8 = 320 mérőszámot kaptunk egyetlen számítógépre. Szorozzuk meg 100-zal, 32 helyett 000 400-et kapunk.

Aztán jött Kubernetes. És ez még rosszabb lett, mert a Kubernetes számos különféle szolgáltatást tud fogadni. A Kubernetes minden szolgáltatása sok podból áll. És mindezt figyelemmel kell kísérni. Ezenkívül folyamatosan telepítjük szolgáltatásai új verzióit. Minden új verzióhoz új idősort kell létrehozni. Ennek eredményeként az idősorok száma exponenciálisan növekszik, és a nagyszámú idősor problémájával kell szembenéznünk, amit nagyszámúságnak nevezünk. A VictoriaMetrics más idősoros adatbázisokhoz képest sikeresen megbirkózik vele.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Nézzük meg közelebbről a magas lemorzsolódási arányt. Mi okozza a termelés magas lemorzsolódási arányát? Mivel a címkék és címkék egyes jelentései folyamatosan változnak.

Vegyük például a Kubernetes-t, aminek megvan a koncepciója deployment, azaz amikor az alkalmazás új verziója megjelenik. Valamilyen oknál fogva a Kubernetes fejlesztői úgy döntöttek, hogy hozzáadják a telepítési azonosítót a címkéhez.

Mihez vezetett ez? Ráadásul minden új telepítéskor az összes régi idősor megszakad, és helyettük új idősorok kezdődnek új címkeértékkel deployment_id. Több százezer, sőt millió is lehet ilyen sor.

Mindebben az a fontos, hogy az összes idősorok száma nő, de az aktuálisan aktív és adatokat fogadó idősorok száma állandó marad. Ezt az állapotot magas lemorzsolódási aránynak nevezik.

A nagy lemorzsolódási ráta fő problémája az, hogy egy adott címkekészlethez egy adott időintervallumban állandó keresési sebességet biztosítsunk minden idősorra. Ez általában az utolsó óra vagy az utolsó nap időtartama.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Hogyan lehet megoldani ezt a problémát? Itt az első lehetőség. Ez arra szolgál, hogy az invertált indexet idővel független részekre ossza fel. Vagyis eltelik némi időintervallum, befejezzük a munkát az aktuális fordított indexszel. És hozzon létre egy új fordított indexet. Újabb időintervallum telik el, létrehozunk még egyet és még egyet.

És amikor ezekből az invertált indexekből mintavételezünk, találunk egy olyan fordított indexet, amely az adott intervallumba esik. És ennek megfelelően onnan választjuk ki az idősor id-jét.

Ez erőforrást takarít meg, mert nem kell olyan részeket nézni, amelyek nem esnek az adott intervallumba. Azaz általában ha az utolsó óra adatait választjuk ki, akkor a korábbi időintervallumoknál kihagyjuk a lekérdezéseket.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Van egy másik lehetőség a probléma megoldására. Ennek célja, hogy minden naphoz külön listát tároljon az adott napon előforduló idősorok azonosítóiról.

Ennek a megoldásnak az az előnye az előző megoldáshoz képest, hogy nem duplikálunk idősoros információkat, amelyek idővel nem tűnnek el. Folyamatosan jelen vannak és nem változnak.

Hátránya, hogy egy ilyen megoldást nehezebb megvalósítani, és nehezebb a hibakeresés. A VictoriaMetrics pedig ezt a megoldást választotta. Történelmileg így történt. Ez a megoldás is jól teljesít az előzőhöz képest. Mert ez a megoldás azért nem valósult meg, mert minden partícióban meg kell duplikálni az adatokat olyan idősorokhoz, amelyek nem változnak, azaz nem tűnnek el az idő múlásával. A VictoriaMetrics elsősorban lemezterület-felhasználásra volt optimalizálva, és a korábbi megvalósítás rontotta a lemezterület-felhasználást. De ez a megvalósítás alkalmasabb a lemezterület-felhasználás minimalizálására, ezért ezt választottuk.

Meg kellett küzdenem vele. A küzdelem az volt, hogy ebben a megvalósításban még mindig sokkal nagyobb számot kell választani timeseries_ids adatok esetében, mint amikor az invertált index időbeli particionálva van.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Hogyan oldottuk meg ezt a problémát? Eredeti módon oldottuk meg - úgy, hogy egy azonosító helyett több idősor azonosítót tároltunk minden inverz indexbejegyzésben. Vagyis van kulcsunk label=value, ami minden idősorban előfordul. És most megmentünk néhányat timeseries_ids egy bejegyzésben.

Íme egy példa. Korábban N bejegyzésünk volt, de most van egy bejegyzésünk, amelynek előtagja megegyezik az összes többivel. Az előző bejegyzésnél az érték az összes idősor-azonosítót tartalmazza.

Ez lehetővé tette egy ilyen fordított index pásztázási sebességének akár 10-szeres növelését. És lehetővé tette számunkra, hogy csökkentsük a gyorsítótár memóriafelhasználását, mert most már tároljuk a karakterláncot label=value csak egyszer a gyorsítótárban együtt N-szer. És ez a sor nagy lehet, ha hosszú sorokat tárol a címkékben és címkékben, amelyeket a Kubernetes szeret oda tolni.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Egy másik lehetőség a fordított indexen végzett keresés felgyorsítására a felosztás. Több fordított index létrehozása egy helyett, és az adatok kulcsonkénti felosztása közöttük. Ez egy készlet key=value gőz. Vagyis több független invertált indexet kapunk, melyeket több processzoron párhuzamosan lekérdezhetünk. A korábbi megvalósítások csak egyprocesszoros üzemmódban engedélyezték a működést, azaz csak egy magon végzett adatok szkennelését. Ezzel a megoldással egyszerre több magon is szkennelhetünk adatokat, ahogy a ClickHouse szereti. Ezt tervezzük megvalósítani.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Most térjünk vissza a juhainkhoz – a metszéspont függvényhez timeseries_ids. Nézzük meg, milyen megvalósítások lehetnek. Ez a funkció lehetővé teszi, hogy megtalálja timeseries_ids adott halmazhoz label=value.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Az első lehetőség egy naiv megvalósítás. Két egymásba ágyazott hurok. Itt kapjuk meg a függvény bemenetét intersectInts két szelet - a и b. A kimeneten ezeknek a szeleteknek a metszéspontját kell visszaadnia nekünk.

Egy naiv megvalósítás így néz ki. A szeletből származó összes értéket iteráljuk a, ezen a hurkon belül végigmegyünk a szelet összes értékén b. És összehasonlítjuk őket. Ha egyeznek, akkor találtunk egy kereszteződést. És mentse el result.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Mik a hátrányai? A négyzetes összetettség a fő hátránya. Például, ha a méretei szelet a и b egyszerre egy milliót, akkor ez a függvény soha nem ad választ Önnek. Mert egy billió iterációt kell végrehajtania, ami még a modern számítógépeknél is sok.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

A második megvalósítás térképen alapul. Térképet készítünk. A szelet összes értékét ebbe a térképbe helyeztük a. Ezután külön hurokban megyünk át a szeleten b. És ellenőrizzük, hogy ez az érték a szeletből származik-e b térképen. Ha létezik, adja hozzá az eredményhez.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Milyen előnyökkel jár? Előnye, hogy csak lineáris komplexitás létezik. Vagyis a funkció sokkal gyorsabban fog végrehajtani nagyobb szeleteknél. Egy milliós méretű szelet esetében ez a függvény 2 millió iterációban fog végrehajtani, szemben az előző függvény billió iterációjával.

Hátránya, hogy ez a funkció több memóriát igényel a térkép létrehozásához.

A második hátrány a kivonatolás nagy általános költsége. Ez a hátrány nem túl nyilvánvaló. És számunkra ez sem volt túl nyilvánvaló, így eleinte a VictoriaMetrics-ben a kereszteződés megvalósítása térképen keresztül történt. De aztán a profilozás azt mutatta, hogy a fő processzor ideje a térképre való írással és egy érték jelenlétének ellenőrzésével telik el ezen a térképen.

Miért vesztegetik a CPU-időt ezeken a helyeken? Mivel a Go kivonatolási műveletet hajt végre ezeken a sorokon. Vagyis kiszámítja a kulcs hash értékét, hogy aztán hozzáférjen a HashMap adott indexén. A hash számítási művelet több tíz nanoszekundum alatt fejeződik be. Ez lassú a VictoriaMetrics számára.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Úgy döntöttem, hogy egy kifejezetten erre az esetre optimalizált bitkészletet implementálok. Így néz ki most két szelet metszéspontja. Itt létrehozunk egy bitkészletet. Az első szeletből elemeket adunk hozzá. Ezután ellenőrizzük ezen elemek jelenlétét a második szeletben. És add hozzá őket az eredményhez. Vagyis szinte semmiben sem különbözik az előző példától. Az egyetlen dolog itt az, hogy a térképhez való hozzáférést egyéni funkciókra cseréltük add и has.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Első pillantásra úgy tűnik, hogy ennek lassabban kellene működnie, ha korábban egy szabványos térképet használtak ott, majd néhány más függvényt hívnak meg, de a profilozás azt mutatja, hogy ez a dolog a VictoriaMetrics esetében 10-szer gyorsabban működik, mint a szabványos térkép.

Ráadásul sokkal kevesebb memóriát használ a térképes megvalósításhoz képest. Mert itt a nyolcbájtos értékek helyett biteket tárolunk.

Ennek a megvalósításnak az a hátránya, hogy nem annyira nyilvánvaló, nem triviális.

Egy másik hátrány, amelyet sokan észre sem vesznek, hogy ez a megvalósítás bizonyos esetekben nem működik megfelelően. Vagyis egy adott esetre van optimalizálva, a VictoriaMetrics idősor-azonosítók metszéspontjának erre az esetére. Ez nem jelenti azt, hogy minden esetre alkalmas. Ha nem megfelelően használjuk, akkor nem teljesítménynövekedést kapunk, hanem elfogyott a memória hibát és a teljesítmény lassulását.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Tekintsük ennek a szerkezetnek a megvalósítását. Ha meg akarod nézni, akkor a VictoriaMetrics forrásokban, a mappában található lib/uint64set. Kifejezetten a VictoriaMetrics tokhoz van optimalizálva, ahol timeseries_id egy 64 bites érték, ahol az első 32 bit alapvetően állandó, és csak az utolsó 32 bit változik.

Ez az adatstruktúra nem tárolódik lemezen, csak a memóriában működik.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Itt van az API. Nem túl bonyolult. Az API kifejezetten a VictoriaMetrics használatának egy konkrét példájára van szabva. Vagyis itt nincsenek felesleges funkciók. Itt vannak a VictoriaMetrics által kifejezetten használt funkciók.

Vannak funkciók add, amely új értékeket ad hozzá. Van egy funkció has, amely ellenőrzi az új értékeket. És van egy funkció del, amely eltávolítja az értékeket. Van egy segítő funkció len, amely visszaadja a készlet méretét. Funkció clone sokat klónoznak. És funkció appendto ezt a készletet szeletté alakítja timeseries_ids.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Így néz ki ennek az adatszerkezetnek a megvalósítása. A készlet két elemből áll:

  • ItemsCount egy segédmező, amellyel gyorsan visszaadhatja a halmaz elemeinek számát. Ez a segédmező nélkül is megoldható lenne, de ide kellett hozzáadni, mert a VictoriaMetrics gyakran lekérdezi a bitkészlet hosszát az algoritmusaiban.

  • A második mező az buckets. Ez egy szelet a szerkezetből bucket32. Minden szerkezet tárol hi terület. Ez a felső 32 bit. És két szelet - b16his и buckets A bucket16 szerkezetek.

A 16 bites struktúra második részének felső 64 bitje van itt tárolva. És itt bitkészletek vannak tárolva minden bájt alsó 16 bitjéhez.

Bucket64 tömbből áll uint64. A hossz kiszámítása ezekkel az állandókkal történik. Egyben bucket16 maximum tárolható 2^16=65536 bit. Ha ezt elosztod 8-cal, akkor ez 8 kilobájt. Ha ismét elosztod 8-cal, akkor 1000 uint64 jelentése. Azaz Bucket16 – ez a mi 8 kilobájtos szerkezetünk.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Nézzük meg, hogyan valósul meg ennek a szerkezetnek az egyik módszere egy új érték hozzáadására.

Minden azzal kezdődik uint64 jelentések. Kiszámoljuk a felső 32 bitet, az alsó 32 bitet. Menjünk át mindent buckets. Összehasonlítjuk az egyes gyűjtőkben található felső 32 bitet a hozzáadott értékkel. És ha egyeznek, akkor meghívjuk a függvényt add a b32 szerkezetben buckets. És add hozzá az alsó 32 bitet. És ha visszatérne true, akkor ez azt jelenti, hogy ilyen értéket adtunk oda, és nem volt ilyen értékünk. Ha visszajön false, akkor már létezett ilyen jelentés. Ezután növeljük a szerkezet elemeinek számát.

Ha nem találtuk meg a kívántat bucket a szükséges hi-értékkel, akkor meghívjuk a függvényt addAlloc, amely egy újat fog előállítani bucket, hozzáadva a vödör szerkezetéhez.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Ez a funkció megvalósítása b32.add. Hasonló az előző megvalósításhoz. A legjelentősebb 16 bitet számítjuk ki, a legkisebb jelentőségű 16 bitet.

Ezután végigmegyünk az összes felső 16 biten. Találunk egyezéseket. És ha van egyezés, hívjuk az add metódust, amit a következő oldalon fogunk figyelembe venni bucket16.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

És itt van a legalacsonyabb szint, amelyet lehetőleg optimalizálni kell. Számítunk rá uint64 azonosító érték szelet bitben és azt is bitmask. Ez egy maszk egy adott 64 bites értékhez, amellyel ellenőrizhető a bit megléte, illetve beállítható. Ellenőrizzük, hogy ez a bit be van-e állítva, és beállítjuk, és visszaadjuk a jelenlétet. Ez a mi implementációnk, amely lehetővé tette, hogy az idősorok metszőazonosítóinak működését 10-szeresére gyorsítsuk fel a hagyományos térképekhez képest.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Ezen az optimalizáláson kívül a VictoriaMetrics számos egyéb optimalizálással is rendelkezik. A legtöbb ilyen optimalizálás okkal került hozzáadásra, de a kód éles profilozása után.

Ez az optimalizálás fő szabálya – ne adjunk hozzá optimalizálást, feltételezve, hogy itt lesz szűk keresztmetszet, mert kiderülhet, hogy ott nem lesz szűk keresztmetszet. Az optimalizálás általában rontja a kód minőségét. Ezért csak profilozás után és lehetőleg gyártásban érdemes optimalizálni, hogy ez valós adat legyen. Ha valakit érdekel, megtekintheti a VictoriaMetrics forráskódját, és felfedezheti az ott található egyéb optimalizálásokat.

Kezdje az optimalizálást a VictoriaMetricsben. Alexander Valyalkin

Lenne egy kérdésem a bitsettel kapcsolatban. Nagyon hasonló a C++ vektoros bool implementációhoz, optimalizált bitkészlet. A megvalósítást onnan vetted?

Nem, nem onnan. Ennek a bitkészletnek a megvalósítása során ezen ids idősorok szerkezetének ismerete vezérelt, amelyeket a VictoriaMetrics használ. A felépítésük pedig olyan, hogy a felső 32 bit alapvetően állandó. Az alsó 32 bit változhat. Minél alacsonyabb a bit, annál gyakrabban változhat. Ezért ez a megvalósítás kifejezetten ehhez az adatstruktúrához van optimalizálva. A C++ implementáció, ha jól tudom, általános esetre van optimalizálva. Ha az általános esetre optimalizál, ez azt jelenti, hogy nem lesz a legoptimálisabb egy konkrét esetre.

Azt is tanácsolom, hogy nézze meg Alexey Milovid riportját. Körülbelül egy hónapja beszélt a ClickHouse-ban az optimalizálásról bizonyos szakterületekre. Csak annyit mond, hogy általános esetben egy C++ implementáció vagy más implementáció arra van szabva, hogy átlagosan jól működjön egy kórházban. Lehet, hogy rosszabbul teljesít, mint egy tudásspecifikus megvalósítás, mint a miénk, ahol tudjuk, hogy a felső 32 bit többnyire állandó.

Van egy második kérdésem. Mi az alapvető különbség az InfluxDB-től?

Sok alapvető különbség van. Ami a teljesítményt és a memóriafogyasztást illeti, az InfluxDB a tesztekben 10-szer több memóriafelhasználást mutat a nagy számosságú idősoroknál, amikor sok van belőlük, például milliók. Például a VictoriaMetrics 1 GB-ot fogyaszt millió aktív soronként, míg az InfluxDB 10 GB-ot. És ez nagy különbség.

A második alapvető különbség az, hogy az InfluxDB furcsa lekérdezési nyelvekkel rendelkezik - Flux és InfluxQL. Nem túl kényelmesek az idősorokkal való munkavégzéshez képest PromQL, amelyet a VictoriaMetrics támogat. A PromQL a Prometheus lekérdező nyelve.

És még egy különbség, hogy az InfluxDB egy kissé furcsa adatmodellel rendelkezik, ahol minden sor több mezőt tud tárolni különböző címkékkel. Ezek a sorok további táblázatokra vannak osztva. Ezek a további bonyodalmak megnehezítik a későbbi munkát ezzel az adatbázissal. Nehéz támogatni és megérteni.

A VictoriaMetrics-ben minden sokkal egyszerűbb. Ott minden idősor kulcsérték. Az érték pontok halmaza - (timestamp, value), és a kulcs a készlet label=value. Nincs különbség a mezők és a mérések között. Lehetővé teszi bármilyen adat kiválasztását, majd kombinálását, összeadását, kivonását, szorzását, osztását, ellentétben az InfluxDB-vel, ahol tudomásom szerint a különböző sorok közötti számítások még mindig nincsenek végrehajtva. Még ha implementálva is vannak, nehéz, sok kódot kell írni.

Egy tisztázó kérdésem lenne. Jól értettem, hogy valami probléma volt, amiről beszéltél, hogy ez az invertált index nem fér bele a memóriába, tehát ott particionálás van?

Először egy fordított index naiv megvalósítását mutattam be egy szabványos Go térképen. Ez a megvalósítás nem alkalmas adatbázisokhoz, mert ez az invertált index nem kerül lemezre, és az adatbázist lemezre kell menteni, hogy ezek az adatok újraindításkor is elérhetők maradjanak. Ebben a megvalósításban az alkalmazás újraindításakor a fordított index eltűnik. És elveszíti hozzáférését az összes adathoz, mert nem fogja tudni megtalálni.

Helló! Köszönöm a beszámolót! A nevem Pavel. Wildberryből származom. Lenne néhány kérdésem hozzád. Egy kérdés. Úgy gondolja, hogy ha az alkalmazás architektúrájának felépítése során más elvet választott volna, és idővel particionálta volna az adatokat, akkor talán képes lett volna kereséskor metszeni az adatokat, csak abból a tényből kiindulva, hogy egy partíció egyhez tartozó adatokat tartalmaz időintervallumban, azaz egy időintervallumban, és nem kell attól tartanod, hogy a darabjaid másképp szóródnak? 2. kérdés - mivel hasonló algoritmust valósít meg bitkészlettel és minden mással, akkor talán megpróbálta a processzor utasításait használni? Esetleg próbáltál már ilyen optimalizálást?

A másodikra ​​azonnal válaszolok. Még nem jutottunk el odáig. De ha kell, odaérünk. És az első, mi volt a kérdés?

Két forgatókönyvet beszéltél meg. És azt mondták, hogy a másodikat választották, bonyolultabb megvalósítással. És nem az elsőt részesítették előnyben, ahol az adatok idő szerint vannak felosztva.

Igen. Az első esetben az index teljes mennyisége nagyobb lenne, mert minden partícióban duplikált adatokat kellene tárolnunk azon idősorokhoz, amelyek ezeken a partíciókon keresztül folytatódnak. Ha pedig kicsi az idősorok lemorzsolódási aránya, vagyis folyamatosan ugyanazokat a sorozatokat használjuk, akkor az első esetben sokkal többet veszítenénk a lemezterület elfoglalásából a második esethez képest.

És igen, az idő felosztása jó lehetőség. Prométheusz használja. De Prometheusnak van egy másik hátránya is. Amikor ezeket az adatokat egyesíti, meg kell őriznie a memóriában az összes címke és idősor metainformációit. Ezért, ha az egyesített adatrészek nagyok, akkor a memóriafogyasztás nagyon megnő az egyesítés során, ellentétben a VictoriaMetrics-szel. Egyesítéskor a VictoriaMetrics egyáltalán nem fogyaszt memóriát, mindössze néhány kilobájtot fogyaszt, függetlenül az egyesített adatrészek méretétől.

A használt algoritmus memóriát használ. Megjelöli az értékeket tartalmazó idősor-címkéket. És így ellenőrizheti a páros jelenlétet az egyik adattömbben és a másikban. És megérti, hogy megtörtént-e a metszéspont vagy sem. Az adatbázisok jellemzően olyan kurzorokat és iterátorokat valósítanak meg, amelyek tárolják az aktuális tartalmat, és átfutják a rendezett adatokat, a műveletek egyszerű összetettsége miatt.

Miért nem használunk kurzorokat az adatok bejárására?

Igen.

A rendezett sorokat LevelDB-ben vagy mergeset-ben tároljuk. Mozgathatjuk a kurzort és megkereshetjük a kereszteződést. Miért nem használjuk? Mert lassú. Mert a kurzorok azt jelentik, hogy minden sorhoz meg kell hívni egy függvényt. Egy függvényhívás 5 nanoszekundum. És ha 100 000 000 sora van, akkor kiderül, hogy fél másodpercet töltünk csak a függvény meghívásával.

Van ilyen, igen. És az utolsó kérdésem. A kérdés kissé furcsán hangozhat. Miért nem lehet az adatok beérkezésekor az összes szükséges aggregátumot kiolvasni és a kívánt formában elmenteni? Miért spórolna hatalmas mennyiségeket egyes rendszerekben, például a VictoriaMetricsben, a ClickHouse-ban stb., és miért töltene velük sok időt?

Mondok egy példát, hogy világosabb legyen. Mondjuk, hogyan működik egy kis játék sebességmérő? Rögzíti a megtett távolságot, folyamatosan hozzáadva egy értékhez, másodszor pedig az időt. És oszt. És átlagos sebességet kap. Körülbelül ugyanezt teheted. Adja össze az összes szükséges tényt menet közben.

Oké, értem a kérdést. A példádnak megvan a maga helye. Ha tudja, milyen aggregátumokra van szüksége, akkor ez a legjobb megvalósítás. De a probléma az, hogy az emberek elmentik ezeket a mutatókat, néhány adatot a ClickHouse-ba, és még nem tudják, hogyan fogják összesíteni és szűrni őket a jövőben, ezért el kell menteniük az összes nyers adatot. De ha tudja, hogy valamit átlagban kell kiszámítania, akkor miért ne számolja ki ahelyett, hogy egy csomó nyers értéket tárolna ott? De ez csak akkor lehetséges, ha pontosan tudja, mire van szüksége.

Az idősorok tárolására szolgáló adatbázisok egyébként támogatják az aggregátumok számlálását. Például a Prometheus támogatja rögzítési szabályok. Vagyis ezt meg lehet tenni, ha tudja, milyen egységekre lesz szüksége. A VictoriaMetrics még nem rendelkezik ilyennel, de általában megelőzi a Prometheus, amiben ez az átkódolási szabályokban megtehető.

Például az előző munkámban meg kellett számolnom az események számát egy csúszó ablakban az elmúlt órában. A probléma az, hogy a Go-ban egy egyedi implementációt kellett készítenem, vagyis egy szolgáltatást ennek a dolognak a számlálására. Ez a szolgáltatás végül nem volt triviális, mert nehéz kiszámítani. A megvalósítás egyszerű lehet, ha meghatározott időközönként meg kell számolnia néhány aggregátumot. Ha az eseményeket egy csúszó ablakban szeretné számolni, akkor ez nem olyan egyszerű, mint amilyennek látszik. Szerintem ez még nem valósult meg sem ClickHouse-ban, sem idősoros adatbázisokban, mert nehezen kivitelezhető.

És még egy kérdés. Épp az átlagolásról beszéltünk, és eszembe jutott, hogy volt egyszer olyan, hogy Graphite Carbon háttérrel. És tudta, hogyan kell ritkítani a régi adatokat, vagyis hagyni egy pontot percenként, egy pontot óránként stb. Elvileg ez nagyon kényelmes, ha nyers adatokra van szükségünk, relatíve egy hónapra, és minden mást megtehet. elvékonyodni . De a Prometheus és a VictoriaMetrics nem támogatja ezt a funkciót. Tervezik-e ennek támogatását? Ha nem, miért nem?

Köszönöm a kérdést. Felhasználóink ​​rendszeresen felteszik ezt a kérdést. Azt kérdezik, hogy mikor adjuk hozzá a mintavételezés támogatását. Itt több probléma is felmerül. Először is, minden felhasználó megérti downsampling valami más: valaki tetszőleges pontot szeretne kapni egy adott intervallumon, valaki maximum, minimum, átlagos értékeket. Ha sok rendszer ír adatokat az adatbázisodba, akkor nem tudod összevonni az egészet. Előfordulhat, hogy minden rendszer más-más hígítást igényel. És ezt nehéz megvalósítani.

A második dolog pedig az, hogy a VictoriaMetrics, akárcsak a ClickHouse, nagy mennyiségű nyers adat feldolgozására van optimalizálva, így kevesebb mint egy másodperc alatt képes egymilliárd sort lapátolni, ha sok mag van a rendszerben. Idősor-pontok pásztázása a VictoriaMetrics-ben – 50 000 000 pont másodpercenként magonként. És ez a teljesítmény a meglévő magokhoz skálázódik. Vagyis ha például 20 magja van, akkor másodpercenként egy milliárd pontot fog pásztázni. A VictoriaMetrics és a ClickHouse ezen tulajdonsága pedig csökkenti a mintavételezés szükségességét.

Egy másik jellemző, hogy a VictoriaMetrics hatékonyan tömöríti ezeket az adatokat. A tömörítés átlagosan 0,4-0,8 bájt/pont. Minden pont egy időbélyeg + érték. És átlagosan egy bájtnál kevesebbre van tömörítve.

Szergej. Kérdésem van. Mi a minimális rögzítési idő kvantum?

Egy milliszekundum. Nemrég beszélgettünk más idősoros adatbázis-fejlesztőkkel. Minimális időszeletük egy másodperc. És például a Grafitban is egy másodperc. OpenTSDB-ben ez is egy másodperc. Az InfluxDB nanoszekundumos pontosságú. A VictoriaMetricsben egy ezredmásodperc, a Prometheusban viszont egy ezredmásodperc. A VictoriaMetrics-et pedig eredetileg a Prometheus távoli tárolójaként fejlesztették ki. De most már képes menteni adatokat más rendszerekről.

Az a személy, akivel beszéltem, azt mondja, hogy másodpercről másodpercre pontosak – ez elég nekik, mert ez az idősor-adatbázisban tárolt adatok típusától függ. Ha ez DevOps adat vagy infrastruktúra adata, ahol 30 másodpercenként, percenként gyűjtöd, akkor elég a másodperces pontosság, nem kell kevesebb. És ha ezeket az adatokat nagyfrekvenciás kereskedési rendszerekből gyűjti, akkor nanoszekundumos pontosságra van szüksége.

A VictoriaMetrics ezredmásodperces pontossága a DevOps esethez is megfelelő, és alkalmas lehet a legtöbb olyan esetre, amelyeket a jelentés elején említettem. Az egyetlen dolog, amelyre nem alkalmas, az a magas frekvenciájú kereskedési rendszerek.

Köszönöm! És még egy kérdés. Mi a kompatibilitás a PromQL-ben?

Teljes visszafelé kompatibilitás. A VictoriaMetrics teljes mértékben támogatja a PromQL-t. Ezenkívül további speciális funkciókat ad a PromQL-hez, amely az ún MetricsQL. A YouTube-on szó esik erről a kiterjesztett funkcióról. A Monitoring Meetupon beszéltem tavasszal Szentpéterváron.

Távirati csatorna VictoriaMetrics.

A felmérésben csak regisztrált felhasználók vehetnek részt. Bejelentkezés, kérem.

Mi akadályoz meg abban, hogy a VictoriaMetrics-re váltson a Prometheus hosszú távú tárolójaként? (Írja meg kommentben, felveszem a szavazáshoz))

  • 71,4%Nem használok Prometheus5-öt

  • 28,6%Nem tudtam a VictoriaMetrics2-ről

7 felhasználó szavazott. 12 felhasználó tartózkodott.

Forrás: will.com

Hozzászólás