{"id":80733,"date":"2020-05-08T13:42:31","date_gmt":"2020-05-08T11:42:31","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin"},"modified":"2020-05-08T13:42:31","modified_gmt":"2020-05-08T11:42:31","slug":"go-optimizations-in-victoriametrics-aleksandr-valyalkin","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","title":{"rendered":"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Zach\u0119cam do zapoznania si\u0119 z transkrypcj\u0105 sprawozdania z ko\u0144ca 2019 roku autorstwa Aleksandra Walia\u0142kina \"Optymalizacje Go w VictoriaMetrics\"<\/strong><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/victoriametrics.com\/\">VictoriaMetrics<\/a><\/noindex> \u2014 szybka i skalowalna baza danych do przechowywania i przetwarzania danych w postaci szeregu czasowego (zapis tworzy czas oraz zbi\u00f3r odpowiadaj\u0105cych temu czasowi warto\u015bci, na przyk\u0142ad uzyskanych za pomoc\u0105 okresowego odczytu stanu czujnik\u00f3w lub zbierania metryk).<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/dd13ec10594aeb138be29ce439cc476a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p>Oto link do wideo z tego wyst\u0105pienia \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/MZ5P21j_HLE\">https:\/\/youtu.be\/MZ5P21j_HLE<\/a><\/noindex><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.google.com\/presentation\/d\/1k7OjHvxTHA7669MFwsNTCx8hII-a8lNvpmQetLxmrEU\/edit?usp=sharing\">Slajdy<\/a><\/noindex><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/0073390d6dbcc6ab3e5305907ac6a229.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Zaraz opowiem co\u015b o sobie. Nazywam si\u0119 Aleksander Wali\u0142kin. Oto <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/valyala\">moje konto GitHub<\/a><\/noindex>. Zafascynowany jestem Go i optymalizacj\u0105 wydajno\u015bci. Napisa\u0142em wiele r\u00f3\u017cnych przydatnych i mniej przydatnych bibliotek. Zaczynaj\u0105 si\u0119 one od <code>fast<\/code>, albo od <code>quick<\/code> prefiksu. <\/p>\n<p><\/p>\n<p>Obecnie pracuj\u0119 nad VictoriaMetrics. Czym jest i co tam robi\u0119? O tym opowiem w tej prezentacji. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/c9194bc3fee1f615d838979bec12f7d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Plan wyst\u0105pienia jest nast\u0119puj\u0105cy:<\/p>\n<p><\/p>\n<ul>\n<li>Na pocz\u0105tku opowiem, czym jest VictoriaMetrics. <\/li>\n<li>Nast\u0119pnie om\u00f3wi\u0119, czym s\u0105 szeregi czasowe. <\/li>\n<li>Potem wyja\u015bni\u0119, jak dzia\u0142a baza danych dla szereg\u00f3w czasowych.<\/li>\n<li>Nast\u0119pnie opowiem o architekturze bazy danych: z czego si\u0119 sk\u0142ada.<\/li>\n<li>A potem przejdziemy do optymalizacji, kt\u00f3re s\u0105 w VictoriaMetrics. To optymalizacja indeksu odwr\u00f3conego oraz optymalizacja dla implementacji bitset w Go.<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/3e0244b6b6fe952f660e4a094778921a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Czy kto\u015b w audytorium wie, czym jest VictoriaMetrics? Nic sobie, wiele os\u00f3b ju\u017c wie. To dobra wiadomo\u015b\u0107. Dla tych, kt\u00f3rzy nie wiedz\u0105 \u2013 to baza danych dla szereg\u00f3w czasowych. Jest oparta na architekturze ClickHouse oraz na niekt\u00f3rych detalach implementacji ClickHouse. Na przyk\u0142ad takich jak: MergeTree, r\u00f3wnoleg\u0142e obliczenia na wszystkich dost\u0119pnych rdzeniach procesora oraz optymalizacja wydajno\u015bci dzi\u0119ki pracy z blokami danych, kt\u00f3re s\u0105 umieszczane w pami\u0119ci podr\u0119cznej procesora. <\/p>\n<p><\/p>\n<p>VictoriaMetrics zapewnia lepsze kompresj\u0119 danych w por\u00f3wnaniu do innych baz danych dla szereg\u00f3w czasowych. <\/p>\n<p><\/p>\n<p>Skaluje si\u0119 pionowo \u2014 tzn. mo\u017cesz dodawa\u0107 wi\u0119cej procesor\u00f3w, wi\u0119cej pami\u0119ci RAM na jednym komputerze. VictoriaMetrics efektywnie wykorzysta te dost\u0119pne zasoby i zwi\u0119kszy liniow\u0105 wydajno\u015b\u0107.<\/p>\n<p><\/p>\n<p>VictoriaMetrics skaluje si\u0119 r\u00f3wnie\u017c poziomo \u2014 tzn. mo\u017cesz dodawa\u0107 dodatkowe w\u0119z\u0142y do klastra VictoriaMetrics, a jej wydajno\u015b\u0107 wzro\u015bnie prawie liniowo.<\/p>\n<p><\/p>\n<p>Jak si\u0119 domy\u015blili\u015bcie, VictoriaMetrics to szybka baza danych, poniewa\u017c nie mog\u0119 pisa\u0107 o innych. I jest napisana w Go, dlatego opowiadam o niej na tym meet-upie.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/0121c9baead722eb1fe22fb6f900d4ad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Kto wie, co to jest szereg czasowy? Wiele os\u00f3b to wie. Szereg czasowy to seria par <code>(timestamp, warto\u015b\u0107)<\/code>, gdzie te pary s\u0105 uporz\u0105dkowane wed\u0142ug czasu. Warto\u015b\u0107 to liczba zmiennoprzecinkowa \u2013 float64.<\/p>\n<p><\/p>\n<p>Ka\u017cdy szereg czasowy jest unikalnie identyfikowany kluczem. Z czego sk\u0142ada si\u0119 ten klucz? Sk\u0142ada si\u0119 z niepustego zbioru par klucz-warto\u015b\u0107. <\/p>\n<p><\/p>\n<p>Oto przyk\u0142ad szeregu czasowego. Kluczem tego szeregu jest lista par: <code>__name__=\"cpu_usage\"<\/code> \u2013 to nazwa metryki, <code>instance=\"my-server\"<\/code> \u2013 to komputer, na kt\u00f3rym ta metryka zosta\u0142a zebrana, <code>datacenter=\"us-east\"<\/code> \u2013 to centrum danych, w kt\u00f3rym znajduje si\u0119 ten komputer.<\/p>\n<p><\/p>\n<p>Uzyskali\u015bmy nazw\u0119 szeregu czasowego, sk\u0142adaj\u0105c\u0105 si\u0119 z trzech par klucz-warto\u015b\u0107. Do tego klucza odpowiada lista par <code>(timestamp, value)<\/code>. <code>t1, t2, t3, ..., tN<\/code> \u2013 to znaczniki czasu, <code>10, 20, 12, ..., 15<\/code> \u2013 odpowiadaj\u0105ce warto\u015bci. To cpu-usage w danym momencie czasu dla tego szeregu.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/76cab9db0263318d355ac607ceb7e0f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Gdzie mo\u017cna wykorzysta\u0107 szeregi czasowe? Czy kto\u015b ma jakie\u015b pomys\u0142y? <\/p>\n<p><\/p>\n<ul>\n<li>W DevOps mo\u017cna mierzy\u0107 obci\u0105\u017cenie CPU, RAM, sieci, rps, liczb\u0119 b\u0142\u0119d\u00f3w itp. <\/li>\n<li>IoT \u2013 mo\u017cemy mierzy\u0107 temperatur\u0119, ci\u015bnienie, wsp\u00f3\u0142rz\u0119dne geograficzne i inne rzeczy.<\/li>\n<li>R\u00f3wnie\u017c w finansach \u2013 mo\u017cemy monitorowa\u0107 ceny r\u00f3\u017cnych akcji i walut. <\/li>\n<li>Ponadto, szeregi czasowe mog\u0105 by\u0107 wykorzystywane do monitorowania proces\u00f3w produkcyjnych w fabrykach. Mamy u\u017cytkownik\u00f3w, kt\u00f3rzy wykorzystuj\u0105 VictoriaMetrics do monitorowania turbin wiatrowych, dla robot\u00f3w.<\/li>\n<li>Szeregi czasowe s\u0105 r\u00f3wnie\u017c przydatne do zbierania informacji z czujnik\u00f3w r\u00f3\u017cnych urz\u0105dze\u0144. Na przyk\u0142ad, dla silnika; do mierzenia ci\u015bnienia w oponach; do mierzenia pr\u0119dko\u015bci, dystansu; do mierzenia zu\u017cycia paliwa itp.<\/li>\n<li>Szeregi czasowe mog\u0105 by\u0107 tak\u017ce wykorzystywane do monitorowania samolot\u00f3w. W ka\u017cdym samolocie znajduje si\u0119 czarna skrzynka, kt\u00f3ra zbiera szeregi czasowe na temat r\u00f3\u017cnych parametr\u00f3w stanu samolotu. Szeregi czasowe s\u0105 r\u00f3wnie\u017c u\u017cywane w przemy\u015ble kosmicznym. <\/li>\n<li>Ochrona zdrowia \u2013 to ci\u015bnienie krwi, puls itp.<\/li>\n<\/ul>\n<p><\/p>\n<p>Mo\u017ce s\u0105 jeszcze inne zastosowania, o kt\u00f3rych zapomnia\u0142em, ale mam nadziej\u0119, \u017ce zrozumieli\u015bcie, \u017ce szeregi czasowe s\u0105 aktywnie wykorzystywane w nowoczesnym \u015bwiecie. A ich zastosowanie ro\u015bnie z ka\u017cdym rokiem.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/f1da29d372163751268b6a8f86836d04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Do czego potrzebna jest baza danych dla szereg\u00f3w czasowych? Dlaczego nie mo\u017cna u\u017cywa\u0107 zwyk\u0142ej bazy relacyjnej do przechowywania szereg\u00f3w czasowych?<\/p>\n<p><\/p>\n<p>Poniewa\u017c w szeregach czasowych zazwyczaj znajduje si\u0119 du\u017ca ilo\u015b\u0107 informacji, kt\u00f3r\u0105 trudno przechowywa\u0107 i przetwarza\u0107 w zwyk\u0142ych bazach danych. Dlatego powsta\u0142y specjalistyczne bazy danych do szereg\u00f3w czasowych. Bazy te skutecznie przechowuj\u0105 punkty <code>(timestamp, value)<\/code> z okre\u015blonym kluczem. Oferuj\u0105 interfejs API do odczytu zapisanych danych po kluczu, pojedynczej parze klucz-warto\u015b\u0107, grupie par lub za pomoc\u0105 regexp. Na przyk\u0142ad, je\u015bli chcesz znale\u017a\u0107 obci\u0105\u017cenie CPU wszystkich swoich us\u0142ug w centrum danych w Ameryce, musisz u\u017cy\u0107 takiego pseudo zapytania.<\/p>\n<p><\/p>\n<p>Zazwyczaj bazy danych do szereg\u00f3w czasowych oferuj\u0105 specjalistyczne j\u0119zyki zapyta\u0144, poniewa\u017c SQL nie nadaje si\u0119 dobrze do tych zastosowa\u0144. Cho\u0107 s\u0105 bazy danych, kt\u00f3re wspieraj\u0105 SQL, nie jest to rozwi\u0105zanie optymalne. Lepiej nadaj\u0105 si\u0119 takie j\u0119zyki zapyta\u0144 jak <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.influxdata.com\/influxdb\/v1.8\/query_language\/spec\/\">InfluxQL<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.influxdata.com\/products\/flux\/\">Flux<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/code.kx.com\/q\/\">Q<\/a><\/noindex>. Mam nadziej\u0119, \u017ce kto\u015b s\u0142ysza\u0142 przynajmniej o jednym z tych j\u0119zyk\u00f3w. O PromQL, prawdopodobnie, s\u0142ysza\u0142o wielu. To jest j\u0119zyk zapyta\u0144 Prometheusa.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/59e5b6d82a2f7861077bc5fe34519ee4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Oto jak wygl\u0105da architektura nowoczesnej bazy danych do szereg\u00f3w czasowych na przyk\u0142adzie VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Sk\u0142ada si\u0119 z dw\u00f3ch cz\u0119\u015bci. To magazyn dla indeksu odwr\u00f3conego i magazyn dla warto\u015bci szereg\u00f3w czasowych. Te magazyny s\u0105 oddzielone. <\/p>\n<p><\/p>\n<p>Kiedy przychodzi nowy zapis do bazy danych, najpierw zwracamy si\u0119 do indeksu odwr\u00f3conego, aby znale\u017a\u0107 identyfikator szereg\u00f3w czasowych wed\u0142ug danego zestawu <code>label=value<\/code> dla danej metryki. Znajdujemy ten identyfikator i zapisujemy warto\u015b\u0107 w magazynie danych.<\/p>\n<p><\/p>\n<p>Kiedy przychodzi jakie\u015b zapytanie o wyci\u0105gni\u0119cie danych z TSDB, w pierwszej kolejno\u015bci zagl\u0105damy do indeksu odwr\u00f3conego. Wyci\u0105gamy wszystkie <code>timeseries_ids<\/code> zapisy, kt\u00f3re odpowiadaj\u0105 danemu zestawowi. <code>label=value<\/code>A nast\u0119pnie wydobywamy wszystkie niezb\u0119dne dane z magazynu danych, indeksowanych wed\u0142ug <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/eb9b36fa3c6de2188b97b56ad3839b2c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Zobaczmy przyk\u0142ad, jak baza danych do szereg\u00f3w czasowych przetwarza przychodz\u0105ce zapytanie select.<\/p>\n<p><\/p>\n<ul>\n<li>W pierwszej kolejno\u015bci wyci\u0105ga wszystkie <code>timeseries_ids<\/code> z indeksu odwr\u00f3conego, kt\u00f3re zawieraj\u0105 podane pary <code>label=value<\/code>, lub spe\u0142niaj\u0105 okre\u015blony wz\u00f3r regularny.<\/li>\n<li>Nast\u0119pnie wyci\u0105ga wszystkie punkty danych z magazynu danych w okre\u015blonym przedziale czasowym dla znalezionych <code>timeseries_ids<\/code>.<\/li>\n<li>Po tym baza danych przeprowadza jakie\u015b obliczenia nad tymi punktami danych, zgodnie z zapytaniem u\u017cytkownika. A potem zwraca odpowied\u017a.<\/li>\n<\/ul>\n<p><\/p>\n<p>W tej prezentacji opowiem o pierwszej cz\u0119\u015bci. To jest wyszukiwanie <code>timeseries_ids<\/code> na temat indeksu inwersyjnego. O drugiej cz\u0119\u015bci i trzeciej cz\u0119\u015bci mo\u017cesz p\u00f3\u017aniej spojrze\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">\u017ar\u00f3d\u0142a VictoriaMetrics<\/a><\/noindex>, albo poczeka\u0107, a\u017c przygotuj\u0119 inne prezentacje \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/3a7dab707dd1defb9bc674a342052a03.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Zacznijmy od indeksu inwersyjnego. Wielu mo\u017ce wydawa\u0107 si\u0119, \u017ce to proste. Kto wie, co to jest indeks inwersyjny i jak dzia\u0142a? O, ju\u017c nie tak wiele os\u00f3b. Spr\u00f3bujmy zrozumie\u0107, czym to jest. <\/p>\n<p><\/p>\n<p>W rzeczywisto\u015bci wszystko jest proste. To po prostu s\u0142ownik, kt\u00f3ry mapuje klucz na warto\u015b\u0107. Co to jest klucz? Ta para <code>label=value<\/code>, gdzie <code>label<\/code> i <code>value<\/code> to ci\u0105gi. A warto\u015bci to zestaw <code>timeseries_ids<\/code>, kt\u00f3ry zawiera dan\u0105 par\u0119 <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Indeks inwersyjny pozwala szybko znajdowa\u0107 wszystkie <code>timeseries_ids<\/code>, kt\u00f3re maj\u0105 dan\u0105 <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>A tak\u017ce pozwala szybko znajdowa\u0107 <code>timeseries_ids<\/code> szereg\u00f3w czasowych dla kilku par <code>label=value<\/code>, albo dla par <code>label=regexp<\/code>. Jak to dzia\u0142a? Poprzez znalezienie przeci\u0119cia zbioru <code>timeseries_ids<\/code> dla ka\u017cdej pary <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/837a15a078e8c9422c721f3f072c0c09.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Rozwa\u017cmy r\u00f3\u017cne implementacje indeksu inwersyjnego. Zacznijmy od najprostszej naiwnej implementacji. Wygl\u0105da to tak. <\/p>\n<p><\/p>\n<p>veth_xdp_flush_bq() <code>getMetricIDs<\/code> uzyskuje list\u0119 ci\u0105g\u00f3w. Ka\u017cdy ci\u0105g zawiera <code>label=value<\/code>. Ta funkcja zwraca list\u0119 <code>metricIDs<\/code>.<\/p>\n<p><\/p>\n<p>Jak to dzia\u0142a? Mamy globaln\u0105 zmienn\u0105, kt\u00f3ra nazywa si\u0119 <code>invertedIndex<\/code>. To zwyk\u0142y s\u0142ownik (<code>map<\/code>), kt\u00f3ry mapuje ci\u0105g na slice int-\u00f3w. Ci\u0105g zawiera <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Implementacja funkcji: pobieramy <code>metricIDs<\/code> dla pierwszego <code>label=value<\/code>, nast\u0119pnie przechodzimy przez wszystkie pozosta\u0142e <code>label=value<\/code>, pobieramy <code>metricIDs<\/code> dla nich. I wywo\u0142ujemy funkcj\u0119 <code>intersectInts<\/code>, o kt\u00f3rej b\u0119dzie mowa p\u00f3\u017aniej. Ta funkcja zwraca przeci\u0119cie tych list.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/709beca79884a9693098781974c26f4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Jak wida\u0107, implementacja indeksu inwersyjnego nie jest bardzo skomplikowana. Ale to naiwna implementacja. Jakie ma wady? G\u0142\u00f3wn\u0105 wad\u0105 naiwnej implementacji jest to, \u017ce taki indeks inwersyjny przechowujemy w pami\u0119ci RAM. Po ponownym uruchomieniu aplikacji tracimy ten indeks. Nie ma zapisu tego indeksu na dysku. Dla bazy danych taki indeks inwersyjny raczej si\u0119 nie nada.<\/p>\n<p><\/p>\n<p>Drug\u0105 wad\u0105 jest r\u00f3wnie\u017c zwi\u0105zana z pami\u0119ci\u0105. Indeks inwersyjny musi mie\u015bci\u0107 si\u0119 w pami\u0119ci RAM. Je\u015bli przekroczy rozmiar pami\u0119ci RAM, to oczywi\u015bcie otrzymamy \u2013 b\u0142\u0105d pami\u0119ci. I program nie b\u0119dzie dzia\u0142a\u0142.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/495394168ac03aa8cbcf56ccad1cb2b8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ten problem mo\u017cna rozwi\u0105za\u0107 przy pomocy gotowych rozwi\u0105za\u0144 takich jak <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/google\/leveldb\">LevelDB<\/a><\/noindex>, albo <noindex><a rel=\"nofollow\" href=\"https:\/\/rocksdb.org\/\">RocksDB<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Je\u015bli pokr\u00f3tce, potrzebujemy bazy danych, kt\u00f3ra pozwala na szybkie wykonanie trzech operacji. <\/p>\n<p><\/p>\n<ul>\n<li>Pierwsza operacja to zapis <code>klucz-warto\u015b\u0107<\/code> do tej bazy. Robi to bardzo szybko, gdzie <code>klucz-warto\u015b\u0107<\/code> s\u0105 to dowolne ci\u0105gi. <\/li>\n<li>Druga operacja to szybkie wyszukiwanie warto\u015bci na podstawie zadanego klucza.<\/li>\n<li>A trzecia operacja to szybkie wyszukiwanie wszystkich warto\u015bci na podstawie zadanego prefiksu. <\/li>\n<\/ul>\n<p><\/p>\n<p>LevelDB i RocksDB \u2013 te bazy zosta\u0142y opracowane w Google i Facebooku. Najpierw powsta\u0142a LevelDB. Potem ludzie z Facebooka wzi\u0119li LevelDB i zacz\u0119li j\u0105 ulepsza\u0107, tworz\u0105c RocksDB. Obecnie na RocksDB w Facebooku pracuj\u0105 niemal wszystkie wewn\u0119trzne bazy danych, w tym przekszta\u0142cono tak\u017ce MySQL na RocksDB. Nazywaj\u0105 to <noindex><a rel=\"nofollow\" href=\"http:\/\/myrocks.io\/\">MyRocks<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Indeks odwr\u00f3cony mo\u017cna zrealizowa\u0107 za pomoc\u0105 LevelDB. Jak to zrobi\u0107? Zapisujemy jako klucz <code>label=value<\/code>. A jako warto\u015b\u0107 \u2013 identyfikator szereg\u00f3w czasowych, w kt\u00f3rych wyst\u0119puje para <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Je\u015bli mamy wiele szereg\u00f3w czasowych z dan\u0105 par\u0105 <code>label=value<\/code>, to b\u0119dzie wiele wierszy w tej bazie danych z takim samym kluczem i r\u00f3\u017cnymi <code>timeseries_ids<\/code>. Aby uzyska\u0107 list\u0119 wszystkich <code>timeseries_ids<\/code>, kt\u00f3re zaczynaj\u0105 si\u0119 od danego <code>label=prefix<\/code>, wykonujemy skanowanie zakresu, dla kt\u00f3rego ta baza danych jest zoptymalizowana. Tzn. wybieramy wszystkie wiersze, kt\u00f3re zaczynaj\u0105 si\u0119 od <code>label=prefix<\/code> i otrzymujemy potrzebne <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/a40877ecb758ac3bc1979bcd568d7e83.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Oto przyk\u0142adowa implementacja, jak mog\u0142aby wygl\u0105da\u0107 w Go. Mamy indeks odwr\u00f3cony. To jest LevelDB.<\/p>\n<p><\/p>\n<p>Funkcja jest taka sama jak w naiwnej implementacji. Prawie linijka w linijk\u0119 powtarza naiwn\u0105 implementacj\u0119. Jedyny moment, \u017ce zamiast odwo\u0142ania do <code>map<\/code> odwo\u0142ujemy si\u0119 do indeksu odwr\u00f3conego. Wyci\u0105gamy wszystkie warto\u015bci dla pierwszej <code>label=value<\/code>. Potem przechodzimy przez wszystkie pozosta\u0142e pary <code>label=value<\/code> i wyci\u0105gamy odpowiednie zestawy metricIDs dla nich. Nast\u0119pnie znajdujemy przeci\u0119cia. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/de11b15286816ada04b22727ff78e43d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wydaje si\u0119, \u017ce wszystko jest w porz\u0105dku, ale w tym rozwi\u0105zaniu s\u0105 wady. VictoriaMetrics na pocz\u0105tku zaimplementowa\u0142a indeks odwr\u00f3cony na podstawie LevelDB. Ale ostatecznie musia\u0142a z niego zrezygnowa\u0107.<\/p>\n<p><\/p>\n<p>Dlaczego? Poniewa\u017c LevelDB jest wolniejsza ni\u017c naiwna implementacja. W naiwnym podej\u015bciu na podstawie zadanego klucza od razu wyci\u0105gamy ca\u0142y slice <code>metricIDs<\/code>. To bardzo szybka operacja \u2014 ca\u0142y slice jest gotowy do u\u017cycia.<\/p>\n<p><\/p>\n<p>W LevelDB natomiast przy ka\u017cdym wywo\u0142aniu funkcji <code>GetValues<\/code> trzeba przej\u015b\u0107 przez wszystkie wiersze, kt\u00f3re zaczynaj\u0105 si\u0119 od <code>label=value<\/code>. I dla ka\u017cdego wiersza wyci\u0105gn\u0105\u0107 warto\u015b\u0107 <code>timeseries_ids<\/code>. Z takich <code>timeseries_ids<\/code> zebra\u0107 slice tych <code>timeseries_ids<\/code>. Oczywi\u015bcie jest to znacznie wolniejsze ni\u017c po prostu odwo\u0142anie si\u0119 do zwyk\u0142ej mapy po kluczu.<\/p>\n<p><\/p>\n<p>Drug\u0105 wad\u0105 jest to, \u017ce LevelDB jest napisany w C. Wywo\u0142anie funkcji C z Go nie jest zbyt szybkie. Zajmuje to setki nanosekund. To nie jest bardzo szybkie, poniewa\u017c w por\u00f3wnaniu z typowym wywo\u0142aniem funkcji napisanej w Go, kt\u00f3re zajmuje 1-5 nanosekund, r\u00f3\u017cnica w wydajno\u015bci wynosi dziesi\u0105tki razy. Dla VictoriaMetrics by\u0142a to tragiczna wada \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/47cace825807b7b062064c2e790dc9d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Dlatego napisa\u0142em w\u0142asn\u0105 implementacj\u0119 indeksu odwr\u00f3conego. Nazwa\u0142em j\u0105 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">mergeset<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Mergeset oparty jest na strukturze danych MergeTree. Ta struktura danych zosta\u0142a zapo\u017cyczona z ClickHouse. Oczywi\u015bcie, \u017ce mergeset musi by\u0107 optymalizowany do szybkiego wyszukiwania <code>timeseries_ids<\/code> na podstawie okre\u015blonego klucza. Mergeset jest napisany w ca\u0142o\u015bci w Go. Mo\u017cesz zobaczy\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">\u017ar\u00f3d\u0142a VictoriaMetrics na GitHubie<\/a><\/noindex>. Implementacja mergeset znajduje si\u0119 w folderze <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">\/lib\/mergeset<\/a><\/noindex>. Mo\u017cesz spr\u00f3bowa\u0107 zrozumie\u0107, co tam si\u0119 dzieje.<\/p>\n<p><\/p>\n<p>API mergeset jest bardzo podobne do LevelDB i RocksDB. T. j. pozwala szybko zapisa\u0107 nowe rekordy i szybko wybiera\u0107 rekordy na podstawie okre\u015blonego prefiksu.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/845704ed36f70beb468907d22701af59.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>O wadach mergeset porozmawiamy p\u00f3\u017aniej. Teraz om\u00f3wimy problemy, jakie wyst\u0105pi\u0142y z VictoriaMetrics w produkcji przy realizacji indeksu odwr\u00f3conego.<\/p>\n<p><\/p>\n<p>Dlaczego si\u0119 pojawi\u0142y?<\/p>\n<p><\/p>\n<p>Pierwsz\u0105 przyczyn\u0105 jest wysoka rotacja. W t\u0142umaczeniu na polski \u2013 to cz\u0119sta zmiana szereg\u00f3w czasowych. To wtedy szereg czasowy ko\u0144czy si\u0119, a zaczyna nowy, lub zaczyna si\u0119 wiele nowych szereg\u00f3w czasowych. I dzieje si\u0119 to cz\u0119sto.<\/p>\n<p><\/p>\n<p>Drug\u0105 przyczyn\u0105 jest du\u017ca liczba szereg\u00f3w czasowych. Na pocz\u0105tku, gdy monitorowanie zyskiwa\u0142o na popularno\u015bci, liczba szereg\u00f3w czasowych by\u0142a ma\u0142a. Na przyk\u0142ad, na ka\u017cdy komputer trzeba monitorowa\u0107 obci\u0105\u017cenie procesora, pami\u0119ci, sieci i dysku. 4 szeregi czasowe na ka\u017cdy komputer. Powiedzmy, \u017ce masz 100 komputer\u00f3w i 400 szereg\u00f3w czasowych. To bardzo ma\u0142o. <\/p>\n<p><\/p>\n<p>Z czasem ludzie wymy\u015blili, \u017ce mo\u017cna mierzy\u0107 bardziej szczeg\u00f3\u0142owe informacje. Na przyk\u0142ad, mierzy\u0107 obci\u0105\u017cenie nie ca\u0142ego procesora, ale oddzielnie ka\u017cdego rdzenia procesora. Je\u015bli masz 40 rdzeni procesora, to odpowiednio pojawia si\u0119 40 razy wi\u0119cej szereg\u00f3w czasowych do pomiaru obci\u0105\u017cenia procesora. <\/p>\n<p><\/p>\n<p>Ale to nie wszystko. Ka\u017cde j\u0105dro procesora mo\u017ce mie\u0107 kilka stan\u00f3w, takich jak idle, kiedy jest nieaktywne. A tak\u017ce praca w przestrzeni u\u017cytkownika, praca w przestrzeni j\u0105dra i inne stany. I ka\u017cdy z tych stan\u00f3w r\u00f3wnie\u017c mo\u017cna mierzy\u0107 jako oddzielny szereg czasowy. To dodatkowo zwi\u0119ksza liczb\u0119 szereg\u00f3w o 7-8 razy.<\/p>\n<p><\/p>\n<p>Z jednej metryki uzyskali\u015bmy 40 x 8 = 320 metryk tylko na jeden komputer. Mno\u017cymy przez 100, otrzymujemy 32 000 zamiast 400. <\/p>\n<p><\/p>\n<p>Potem pojawi\u0142 si\u0119 Kubernetes. I sytuacja si\u0119 pogorszy\u0142a, poniewa\u017c w Kubernetes mo\u017ce by\u0107 hostowanych wiele r\u00f3\u017cnych us\u0142ug. Ka\u017cda us\u0142uga w Kubernetes sk\u0142ada si\u0119 z wielu pod\u00f3w. Wszystko to trzeba monitorowa\u0107. Dodatkowo mamy sta\u0142y deployment nowych wersji twoich us\u0142ug. Dla ka\u017cdej nowej wersji trzeba tworzy\u0107 nowe szeregi czasowe. W efekcie liczba szereg\u00f3w czasowych ro\u015bnie wyk\u0142adniczo i stajemy przed problemem du\u017cej liczby szereg\u00f3w czasowych, kt\u00f3ry nazywa si\u0119 high-cardinality. VictoriaMetrics skutecznie radzi sobie z tym w por\u00f3wnaniu z innymi bazami danych dla szereg\u00f3w czasowych. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/c11b7ce6d3294ae420243f72636af4b9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Przyjrzyjmy si\u0119 bli\u017cej high churn rate. Z jakich powod\u00f3w pojawia si\u0119 high churn rate w produkcji? Poniewa\u017c niekt\u00f3re warto\u015bci etykiet i tag\u00f3w stale si\u0119 zmieniaj\u0105.<\/p>\n<p><\/p>\n<p>Na przyk\u0142ad we\u017amy Kubernetes, w kt\u00f3rym istnieje poj\u0119cie <code>deployment<\/code>, tzn. gdy wdra\u017cana jest nowa wersja Twojej aplikacji. Programi\u015bci Kubernetes postanowili dziwnym trafem doda\u0107 identyfikator deploymentu do etykiety.<\/p>\n<p><\/p>\n<p>Do czego to doprowadzi\u0142o? Do tego, \u017ce przy ka\u017cdym nowym wdro\u017ceniu wszystkie stare serie czasowe s\u0105 przerywane, a w ich miejsce rozpoczynaj\u0105 si\u0119 nowe serie czasowe z now\u0105 warto\u015bci\u0105 etykiety. <code>deployment_id<\/code>. Takich szereg\u00f3w mo\u017ce by\u0107 setki tysi\u0119cy, a nawet miliony.<\/p>\n<p><\/p>\n<p>Wa\u017cn\u0105 cech\u0105 ca\u0142ej tej sytuacji jest to, \u017ce ca\u0142kowita liczba szereg\u00f3w czasowych ro\u015bnie, ale liczba aktywnych szereg\u00f3w czasowych, na kt\u00f3re sp\u0142ywaj\u0105 dane, pozostaje sta\u0142a. Taki stan nazywa si\u0119 high churn rate.<\/p>\n<p><\/p>\n<p>G\u0142\u00f3wnym problemem high churn rate jest zapewnienie sta\u0142ej pr\u0119dko\u015bci wyszukiwania wszystkich szereg\u00f3w czasowych zgodnie z okre\u015blonym zestawem etykiet w danym przedziale czasowym. Zazwyczaj jest to przedzia\u0142 czasowy za ostatni\u0105 godzin\u0119 lub ostatni dzie\u0144. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/ac18482b87cc37624d163b30c68cf7be.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Jak rozwi\u0105za\u0107 ten problem? Oto pierwszy wariant. Jest to podzielenie indeksu odwrotnego na niezale\u017cne cz\u0119\u015bci w czasie. Tzn. mija jaki\u015b okres czasu, ko\u0144czymy prac\u0119 z bie\u017c\u0105cym indeksem odwrotnym i tworzymy nowy indeks odwrotny. Mija kolejny okres czasu, tworzymy jeszcze jeden i jeszcze jeden. <\/p>\n<p><\/p>\n<p>Podczas odpytywania tych indeks\u00f3w odwrotnych znajdujemy zbi\u00f3r indeks\u00f3w odwrotnych, kt\u00f3re mieszcz\u0105 si\u0119 w okre\u015blonym przedziale. I odpowiednio wybieramy stamt\u0105d identyfikatory szereg\u00f3w czasowych. <\/p>\n<p><\/p>\n<p>To pozwala zaoszcz\u0119dzi\u0107 zasoby, poniewa\u017c nie musimy przegl\u0105da\u0107 cz\u0119\u015bci, kt\u00f3re nie mieszcz\u0105 si\u0119 w okre\u015blonym przedziale. Tzn. zazwyczaj, je\u015bli wybieramy dane za ostatni\u0105 godzin\u0119, to pominamy zapytania z wcze\u015bniejszych przedzia\u0142\u00f3w czasowych. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/1afa3a88caa7423941ae3494b9cec706.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Jest jeszcze jedna opcja rozwi\u0105zania tego problemu. To przechowywanie dla ka\u017cdego dnia oddzielnej listy identyfikator\u00f3w szereg\u00f3w czasowych, kt\u00f3re pojawi\u0142y si\u0119 w tym dniu.<\/p>\n<p><\/p>\n<p>Zalet\u0105 tego rozwi\u0105zania w por\u00f3wnaniu do poprzedniego jest to, \u017ce nie duplikujemy informacji o szeregach czasowych, kt\u00f3re nie znikaj\u0105 z up\u0142ywem czasu. One s\u0105 stale dost\u0119pne i si\u0119 nie zmieniaj\u0105. <\/p>\n<p><\/p>\n<p>Wad\u0105 jest to, \u017ce takie rozwi\u0105zanie jest trudniejsze w realizacji i w debugowaniu. I VictoriaMetrics wybra\u0142a to rozwi\u0105zanie. Tak si\u0119 z\u0142o\u017cy\u0142o historycznie. To rozwi\u0105zanie r\u00f3wnie\u017c sprawdza si\u0119 nie\u017ale w por\u00f3wnaniu do poprzedniego. Poniewa\u017c to rozwi\u0105zanie nie zosta\u0142o zrealizowane z powodu potrzeby duplikacji danych w ka\u017cdej partycji dla szereg\u00f3w czasowych, kt\u00f3re si\u0119 nie zmieniaj\u0105, tzn. kt\u00f3re nie znikaj\u0105 z up\u0142ywem czasu. VictoriaMetrics by\u0142a przede wszystkim zoptymalizowana pod k\u0105tem zu\u017cycia przestrzeni dyskowej, a poprzednia realizacja pogarsza\u0142a zu\u017cycie przestrzeni dyskowej. Natomiast ta realizacja lepiej nadaje si\u0119 do minimalizacji zu\u017cycia przestrzeni dyskowej, dlatego zosta\u0142a wybrana. <\/p>\n<p><\/p>\n<p>Trzeba by\u0142o z ni\u0105 walczy\u0107. Walka polega\u0142a na tym, \u017ce w tej realizacji trzeba by\u0142o mimo wszystko wybiera\u0107 znacznie wi\u0119ksz\u0105 ilo\u015b\u0107 <code>timeseries_ids<\/code> danych, ni\u017c gdy indeks odwrotny jest podzielony w czasie.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/5b10e61aaa803428ed3bfe31815f09d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Jak rozwi\u0105zali\u015bmy ten problem? Rozwi\u0105zali\u015bmy go w oryginalny spos\u00f3b \u2013 poprzez zapisanie kilku identyfikator\u00f3w szereg\u00f3w czasowych w ka\u017cdym wpisie indeksu odwrotnego zamiast jednego identyfikatora. Tzn. mamy klucz <code>label=value<\/code>, kt\u00f3ry wyst\u0119puje w ka\u017cdym szereg czasowym. Teraz przechowujemy kilka <code>timeseries_ids<\/code> w jednym rekordzie.<\/p>\n<p><\/p>\n<p>Oto przyk\u0142ad. Wcze\u015bniej mieli\u015bmy N rekord\u00f3w, a teraz mamy jeden rekord, kt\u00f3rego prefiks jest taki sam jak w przypadku wszystkich innych. W poprzednim rekordzie warto\u015b\u0107 zawiera wszystkie id szereg\u00f3w czasowych. <\/p>\n<p><\/p>\n<p>To pozwoli\u0142o zwi\u0119kszy\u0107 pr\u0119dko\u015b\u0107 skanowania takiego odwr\u00f3conego indeksu do 10 razy. I pozwoli\u0142o to zmniejszy\u0107 zu\u017cycie pami\u0119ci na cache, poniewa\u017c teraz przechowujemy ci\u0105g <code>label=value<\/code> tylko raz w cache zamiast N razy. Ten ci\u0105g mo\u017ce by\u0107 du\u017cy, je\u015bli w tagach i etykietach s\u0105 d\u0142ugie ci\u0105gi, kt\u00f3re lubi tam wk\u0142ada\u0107 Kubernetes.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/ae4f38a440203bad2d03cf2abd1faafe.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Inn\u0105 opcj\u0105 przyspieszenia wyszukiwania w odwr\u00f3conym indeksie jest sharding. Tworzenie kilku odwr\u00f3conych indeks\u00f3w zamiast jednego i shardowanie danych mi\u0119dzy nimi wed\u0142ug klucza. To zestaw <code>klucz=warto\u015b\u0107<\/code> par. To znaczy, \u017ce otrzymujemy kilka niezale\u017cnych odwr\u00f3conych indeks\u00f3w, kt\u00f3re mo\u017cemy przeszukiwa\u0107 r\u00f3wnolegle na kilku procesorach. Poprzednie implementacje pozwala\u0142y dzia\u0142a\u0107 tylko w trybie jednoproc, to znaczy skanowa\u0107 dane tylko na jednym rdzeniu. To rozwi\u0105zanie pozwala skanowa\u0107 dane jednocze\u015bnie na kilku rdzeniach, tak jak lubi to robi\u0107 ClickHouse. Plan mamy to zrealizowa\u0107.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/5ced6446efbf8ded8d7fb91694a999b0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A teraz wr\u00f3\u0107my do naszego sedna sprawy \u2014 do funkcji przeci\u0119cia <code>timeseries_ids<\/code>. Zobaczmy, jakie mog\u0105 by\u0107 implementacje. Ta funkcja pozwala znajdowa\u0107 <code>timeseries_ids<\/code> dla okre\u015blonego zestawu <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/073a155018a91120c6996c324772f9af.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Pierwsza opcja to naiwna implementacja. Dwa zagnie\u017cd\u017cone p\u0119tle. Oto wprowadzenie do funkcji <code>intersectInts<\/code> dwa slices \u2014 <code>a<\/code> i <code>b<\/code>. Na wyj\u015bciu powinna nam zwr\u00f3ci\u0107 przeci\u0119cie tych slices.<\/p>\n<p><\/p>\n<p>Naiwna implementacja wygl\u0105da tak. Przechodzimy przez wszystkie warto\u015bci z slice <code>a<\/code>, wewn\u0105trz tej p\u0119tli przechodzimy przez wszystkie warto\u015bci slice <code>b<\/code>. I por\u00f3wnujemy je. Je\u015bli si\u0119 zgadzaj\u0105, oznacza to, \u017ce znale\u017ali\u015bmy przeci\u0119cie. I zapisujemy to w <code>result<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/b89dcbb4f6d73377f9cf4f50169d6dd7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Jakie s\u0105 wady? Kwadratowa z\u0142o\u017cono\u015b\u0107 \u2014 to jej g\u0142\u00f3wna wada. Na przyk\u0142ad, je\u015bli rozmiary slice <code>a<\/code> i <code>b<\/code> maj\u0105 po milionie, to ta funkcja nigdy nie zwr\u00f3ci ci odpowiedzi. Poniewa\u017c potrzebuje wykona\u0107 trylion iteracji, co jest bardzo du\u017co, nawet dla nowoczesnych komputer\u00f3w.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/bad26b0092a2fd7fc813bcb79a9138ce.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Druga implementacja opiera si\u0119 na mapie. Tworzymy map\u0119. Umieszczamy w tej mapie wszystkie warto\u015bci z slice <code>a<\/code>. Nast\u0119pnie przechodzimy oddzieln\u0105 p\u0119tl\u0105 przez slice <code>b<\/code>. I sprawdzamy \u2014 czy ta warto\u015b\u0107 z slice istnieje <code>b<\/code> w map. Je\u015bli jest, to dodajemy go do wyniku. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/1e610acf641a9cfe4c80aac1fe26044c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Jakie s\u0105 zalety? Zalet\u0105 jest to, \u017ce tutaj wyst\u0119puje tylko liniowa z\u0142o\u017cono\u015b\u0107. To znaczy, \u017ce funkcja wykona si\u0119 znacznie szybciej dla du\u017cych rozmiar\u00f3w slices. Dla rozmiaru slice wynosz\u0105cego milion ta funkcja wykona si\u0119 w 2 milionach iteracji, w przeciwie\u0144stwie do biliona iteracji, jak w poprzedniej funkcji.<\/p>\n<p><\/p>\n<p>A wad\u0105 jest to, \u017ce ta funkcja wymaga wi\u0119cej pami\u0119ci, aby stworzy\u0107 t\u0119 map\u0119.<\/p>\n<p><\/p>\n<p>Drug\u0105 wad\u0105 jest du\u017cy overhead zwi\u0105zany z haszowaniem. Ta wada nie jest zbyt oczywista. Dla nas r\u00f3wnie\u017c nie by\u0142a bardzo oczywista, wi\u0119c na pocz\u0105tku w VictoriaMetrics implementacja intersection by\u0142a za pomoc\u0105 map. Ale p\u00f3\u017aniejsze profilowanie pokaza\u0142o, \u017ce g\u0142\u00f3wny czas procesora by\u0142 po\u015bwi\u0119cany na zapis do mapy i sprawdzanie obecno\u015bci warto\u015bci w tej mapie.<\/p>\n<p><\/p>\n<p>Dlaczego w tych miejscach marnuje si\u0119 czas procesora? Poniewa\u017c w tych linijkach Go wykonuje operacj\u0119 haszowania. To znaczy, \u017ce oblicza hash klucza, aby nast\u0119pnie odwo\u0142a\u0107 si\u0119 do okre\u015blonego indeksu w HashMap. Operacja obliczania hasha wykonuje si\u0119 w dziesi\u0105tkach nanosekund. To jest wolne dla VictoriaMetrics.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/969f10d875d88998e958894ca28b1181.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Postanowi\u0142em zaimplementowa\u0107 bitset, zoptymalizowany specjalnie na t\u0119 okazj\u0119. Oto jak teraz wygl\u0105da przeci\u0119cie dw\u00f3ch slices. Tu tworzymy bitset. Dodajemy do niego elementy z pierwszego slice. Nast\u0119pnie sprawdzamy obecno\u015b\u0107 tych element\u00f3w w drugim slice. I dodajemy je do wyniku. To znaczy, \u017ce praktycznie nie r\u00f3\u017cni si\u0119 od poprzedniego przyk\u0142adu. Jedyn\u0105 r\u00f3\u017cnic\u0105 jest to, \u017ce tutaj zast\u0105pili\u015bmy odwo\u0142anie do mapy funkcjami niestandardowymi. <code>add<\/code> i <code>has<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/b4ec0753d30d9684868a031f605632c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Na pierwszy rzut oka wydaje si\u0119, \u017ce powinno to dzia\u0142a\u0107 wolniej, je\u015bli wcze\u015bniej u\u017cywano standardowej mapy, a teraz wywo\u0142ywane s\u0105 jakie\u015b funkcje, ale profilowanie pokazuje, \u017ce to dzia\u0142a 10 razy szybciej ni\u017c standardowa mapa w przypadku VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Dodatkowo u\u017cywa du\u017co mniej pami\u0119ci w por\u00f3wnaniu z implementacj\u0105 opartej na mapie. Poniewa\u017c przechowujemy tutaj bity zamiast o\u015bmiobajtowych warto\u015bci.<\/p>\n<p><\/p>\n<p>Wad\u0105 takiej implementacji jest to, \u017ce nie jest ona tak oczywista, nie jest trywialna. <\/p>\n<p><\/p>\n<p>Kolejn\u0105 wad\u0105, kt\u00f3r\u0105 wiele os\u00f3b mo\u017ce przeoczy\u0107, jest to, \u017ce to wdro\u017cenie mo\u017ce dzia\u0142a\u0107 \u017ale w niekt\u00f3rych przypadkach. To znaczy, \u017ce jest zoptymalizowane dla konkretnego przypadku, dla tego przypadku przeci\u0119cia identyfikator\u00f3w czasowych VictoriaMetrics. Nie oznacza to, \u017ce b\u0119dzie odpowiednie dla wszystkich sytuacji. Je\u015bli zostanie \u017ale u\u017cyte, zamiast poprawy wydajno\u015bci uzyskamy b\u0142\u0105d out of memory i spowolnienie dzia\u0142ania. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/94bd174afe43e67b3cf279dc7a1be17c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Rozwa\u017cmy wdro\u017cenie tej struktury. Je\u015bli chcesz zobaczy\u0107, znajduje si\u0119 w \u017ar\u00f3d\u0142ach VictoriaMetrics, w folderze <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/uint64set\">lib\/uint64set<\/a><\/noindex>. Jest zoptymalizowane dok\u0142adnie pod k\u0105tem VictoriaMetrics, gdzie <code>timeseries_id<\/code> reprezentuje 64-bitow\u0105 warto\u015b\u0107, gdzie pierwsze 32 bity s\u0105 sta\u0142e, a zmieniaj\u0105 si\u0119 tylko ostatnie 32 bity.<\/p>\n<p><\/p>\n<p>Ta struktura danych nie jest przechowywana na dysku, dzia\u0142a tylko w pami\u0119ci. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/e24ca65f860e2b04037e073f7acae0c1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Oto jej API. Nie jest ono zbyt skomplikowane. API jest dostosowane dok\u0142adnie do konkretnego przypadku u\u017cycia VictoriaMetrics. To znaczy, \u017ce nie ma zb\u0119dnych funkcji. Tutaj znajduj\u0105 si\u0119 funkcje, kt\u00f3re s\u0105 wyra\u017anie u\u017cywane przez VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Jest funkcja <code>add<\/code>, kt\u00f3ra dodaje nowe warto\u015bci. Jest funkcja <code>has<\/code>, kt\u00f3ra sprawdza nowe warto\u015bci. I jest funkcja <code>del<\/code>, kt\u00f3ra usuwa warto\u015bci. Jest pomocnicza funkcja <code>len<\/code>, kt\u00f3ra zwraca rozmiar zbioru. Funkcja <code>clone<\/code> klonuje zbi\u00f3r. I funkcja <code>appendto<\/code> przekszta\u0142ca ten zbi\u00f3r w slice. <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/874b04042d13f342d39f8f393e7fb78c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Oto jak wygl\u0105da implementacja tej struktury danych. W zbiorze s\u0105 dwa elementy:<\/p>\n<p><\/p>\n<ul>\n<li>\n<p><code>ItemsCount<\/code> \u2013 to pole pomocnicze, aby szybko zwr\u00f3ci\u0107 liczb\u0119 element\u00f3w w zbiorze. Mo\u017cna by obej\u015b\u0107 si\u0119 bez tego pola pomocniczego, ale musiano je doda\u0107 tutaj, poniewa\u017c VictoriaMetrics cz\u0119sto sprawdza d\u0142ugo\u015b\u0107 bitsetu w swoich algorytmach.<\/p>\n<p>\n<\/li>\n<li>\n<p>Drugie pole to <code>buckets<\/code>. To slice ze struktury <code>bucket32.<\/code>W ka\u017cdej strukturze przechowywane jest <code>hi<\/code> pole. To g\u00f3rne 32 bity. I dwa slice \u2014 <code>b16his<\/code> i <code>buckets<\/code> z <code>bucket16<\/code> struktur. <\/p>\n<p>\n<\/li>\n<\/ul>\n<p><\/p>\n<p>Tutaj przechowywane s\u0105 g\u00f3rne 16 bit\u00f3w drugiej cz\u0119\u015bci 64-bitowej struktury. A tutaj znajduj\u0105 si\u0119 bitsety dla ni\u017cszych 16 bit\u00f3w ka\u017cdego bajtu. <\/p>\n<p><\/p>\n<p><code>Bucket64<\/code> sk\u0142ada si\u0119 z tablicy <code>uint64.<\/code>D\u0142ugo\u015b\u0107 oblicza si\u0119 przy pomocy tych konstant. W jednym <code>bucket16<\/code> maksimum mo\u017ce by\u0107 przechowywane <code>2^16=65536<\/code> bit\u00f3w. Je\u015bli podzielimy to przez 8, to mamy 8 kilobajt\u00f3w. Je\u015bli podzielimy jeszcze raz przez 8, to mamy 1000 <code>uint64.<\/code> warto\u015bci. To znaczy, \u017ce <code>Bucket16<\/code> to 8-kilobajtowa struktura. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/1cca4e6bfafa3408a3c95e0118e26d80.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Rozwa\u017cmy, jak zaimplementowano jedn\u0105 z metod tej struktury dodawania nowej warto\u015bci. <\/p>\n<p><\/p>\n<p>Wszystko zaczyna si\u0119 od <code>uint64.<\/code> warto\u015bci. Obliczamy g\u00f3rne 32 bity, obliczamy dolne 32 bity. Przechodzimy przez wszystkie <code>buckets<\/code>. Por\u00f3wnujemy g\u00f3rne 32 bity w ka\u017cdym kube\u0142ku z dodawan\u0105 warto\u015bci\u0105. A je\u015bli si\u0119 zgadzaj\u0105, wywo\u0142ujemy funkcj\u0119 <code>add<\/code> w strukturze b32 <code>buckets<\/code>. I dodajemy tam dolne 32 bity. A je\u015bli to zwr\u00f3ci\u0142o <code>true<\/code>, to znaczy, \u017ce dodali\u015bmy tak\u0105 warto\u015b\u0107 i wcze\u015bniej nie mieli\u015bmy jej. Je\u015bli zwraca <code>false<\/code>, to taka warto\u015b\u0107 ju\u017c istnia\u0142a. Nast\u0119pnie zwi\u0119kszamy liczb\u0119 element\u00f3w w strukturze. <\/p>\n<p><\/p>\n<p>Je\u015bli nie znale\u017ali\u015bmy potrzebnej <code>bucket<\/code> z odpowiedni\u0105 warto\u015bci\u0105 hi, wywo\u0142ujemy funkcj\u0119 <code>addAlloc<\/code>, kt\u00f3ra przydziela nowy <code>bucket<\/code>, dodaj\u0105c go do struktury kube\u0142k\u00f3w.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/c5d1e5de767f47c7c40e01e12b6d0617.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>To realizacja funkcji <code>b32.add<\/code>. Jest podobna do poprzedniej realizacji. Obliczamy starsze 16 bit\u00f3w, m\u0142odsze 16 bit\u00f3w.<\/p>\n<p><\/p>\n<p>Potem przechodzimy przez wszystkie g\u00f3rne 16 bit\u00f3w. Znajdujemy dopasowania. A w przypadku dopasowania wywo\u0142ujemy metod\u0119 add, kt\u00f3r\u0105 om\u00f3wimy na nast\u0119pnej stronie dla <code>bucket16<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/e98c4ef316f3a894fd7019c3c85696f7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>I oto najni\u017cszy poziom, kt\u00f3ry powinien by\u0107 maksymalnie zoptymalizowany. Obliczamy dla <code>uint64.<\/code> id warto\u015b\u0107 w slice bit oraz <code>bitmask<\/code>. To maska dla tej 64-bitowej warto\u015bci, na podstawie kt\u00f3rej mo\u017cna sprawdzi\u0107 obecno\u015b\u0107 tego bitu lub go ustawi\u0107. Sprawdzamy obecno\u015b\u0107 tego bitu, ustawionego i ustawiamy go, a nast\u0119pnie zwracamy obecno\u015b\u0107. Oto taka realizacja, kt\u00f3ra umo\u017cliwi\u0142a przyspieszenie operacji przeci\u0119cia ids szereg\u00f3w czasowych 10 razy w por\u00f3wnaniu do zwyk\u0142ych map.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/4dfa5a6142829a3551be22d6c9c3ab92.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>W VictoriaMetrics opr\u00f3cz tej optymalizacji istnieje wiele innych optymalizacji. Wi\u0119kszo\u015b\u0107 z tych optymalizacji zosta\u0142a dodana nie bez powodu, a po profilowaniu kodu w produkcji.<\/p>\n<p><\/p>\n<p>To g\u0142\u00f3wna zasada optymalizacji - nie dodawa\u0107 optymalizacji, zak\u0142adaj\u0105c, \u017ce tutaj b\u0119dzie w\u0105skie miejsce, poniewa\u017c mo\u017ce okaza\u0107 si\u0119, \u017ce tam w\u0105skiego miejsca nie ma. Optymalizacja zazwyczaj pogarsza jako\u015b\u0107 kodu. Dlatego warto optymalizowa\u0107 tylko po profilowaniu, a najlepiej w produkcji, aby mia\u0142y to rzeczywiste dane. Kto jest zainteresowany, mo\u017ce zajrze\u0107 do \u017ar\u00f3de\u0142 VictoriaMetrics i zbada\u0107 inne optymalizacje, kt\u00f3re tam s\u0105.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optymalizacje Go w VictoriaMetrics. Aleksandr Walia\u0142kin\" src=\"\/wp-content\/uploads\/2020\/05\/7585c4dc782627bac5b649e6a55e2ffb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><em>Mam pytanie dotycz\u0105ce bitset. Bardzo przypomina implementacj\u0119 C++ vector bool, zoptymalizowany bitset. Czy wzi\u0119li\u015bcie stamt\u0105d t\u0119 realizacj\u0119?<\/em><\/p>\n<p><\/p>\n<p>Nie, nie stamt\u0105d. Przy realizacji tego bitsetu opiera\u0142em si\u0119 na wiedzy o strukturze tych identyfikator\u00f3w czasowych, kt\u00f3re s\u0105 wykorzystywane w VictoriaMetrics. Ich struktura jest taka, \u017ce g\u00f3rne 32 bity s\u0105 g\u0142\u00f3wnie sta\u0142e. Dolne 32 bity mog\u0105 si\u0119 zmienia\u0107. Im ni\u017cszy bit, tym cz\u0119\u015bciej mo\u017ce si\u0119 zmienia\u0107. Dlatego ta realizacja jest dok\u0142adnie zoptymalizowana pod t\u0119 struktur\u0119 danych. Implementacja w C++, o ile wiem, jest zoptymalizowana pod og\u00f3lny przypadek. Je\u015bli dokonuje si\u0119 optymalizacji pod og\u00f3lny przypadek, to oznacza, \u017ce nie b\u0119dzie ona najbardziej optymalna dla konkretnego przypadku.<\/p>\n<p><\/p>\n<p>Zalecam r\u00f3wnie\u017c zapoznanie si\u0119 z prezentacj\u0105 Aleksieja Mi\u0142owida. M\u00f3wi\u0142 oko\u0142o miesi\u0105ca temu o optymalizacjach w ClickHouse pod konkretne specjalizacje. W\u0142a\u015bnie wskazuje, \u017ce w og\u00f3lnym przypadku implementacja w C++ lub jakakolwiek inna implementacja jest dostosowana do dobrej pracy \u015brednio w szpitalu. Mo\u017ce dzia\u0142a\u0107 gorzej ni\u017c specjalizowana implementacja, jak w naszym przypadku, gdy wiemy, \u017ce g\u00f3rne 32 bity s\u0105 g\u0142\u00f3wnie sta\u0142e.<\/p>\n<p><\/p>\n<p><em>Mam drugie pytanie. Jaka jest fundamentalna r\u00f3\u017cnica w por\u00f3wnaniu do InfluxDB?<\/em><\/p>\n<p><\/p>\n<p>Istnieje wiele zasadniczych r\u00f3\u017cnic. Je\u015bli chodzi o wydajno\u015b\u0107 i zu\u017cycie pami\u0119ci, to InfluxDB w testach wykazuje dziesi\u0119ciokrotnie wy\u017csze zu\u017cycie pami\u0119ci dla wzorc\u00f3w czasowych o du\u017cej kardynalno\u015bci, gdy jest ich du\u017co, na przyk\u0142ad miliony. Na przyk\u0142ad VictoriaMetrics zu\u017cywa 1 GB na milion aktywnych serii, podczas gdy InfluxDB zu\u017cywa 10 GB. To jest du\u017ca r\u00f3\u017cnica. <\/p>\n<p><\/p>\n<p>Drug\u0105 fundamentaln\u0105 r\u00f3\u017cnic\u0105 jest to, \u017ce w InfluxDB wyst\u0119puj\u0105 dziwne j\u0119zyki zapyta\u0144 \u2013 Flux i InfluxQL. Nie s\u0105 one zbyt wygodne do pracy z szeregami czasowymi w por\u00f3wnaniu z <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, kt\u00f3ry jest obs\u0142ugiwany w VictoriaMetrics. PromQL to j\u0119zyk zapyta\u0144 w Prometheus.<\/p>\n<p><\/p>\n<p>I jeszcze jedna r\u00f3\u017cnica \u2013 to, \u017ce InfluxDB ma nieco dziwny model danych, w kt\u00f3rym w ka\u017cdej linii mo\u017ce by\u0107 przechowywanych kilka fields z r\u00f3\u017cnymi zestawami tag\u00f3w. Te linie dziel\u0105 si\u0119 jeszcze na r\u00f3\u017cne tabele. Te dodatkowe komplikacje utrudniaj\u0105 p\u00f3\u017aniejsz\u0105 prac\u0119 z t\u0105 baz\u0105. Jest trudno j\u0105 utrzymywa\u0107 i rozumie\u0107.<\/p>\n<p><\/p>\n<p>W VictoriaMetrics wszystko jest znacznie prostsze. Tam ka\u017cdy szereg czasowy przedstawia par\u0119 klucz-warto\u015b\u0107. Warto\u015b\u0107 to zestaw punkt\u00f3w \u2013 <code>(timestamp, value)<\/code>, a klucz to zestaw <code>label=value<\/code>. Nie ma \u017cadnego podzia\u0142u na pola i pomiary. Umo\u017cliwia to wybieranie dowolnych danych, a nast\u0119pnie ich \u0142\u0105czenie, dodawanie, odejmowanie, mno\u017cenie, dzielenie, co r\u00f3\u017cni si\u0119 od InfluxDB, gdzie obliczenia mi\u0119dzy r\u00f3\u017cnymi wierszami wci\u0105\u017c nie s\u0105 zrealizowane, o ile mi wiadomo. Nawet je\u015bli by\u0142yby zrealizowane, to s\u0105 skomplikowane i wymaga\u0142yby napisania du\u017cej ilo\u015bci kodu. <\/p>\n<p><\/p>\n<p><em>Mam pytanie do wyja\u015bnienia. Czy dobrze zrozumia\u0142em, \u017ce by\u0142 jaki\u015b problem, o kt\u00f3rym m\u00f3wi\u0142e\u015b, \u017ce ten odwr\u00f3cony indeks nie mie\u015bci si\u0119 w pami\u0119ci, przez co stosujecie partycjonowanie?<\/em><\/p>\n<p><\/p>\n<p>Na pocz\u0105tku pokaza\u0142em naiwn\u0105 implementacj\u0119 odwr\u00f3conego indeksu w standardowej mapie Go. Taka implementacja nie nadaje si\u0119 do baz danych, poniewa\u017c ten odwr\u00f3cony indeks nie jest zapisywany na dysku, a baza danych powinna zapisywa\u0107 dane na dysku, aby po restarcie by\u0142y one dost\u0119pne. W tej implementacji po restarcie aplikacji stracisz odwr\u00f3cony indeks. I stracisz dost\u0119p do wszystkich danych, poniewa\u017c nie b\u0119dziesz m\u00f3g\u0142 ich znale\u017a\u0107. <\/p>\n<p><\/p>\n<p><em>Cze\u015b\u0107! Dzi\u0119kuj\u0119 za prezentacj\u0119! Nazywam si\u0119 Pawel. Jestem z firmy Wildberries. Mam do Ciebie kilka pyta\u0144. Pierwsze pytanie. Jak uwa\u017casz, gdyby\u015b wybra\u0142 inn\u0105 zasad\u0119 przy budowie architektury swojej aplikacji i partycjonowa\u0142 dane wed\u0142ug czasu, to by\u0107 mo\u017ce uda\u0142oby Ci si\u0119 przeprowadza\u0107 przeci\u0119cia danych podczas wyszukiwania, opieraj\u0105c si\u0119 wy\u0142\u0105cznie na tym, \u017ce w jednej partycji znajduj\u0105 si\u0119 dane za jeden okres czasu, tzn. za jeden przedzia\u0142 czasu i nie musia\u0142by\u015b si\u0119 martwi\u0107, \u017ce kawa\u0142ki s\u0105 r\u00f3\u017cnie rozrzucone? Drugie pytanie \u2014 skoro wdra\u017casz podobny algorytm z bitsetem i wszystkim innym, to by\u0107 mo\u017ce pr\u00f3bowa\u0142e\u015b u\u017cywa\u0107 instrukcji procesora? Mo\u017ce pr\u00f3bowa\u0142e\u015b takich optymalizacji?<\/em><\/p>\n<p><\/p>\n<p>Na drugie od razu odpowiem. Jeszcze do tego nie doszli\u015bmy. Ale je\u015bli b\u0119dzie trzeba, dojdziemy. A pierwsze, jakie by\u0142o pytanie?<\/p>\n<p><\/p>\n<p><em>Dyskutowa\u0142e\u015b o dw\u00f3ch scenariuszach. I powiedzia\u0142e\u015b, \u017ce wybra\u0142e\u015b drugi z bardziej skomplikowan\u0105 implementacj\u0105. A pierwszy, gdzie dane s\u0105 partycjonowane wed\u0142ug czasu, Ci\u0119 nie interesowa\u0142.<\/em> <\/p>\n<p><\/p>\n<p>Tak. W pierwszym przypadku ca\u0142kowity rozmiar indeksu by\u0142by wi\u0119kszy, poniewa\u017c w ka\u017cdej partycji musieliby\u015bmy przechowywa\u0107 duplikaty danych dla tych wszystkich szereg\u00f3w czasowych, kt\u00f3re przechodz\u0105 przez wszystkie te partycje. A je\u015bli wska\u017anik churn dla szereg\u00f3w czasowych jest niski, tzn. stale u\u017cywane s\u0105 te same szeregi, to w pierwszym przypadku znacznie bardziej straciliby\u015bmy na zajmowanej przestrzeni dyskowej w por\u00f3wnaniu do drugiego przypadku.<\/p>\n<p><\/p>\n<p>Zgadza si\u0119, partycjonowanie wed\u0142ug czasu to dobry wyb\u00f3r. U\u017cywa go Prometheus. Ale w Prometheus jest inna wada. Podczas \u0142\u0105czenia tych fragment\u00f3w danych wymaga trzymania w pami\u0119ci informacji meta na temat wszystkich etykiet i szereg\u00f3w czasowych. Dlatego, je\u015bli fragmenty danych s\u0105 du\u017ce, kt\u00f3re on \u0142\u0105czy, to zu\u017cycie pami\u0119ci znacznie wzrasta w trakcie tego procesu, w przeciwie\u0144stwie do VictoriaMetrics. Podczas \u0142\u0105czenia VictoriaMetrics w og\u00f3le nie zu\u017cywa pami\u0119ci, tam zu\u017cycie wynosi kilka kilobajt\u00f3w, niezale\u017cnie od rozmiar\u00f3w \u0142\u0105czonych fragment\u00f3w danych.<\/p>\n<p><\/p>\n<p><em>Algorytm, kt\u00f3ry u\u017cywacie, korzysta z pami\u0119ci. W niej zaznaczane s\u0105 etykiety szereg\u00f3w czasowych, dla kt\u00f3rych istniej\u0105 warto\u015bci. W ten spos\u00f3b sprawdzacie, czy wyst\u0119puje wsp\u00f3\u0142istnienie w jednej i drugiej tablicy danych. I rozumiecie \u2013 czy wyst\u0105pi\u0142o przeci\u0119cie, czy nie. Zwykle w bazach danych wdra\u017cane s\u0105 kursory, iteratory, kt\u00f3re przechowuj\u0105 swoje bie\u017c\u0105ce stany i poruszaj\u0105 si\u0119 po posortowanych danych, co zapewnia prost\u0105 z\u0142o\u017cono\u015b\u0107 tych operacji.<\/em> <\/p>\n<p><\/p>\n<p>Dlaczego nie u\u017cywamy kursor\u00f3w do przeci\u0119cia danych?<\/p>\n<p><\/p>\n<p><em>Tak.<\/em> <\/p>\n<p><\/p>\n<p>W LevelDB lub w mergeset przechowywane s\u0105 w\u0142a\u015bnie posortowane wiersze. Mo\u017cemy przej\u015b\u0107 kursorem i znale\u017a\u0107 przeci\u0119cie. A dlaczego tego nie robimy? Poniewa\u017c to jest wolne. Poniewa\u017c kursory sugeruj\u0105, \u017ce dla ka\u017cdego wiersza nale\u017cy wywo\u0142a\u0107 funkcj\u0119. Wywo\u0142anie funkcji to 5 nanosekund. A je\u015bli macie 100 000 000 wierszy, to okazuje si\u0119, \u017ce tracimy p\u00f3\u0142 sekundy tylko na wywo\u0142anie funkcji.<\/p>\n<p><\/p>\n<p><em>Tak, to prawda. I mam ostatnie pytanie. Pytanie mo\u017ce brzmie\u0107 nieco dziwnie. Dlaczego w momencie przyj\u0119cia danych nie mo\u017cna od razu obliczy\u0107 wszystkich niezb\u0119dnych agregat\u00f3w i zapisa\u0107 ich w wymaganej formie? Po co przechowywa\u0107 ogromne ilo\u015bci w r\u00f3\u017cnych systemach, takich jak VictoriaMetrics, ClickHouse itd., aby p\u00f3\u017aniej marnowa\u0107 na nie wiele czasu?<\/em><\/p>\n<p><\/p>\n<p><em>Podam przyk\u0142ad, aby to by\u0142o ja\u015bniejsze. Za\u0142\u00f3\u017cmy, jak dzia\u0142a ma\u0142y, zabawkowy licznik pr\u0119dko\u015bci? Rejestruje on odleg\u0142o\u015b\u0107, kt\u00f3r\u0105 przejecha\u0142e\u015b, ca\u0142y czas dodaj\u0105c j\u0105 do jednej wielko\u015bci, a do drugiej \u2013 czas. I dzieli. A potem uzyskuje \u015bredni\u0105 pr\u0119dko\u015b\u0107. Mo\u017cna zrobi\u0107 podobnie. Zbiera\u0107 wszystkie niezb\u0119dne fakty na bie\u017c\u0105co.<\/em><\/p>\n<p><\/p>\n<p>Dobrze, rozumiem pytanie. Tw\u00f3j przyk\u0142ad ma sens. Je\u015bli wiesz, jakie agregaty potrzebujesz, to jest to najlepsza realizacja. Ale problem w tym, \u017ce ludzie przechowuj\u0105 te metryki, jakie\u015b dane w ClickHouse i nie wiedz\u0105 jeszcze, jak b\u0119d\u0105 je w przysz\u0142o\u015bci agregowa\u0107 czy filtrowa\u0107, wi\u0119c musz\u0105 przechowywa\u0107 wszystkie surowe dane. Ale je\u015bli wiesz, \u017ce potrzebujesz obliczy\u0107 co\u015b \u015bredniego, to dlaczego nie obliczy\u0107 tego, zamiast przechowywa\u0107 tam mn\u00f3stwo surowych warto\u015bci? Ale to tylko wtedy, gdy dok\u0142adnie wiesz, co jest ci potrzebne.<\/p>\n<p><\/p>\n<p>Zreszt\u0105, bazy do przechowywania szereg\u00f3w czasowych wspieraj\u0105 liczenie agregat\u00f3w. Na przyk\u0142ad, Prometheus wspiera <noindex><a rel=\"nofollow\" href=\"https:\/\/prometheus.io\/docs\/prometheus\/latest\/configuration\/recording_rules\/\">regu\u0142y rejestracji<\/a><\/noindex>. To znaczy, mo\u017cna to zrobi\u0107, je\u015bli wiesz, jakie agregaty b\u0119d\u0105 ci potrzebne. W VictoriaMetrics na razie tego nie ma, ale zazwyczaj przed ni\u0105 stawia si\u0119 Prometheus, w kt\u00f3rym mo\u017cna to zrobi\u0107 w regu\u0142ach rejestracji.<\/p>\n<p><\/p>\n<p>Na przyk\u0142ad, w mojej poprzedniej pracy trzeba by\u0142o liczy\u0107 liczb\u0119 zdarze\u0144 w oknie przesuwaj\u0105cym si\u0119 za ostatni\u0105 godzin\u0119. Problem w tym, \u017ce musia\u0142em stworzy\u0107 niestandardow\u0105 realizacj\u0119 w Go, to znaczy serwis do liczenia tej rzeczy. Ten serwis by\u0142 ostatecznie nieprosty, poniewa\u017c liczenie tego jest skomplikowane. Realizacja mo\u017ce by\u0107 prosta, je\u015bli potrzebujesz liczy\u0107 jakie\u015b agregaty w sta\u0142ych przedzia\u0142ach czasowych. Je\u015bli jednak chcesz liczy\u0107 zdarzenia w oknie przesuwaj\u0105cym si\u0119, to nie jest to tak proste, jak si\u0119 wydaje. My\u015bl\u0119, \u017ce to nadal nie jest zrealizowane w ClickHouse ani w bazach danych czasowych, poniewa\u017c jest to skomplikowane do realizacji.<\/p>\n<p><\/p>\n<p><em>I jeszcze jedno pytanie. Teraz rozmawiali\u015bmy o \u015bredniej, a przypomnia\u0142em sobie, \u017ce kiedy\u015b istnia\u0142 taki system jak Graphite z backendem Carbon. Potrafi\u0142 on odrzuca\u0107 stare dane, to znaczy zostawia\u0107 jeden punkt na minut\u0119, jeden punkt na godzin\u0119 itd. W zasadzie, to do\u015b\u0107 wygodne, je\u015bli potrzebujemy surowych danych, powiedzmy, za miesi\u0105c, a wszystko inne mo\u017cna odrzuci\u0107. Ale Prometheus i VictoriaMetrics tej funkcjonalno\u015bci nie wspieraj\u0105. Czy planuje si\u0119 wprowadzenie wsparcia dla tego? Je\u015bli nie, to dlaczego?<\/em><\/p>\n<p><\/p>\n<p>Dzi\u0119kujemy za pytanie. Nasi u\u017cytkownicy cz\u0119sto je zadaj\u0105. Pytaj\u0105, kiedy dodamy wsparcie dla pr\u00f3bkowania (downsampling). Jest kilka problem\u00f3w. Po pierwsze, ka\u017cdy u\u017cytkownik rozumie pod <code>downsampling<\/code> to co\u015b innego: niekt\u00f3rzy chc\u0105 uzyska\u0107 dowolny punkt w zadanym przedziale, inni potrzebuj\u0105 warto\u015bci maksymalne, minimalne lub \u015brednie. Je\u015bli w Twojej bazie danych dane pisz\u0105 r\u00f3\u017cne systemy, nie mo\u017cna ich \u0142\u0105czy\u0107 w jedn\u0105 ca\u0142o\u015b\u0107. Mo\u017ce si\u0119 okaza\u0107, \u017ce dla ka\u017cdego systemu nale\u017cy zastosowa\u0107 inne pr\u00f3bkowanie. A to jest trudne do zrealizowania.<\/p>\n<p><\/p>\n<p>A po drugie \u2013 to, \u017ce VictoriaMetrics, podobnie jak ClickHouse, jest zoptymalizowana do pracy z du\u017cymi zbiorami surowych danych, dlatego mo\u017ce przetworzy\u0107 miliard wierszy w mniej ni\u017c sekund\u0119, je\u015bli masz wiele rdzeni w swoim systemie. Skanowanie punkt\u00f3w szereg\u00f3w czasowych w VictoriaMetrics \u2013 50 000 000 punkt\u00f3w na sekund\u0119 na jeden rdze\u0144. I ta wydajno\u015b\u0107 skaluj\u0119 si\u0119 w zale\u017cno\u015bci od dost\u0119pnych rdzeni. Tzn. je\u015bli masz na przyk\u0142ad 20 rdzeni, to uda si\u0119 przeskanowa\u0107 miliard punkt\u00f3w na sekund\u0119. Ta cecha VictoriaMetrics i ClickHouse zmniejsza zapotrzebowanie na downsampling.<\/p>\n<p><\/p>\n<p>Kolejn\u0105 cech\u0105 jest to, \u017ce VictoriaMetrics efektywnie kompresuje te dane. Kompresja w \u015bredniej produkcji wynosi od 0,4 do 0,8 bajta na punkt. Ka\u017cdy punkt to timestamp + warto\u015b\u0107. I \u015brednio kompresuje si\u0119 poni\u017cej jednego bajta. <\/p>\n<p><\/p>\n<p><em>Sergie. Mam pytanie. Jaki jest minimalny kwant czasu zapisu?<\/em><\/p>\n<p><\/p>\n<p>Jedna milisekunda. Niedawno rozmawiali\u015bmy z innymi deweloperami baz danych dla szereg\u00f3w czasowych. U nich minimalny kwant czasu to jedna sekunda. W Graphite na przyk\u0142ad r\u00f3wnie\u017c jedna sekunda. W OpenTSDB tak\u017ce jedna sekunda. W InfluxDB \u2013 precyzja nanosekundowa. W VictoriaMetrics \u2013 jedna milisekunda, poniewa\u017c w Prometheus jest jedna milisekunda. VictoriaMetrics by\u0142a rozwijana jako zdalna przestrze\u0144 do przechowywania dla Prometheus na pocz\u0105tku. Ale teraz mo\u017ce przechowywa\u0107 dane r\u00f3wnie\u017c z innych system\u00f3w. <\/p>\n<p><\/p>\n<p>Osoba, z kt\u00f3r\u0105 rozmawia\u0142em, m\u00f3wi, \u017ce maj\u0105 precyzj\u0119 sekundy \u2013 to im wystarcza, poniewa\u017c to zale\u017cy od rodzaju danych, kt\u00f3re s\u0105 zapisywane w bazie szereg\u00f3w czasowych. Je\u015bli to s\u0105 dane DevOps lub dane z infrastruktury, gdzie zbierasz je co 30 sekund, co minut\u0119, to precyzja sekundy jest wystarczaj\u0105ca, mniej nie jest potrzebne. A je\u015bli zbierasz te dane z system\u00f3w handlu wysokocz\u0119stotliwo\u015bciowego, to tam potrzebna jest precyzja nanosekundowa.<\/p>\n<p><\/p>\n<p>Milisekundowa precyzja w VictoriaMetrics nadaje si\u0119 zar\u00f3wno do przypadk\u00f3w u\u017cycia DevOps, jak i do wi\u0119kszo\u015bci przypadk\u00f3w, kt\u00f3re wspomnia\u0142em na pocz\u0105tku prezentacji. Jedynym przypadkiem, w kt\u00f3rym mo\u017ce nie by\u0107 odpowiednia, s\u0105 systemy handlu wysokiej cz\u0119stotliwo\u015bci.<\/p>\n<p><\/p>\n<p><em>Dzi\u0119kuj\u0119! I jeszcze jedno pytanie. Jaka jest kompatybilno\u015b\u0107 w PromQL?<\/em><\/p>\n<p><\/p>\n<p>Pe\u0142na kompatybilno\u015b\u0107 wsteczna. VictoriaMetrics wspiera w pe\u0142ni PromQL. Dodatkowo dodaje jeszcze dodatkow\u0105 rozszerzon\u0105 funkcjonalno\u015b\u0107 do PromQL, kt\u00f3ra nazywa si\u0119 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/wiki\/MetricsQL\">MetricsQL<\/a><\/noindex>. W tej sprawie jest prezentacja na YouTube. Opowiada\u0142em o tym na Monitoring Meetup wiosn\u0105 w Petersburgu.<\/p>\n<p><\/p>\n<p>Kana\u0142 Telegram <noindex><a rel=\"nofollow\" href=\"https:\/\/t.me\/VictoriaMetrics_ru1\">VictoriaMetrics<\/a><\/noindex>.<\/p>\n<p class=\"for_users_only_msg\">Tylko zarejestrowani u\u017cytkownicy mog\u0105 bra\u0107 udzia\u0142 w ankiecie. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/auth\/login\/\">Zaloguj si\u0119<\/a><\/noindex>, prosz\u0119.<\/p>\n<h2 class=\"default-block__polling-title\">Co powstrzymuje Ci\u0119 przed przej\u015bciem na VictoriaMetrics jako d\u0142ugoterminowe magazynowanie dla Prometheusa? (Napisz w komentarzach, dodam do ankiety))<\/h2>\n<ul class=\"poll-result\">\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent  poll-result__data-percent_winner\">71,4%<\/strong>Nie u\u017cywam Prometheus5<\/p>\n<\/li>\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent\">28,6%<\/strong>Nie wiedzia\u0142em o VictoriaMetrics2<\/p>\n<\/li>\n<\/ul>\n<p>    7 u\u017cytkownik\u00f3w odda\u0142o g\u0142os. 12 u\u017cytkownik\u00f3w wstrzyma\u0142o si\u0119.<br \/>\n<br \/>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/500844\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot; VictoriaMetrics \u2014 \u0431\u044b\u0441\u0442\u0440\u0430\u044f \u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u0430\u044f \u0421\u0423\u0411\u0414 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0444\u043e\u0440\u043c\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0440\u044f\u0434\u0430 (\u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u0432\u0440\u0435\u043c\u044f \u0438 \u043d\u0430\u0431\u043e\u0440 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u044d\u0442\u043e\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u043f\u0440\u043e\u0441 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0438\u043b\u0438 \u0441\u0431\u043e\u0440 \u043c\u0435\u0442\u0440\u0438\u043a). \u0412\u043e\u0442 \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u2014 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":80734,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-80733","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot;\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47Go optimizations in VictoriaMetrics. \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot;\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-05-08T11:42:31+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-05-08T11:42:31+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Optymalizacje Go w VictoriaMetrics. Aleksander Wali\u0142kin | ProHoster","description":"Zach\u0119cam do zapoznania si\u0119 z transkrypcj\u0105 wyst\u0105pienia Aleksandra Wali\u0142kina z ko\u0144ca 2019 roku \"Optymalizacje Go w VictoriaMetrics\"","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47Go optimizations in VictoriaMetrics. \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d | ProHoster","og:description":"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot;","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-05-08T11:42:31+00:00","article:modified_time":"2020-05-08T11:42:31+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"80733","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 16:12:22","updated":"2022-09-29 05:16:51","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/80733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=80733"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/80733\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/80734"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=80733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=80733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=80733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}