{"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\/it\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","title":{"rendered":"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Ti invitiamo a prendere visione della trascrizione della relazione di Alexander Valyalkin del 2019 \"Ottimizzazioni Go in VictoriaMetrics\"<\/strong><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/victoriametrics.com\/\">VictoriaMetrics<\/a><\/noindex> \u2014 un DBMS veloce e scalabile per la memorizzazione e l'elaborazione di dati sotto forma di serie temporali (una registrazione crea un tempo e un insieme di valori corrispondenti a quel tempo, ad esempio, ottenuti tramite interrogazioni periodiche dello stato dei sensori o raccolta di metriche).<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" 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>Ecco il link al video di questa relazione \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\">Diapositive<\/a><\/noindex><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/0073390d6dbcc6ab3e5305907ac6a229.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vi racconter\u00f2 un po' di me. Io sono Aleksandr Valjalkin. Ecco <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/valyala\">il mio account GitHub<\/a><\/noindex>. Sono appassionato di Go e ottimizzazione delle prestazioni. Ho scritto molte librerie utili e non. Iniziano tutte con <code>fast<\/code>, o con <code>quick<\/code> prefisso. <\/p>\n<p><\/p>\n<p>Attualmente lavoro su VictoriaMetrics. Che cos'\u00e8 e cosa faccio l\u00ec? Di questo parler\u00f2 in questa presentazione. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/c9194bc3fee1f615d838979bec12f7d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Il piano della relazione \u00e8 il seguente:<\/p>\n<p><\/p>\n<ul>\n<li>Inizialmente vi parler\u00f2 di cosa sia VictoriaMetrics. <\/li>\n<li>Poi vi dir\u00f2 cosa sono le serie temporali. <\/li>\n<li>Poi vi spiegher\u00f2 come funziona un database di serie temporali.<\/li>\n<li>Successivamente parler\u00f2 dell'architettura del database: da cosa \u00e8 composta.<\/li>\n<li>E infine passeremo alle ottimizzazioni presenti in VictoriaMetrics. Si tratta dell'ottimizzazione dell'indice invertito e dell'ottimizzazione per l'implementazione di bitset in Go.<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/3e0244b6b6fe952f660e4a094778921a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Qualcuno in aula sa cos'\u00e8 VictoriaMetrics? Incredibile, gi\u00e0 molte persone lo sanno. Questa \u00e8 una buona notizia. Per chi non lo sa: \u00e8 un database per le serie temporali. \u00c8 basato sull'architettura di ClickHouse, su alcuni dettagli dell'implementazione di ClickHouse. Ad esempio, come: MergeTree, calcoli paralleli su tutti i core della CPU disponibili e ottimizzazione delle prestazioni lavorando su blocchi di dati che vengono memorizzati nella cache della CPU. <\/p>\n<p><\/p>\n<p>VictoriaMetrics offre una compressione dei dati migliore rispetto ad altri database per serie temporali. <\/p>\n<p><\/p>\n<p>Si scalano verticalmente \u2014 cio\u00e8, puoi aggiungere un numero maggiore di processori, un numero maggiore di RAM su un singolo computer. VictoriaMetrics utilizzer\u00e0 efficacemente queste risorse disponibili e aumenter\u00e0 le prestazioni lineari.<\/p>\n<p><\/p>\n<p>Inoltre, VictoriaMetrics si scala orizzontalmente \u2014 cio\u00e8, puoi aggiungere nodi aggiuntivi al cluster di VictoriaMetrics, e le sue prestazioni cresceranno quasi linearmente.<\/p>\n<p><\/p>\n<p>Come avrete capito, VictoriaMetrics \u00e8 un database veloce, perch\u00e9 non posso scrivere di altri. E \u00e8 scritto in Go, perci\u00f2 ne parlo in questo meetup.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/0121c9baead722eb1fe22fb6f900d4ad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Chi sa cos'\u00e8 una serie temporale? Anche molte persone lo sanno. Una serie temporale \u00e8 una serie di coppie <code>(timestamp, valore)<\/code>, dove queste coppie sono ordinate nel tempo. Il valore \u00e8 un numero in virgola mobile \u2013 float64.<\/p>\n<p><\/p>\n<p>Ogni serie temporale \u00e8 univocamente identificata da una chiave. Di cosa \u00e8 composta questa chiave? \u00c8 composta da un insieme non vuoto di coppie chiave-valore. <\/p>\n<p><\/p>\n<p>Ecco un esempio di una serie temporale. La chiave di questa serie \u00e8 un elenco di coppie: <code>__name__=\"cpu_usage\"<\/code> \u2013 questo \u00e8 il nome della metrica, <code>instance=\"my-server\"<\/code> \u2013 questo \u00e8 il computer su cui \u00e8 stata raccolta questa metrica, <code>datacenter=\"us-east\"<\/code> \u2013 questo \u00e8 il data center dove si trova questo computer.<\/p>\n<p><\/p>\n<p>Abbiamo ottenuto il nome della serie temporale, composto da tre coppie chiave-valore. A questa chiave corrisponde un elenco di coppie <code>(timestamp, valore)<\/code>. <code>t1, t3, t3, ..., tN<\/code> \u2013 questi sono i timestamp, <code>10, 20, 12, ..., 15<\/code> \u2013 i valori corrispondenti. Questo \u00e8 il cpu-usage in quel momento per questa serie.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/76cab9db0263318d355ac607ceb7e0f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Dove possono essere utilizzate le serie temporali? Qualcuno ha idee? <\/p>\n<p><\/p>\n<ul>\n<li>In DevOps si possono misurare i valori di utilizzo della CPU, RAM, rete, rps, numero di errori, ecc. <\/li>\n<li>IoT \u2013 possiamo misurare temperatura, pressione, coordinate geografiche e altro.<\/li>\n<li>Inoltre, nel settore finanziario \u2013 possiamo monitorare i prezzi delle azioni e delle valute. <\/li>\n<li>In aggiunta, le serie temporali possono essere utilizzate per monitorare i processi produttivi nelle fabbriche. Abbiamo utenti che utilizzano VictoriaMetrics per monitorare turbine eoliche, per robot.<\/li>\n<li>Le serie temporali sono anche utili per raccogliere informazioni da sensori di diversi dispositivi. Ad esempio, per i motori; per misurare la pressione nei pneumatici; per misurare velocit\u00e0, distanza; per misurare il consumo di benzina, ecc.<\/li>\n<li>Le serie temporali possono anche essere utilizzate per il monitoraggio degli aerei. Ogni aereo ha una scatola nera che raccoglie serie temporali su diversi parametri di salute dell'aereo. Le serie temporali sono anche utilizzate nell'industria aerospaziale. <\/li>\n<li>In sanit\u00e0 \u2013 si tratta di pressione sanguigna, battito cardiaco, ecc.<\/li>\n<\/ul>\n<p><\/p>\n<p>Forse ci sono altre applicazioni che ho dimenticato, ma spero che abbiate capito che le serie temporali sono ampiamente utilizzate nel mondo moderno. E il loro utilizzo cresce di anno in anno.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/f1da29d372163751268b6a8f86836d04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A cosa serve un database per le serie temporali? Perch\u00e9 non \u00e8 possibile utilizzare un normale database relazionale per memorizzare le serie temporali?<\/p>\n<p><\/p>\n<p>Perch\u00e9 nei serie temporali di solito c'\u00e8 un grande volume di informazioni, che \u00e8 difficile da conservare e elaborare in normali database. Ecco perch\u00e9 sono apparse banche dati specializzate per le serie temporali. Questi database conservano efficacemente i punti <code>(timestamp, valore)<\/code> con una chiave specificata. Forniscono un'API per leggere i dati salvati tramite la chiave, una coppia chiave-valore alla volta, oppure pi\u00f9 coppie, oppure tramite regexp. Ad esempio, se vuoi trovare il carico della CPU di tutti i tuoi servizi nel data center in America, devi usare una pseudo-query del genere.<\/p>\n<p><\/p>\n<p>Di solito, i database per le serie temporali presentano linguaggi di query specializzati, poich\u00e9 SQL per le serie temporali non si adatta molto bene. Anche se ci sono database che supportano SQL, non funziona molto bene. Linguaggi di query come <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>si adattano meglio. Spero che qualcuno abbia sentito parlare almeno uno di questi linguaggi. PromQL, probabilmente, \u00e8 conosciuto da molti. Questo \u00e8 il linguaggio di query di Prometheus.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/59e5b6d82a2f7861077bc5fe34519ee4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ecco come appare l'architettura di un moderno database per le serie temporali, prendendo come esempio VictoriaMetrics.<\/p>\n<p><\/p>\n<p>\u00c8 composta da due parti. C'\u00e8 uno storage per l'indice invertito e uno storage per i valori delle serie temporali. Questi storage sono separati. <\/p>\n<p><\/p>\n<p>Quando arriva una nuova registrazione nel database, prima di tutto ci rivolgiamo all'indice invertito per trovare l'identificativo della serie temporale in base al set di <code>label=value<\/code> per questa metrica. Troviamo questo identificativo e salviamo il valore nello storage dei dati.<\/p>\n<p><\/p>\n<p>Quando arriva una richiesta di estrazione dei dati dal TSDB, prima di tutto andiamo nell'indice invertito. Recuperiamo tutti i <code>timeseries_ids<\/code> record che corrispondono al set dato. <code>label=value<\/code>E poi preleviamo tutti i dati necessari dallo storage dei dati, indicizzati per <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/eb9b36fa3c6de2188b97b56ad3839b2c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esaminiamo un esempio di come un database per serie temporali gestisce una richiesta di select in entrata.<\/p>\n<p><\/p>\n<ul>\n<li>Per prima cosa, recupera tutti gli <code>timeseries_ids<\/code> dall'indice invertito, che contengono le coppie specificate, <code>label=value<\/code>oppure soddisfano l'espressione regolare data.<\/li>\n<li>Poi recupera tutti i data points dallo storage dei dati in un intervallo di tempo specificato per quelli trovati. <code>timeseries_ids<\/code>.<\/li>\n<li>Dopo di che, il database esegue alcuni calcoli su questi data points, secondo la richiesta dell'utente. Dopodich\u00e9 restituisce la risposta.<\/li>\n<\/ul>\n<p><\/p>\n<p>In questa presentazione, parler\u00f2 della prima parte. Questa \u00e8 la ricerca <code>timeseries_ids<\/code> secondo l'indice invertito. Puoi poi guardare la seconda e la terza parte <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">sorgenti VictoriaMetrics<\/a><\/noindex>, oppure aspettare che prepari altre relazioni \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/3a7dab707dd1defb9bc674a342052a03.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Iniziamo con l'indice invertito. A molti potrebbe sembrare semplice. Chi sa cos'\u00e8 un indice invertito e come funziona? Oh, non ci sono pi\u00f9 molte persone. Proviamo a capire di cosa si tratta. <\/p>\n<p><\/p>\n<p>In realt\u00e0 \u00e8 molto semplice. \u00c8 solo un dizionario che mappa una chiave a un valore. Cos'\u00e8 una chiave? Questa coppia <code>label=value<\/code>, dove <code>label<\/code> e <code>value<\/code> \u00e8 costituita da stringhe. E i valori sono un insieme <code>timeseries_ids<\/code>, che include la coppia data <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>L'indice invertito consente di trovare rapidamente tutti <code>timeseries_ids<\/code>, che hanno le <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Consente anche di trovare rapidamente <code>timeseries_ids<\/code> serie temporali per diverse coppie <code>label=value<\/code>, oppure per coppie <code>label=regexp<\/code>. Come funziona? Trovando l'intersezione di un insieme <code>timeseries_ids<\/code> per ogni coppia <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/837a15a078e8c9422c721f3f072c0c09.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esaminiamo diverse implementazioni dell'indice invertito. Iniziamo con la pi\u00f9 semplice, l'implementazione naive. Si presenta cos\u00ec. <\/p>\n<p><\/p>\n<p>La funzione <code>getMetricIDs<\/code> ottiene un elenco di stringhe. Ogni stringa contiene <code>label=value<\/code>. Questa funzione restituisce un elenco <code>metricIDs<\/code>.<\/p>\n<p><\/p>\n<p>Come funziona? Abbiamo una variabile globale chiamata <code>invertedIndex<\/code>. \u00c8 un dizionario normale (<code>map<\/code>), che mappa la stringa su un slice di interi. La stringa contiene <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Implementazione della funzione: estrae <code>metricIDs<\/code> per il primo <code>label=value<\/code>, poi esaminiamo tutti gli altri <code>label=value<\/code>, estraendo <code>metricIDs<\/code> per loro. E chiamiamo la funzione <code>intersectInts<\/code>, di cui si parler\u00e0 in seguito. E questa funzione restituisce l'intersezione di queste liste.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/709beca79884a9693098781974c26f4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Come puoi vedere, l'implementazione dell'indice invertito non \u00e8 molto complessa. Ma \u00e8 un'implementazione naive. Quali sono i suoi svantaggi? Il principale svantaggio dell'implementazione naive \u00e8 che tale indice invertito \u00e8 memorizzato nella memoria volatile. Dopo il riavvio dell'applicazione, perdiamo questo indice. Non viene salvato su disco. Per un database, un tale indice invertito difficilmente sar\u00e0 utile.<\/p>\n<p><\/p>\n<p>Il secondo svantaggio \u00e8 anch'esso legato alla memoria. L'indice invertito deve adattarsi nella memoria volatile. Se supera la dimensione della memoria, \u00e8 evidente che otterremo un errore di out of memory. E il programma non funzioner\u00e0.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/495394168ac03aa8cbcf56ccad1cb2b8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Questo problema pu\u00f2 essere risolto con soluzioni pronte, come <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/google\/leveldb\">LevelDB<\/a><\/noindex>, oppure <noindex><a rel=\"nofollow\" href=\"https:\/\/rocksdb.org\/\">RocksDB<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>In sintesi, abbiamo bisogno di un database che consenta di eseguire rapidamente tre operazioni. <\/p>\n<p><\/p>\n<ul>\n<li>La prima operazione \u00e8 la registrazione <code>chiave-valore<\/code> in questo database. La esegue molto rapidamente, dove <code>chiave-valore<\/code> sono stringhe arbitrarie. <\/li>\n<li>La seconda operazione \u00e8 una ricerca rapida di un valore per chiave specificata.<\/li>\n<li>E la terza operazione \u00e8 una ricerca rapida di tutti i valori per un prefisso specificato. <\/li>\n<\/ul>\n<p><\/p>\n<p>LevelDB e RocksDB sono database sviluppati rispettivamente da Google e Facebook. Inizialmente \u00e8 stato realizzato LevelDB. Successivamente, i ragazzi di Facebook hanno preso LevelDB e hanno iniziato a migliorarla, creando RocksDB. Attualmente, quasi tutti i database interni di Facebook sono basati su RocksDB, incluso MySQL, che \u00e8 stato convertito in RocksDB. Lo hanno chiamato <noindex><a rel=\"nofollow\" href=\"http:\/\/myrocks.io\/\">MyRocks<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Un indice invertito pu\u00f2 essere implementato utilizzando LevelDB. Come si fa? Salviamo come chiave <code>label=value<\/code>. E come valore - l'identificativo di una serie temporale, dove \u00e8 presente la coppia <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Se abbiamo molte serie temporali con questa coppia <code>label=value<\/code>, ci saranno molte righe in questo database con la stessa chiave e valori diversi <code>timeseries_ids<\/code>. Per ottenere l'elenco di tutti i <code>timeseries_ids<\/code>, che iniziano con il dato <code>label=prefix<\/code>, facciamo una scansione di intervallo, per la quale questo database \u00e8 ottimizzato. Cio\u00e8, selezioniamo tutte le righe che iniziano con <code>label=prefix<\/code> e otteniamo i necessari <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/a40877ecb758ac3bc1979bcd568d7e83.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ecco un'implementazione approssimativa di come sarebbe su Go. Abbiamo un indice invertito. Questo \u00e8 LevelDB.<\/p>\n<p><\/p>\n<p>La funzione \u00e8 la stessa di quella per l'implementazione naive. Ripete quasi riga per riga l'implementazione naive. L'unico punto \u00e8 che invece di interagire con <code>map<\/code> interagiamo con l'indice invertito. Recuperiamo tutti i valori per la prima <code>label=value<\/code>. Poi scorriamo tutte le coppie rimanenti <code>label=value<\/code> e otteniamo i corrispondenti set di metricIDs per esse. Poi troviamo l'intersezione. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/de11b15286816ada04b22727ff78e43d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Sembra tutto a posto, ma in questa soluzione ci sono svantaggi. VictoriaMetrics inizialmente implementava l'indice invertito basato su LevelDB. Ma alla fine hanno dovuto rinunciare ad esso.<\/p>\n<p><\/p>\n<p>Perch\u00e9? Perch\u00e9 LevelDB \u00e8 pi\u00f9 lento dell'implementazione naive. Nell'implementazione naive, per una chiave specificata recuperiamo subito l'intero slice <code>metricIDs<\/code>. Questa \u00e8 un'operazione molto veloce: l'intero slice \u00e8 pronto per l'uso.<\/p>\n<p><\/p>\n<p>In LevelDB, ogni volta che chiamiamo la funzione <code>GetValues<\/code> , dobbiamo esaminare tutte le righe che iniziano con <code>label=value<\/code>. E per ogni riga recuperare il valore <code>timeseries_ids<\/code>. Da tali <code>timeseries_ids<\/code> costruire uno slice di essi. <code>timeseries_ids<\/code>. \u00c8 evidente che questo \u00e8 molto pi\u00f9 lento rispetto a una semplice chiamata a una mappa standard per chiave.<\/p>\n<p><\/p>\n<p>Il secondo svantaggio \u00e8 che LevelDB \u00e8 scritto in C. L'interazione con le funzioni C da Go non \u00e8 molto veloce. Ci vogliono centinaia di nanosecondi. Non \u00e8 molto veloce, perch\u00e9 rispetto a una chiamata normale di una funzione scritta in Go, che impiega 1-5 nanosecondi, la differenza di prestazioni \u00e8 anche di decine di volte. Per VictoriaMetrics, questo \u00e8 stato un difetto fatale \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/47cace825807b7b062064c2e790dc9d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Pertanto, ho scritto una mia implementazione dell'indice invertito. E l'ho chiamata <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">mergeset<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Mergeset si basa su una struttura dati MergeTree. Questa struttura dati \u00e8 stata presa in prestito da ClickHouse. \u00c8 ovvio che mergeset deve essere ottimizzato per una ricerca rapida <code>timeseries_ids<\/code> per una chiave specificata. Mergeset \u00e8 scritto interamente in Go. Puoi vedere <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">il codice sorgente di VictoriaMetrics su GitHub<\/a><\/noindex>. L'implementazione di mergeset si trova nella cartella <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">\/lib\/mergeset<\/a><\/noindex>. Puoi provare a capire cosa succede l\u00ec.<\/p>\n<p><\/p>\n<p>L'API di mergeset \u00e8 molto simile a LevelDB e RocksDB. Cio\u00e8, permette di memorizzare rapidamente nuovi record e di selezionare rapidamente i record per un prefisso specificato.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/845704ed36f70beb468907d22701af59.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Parleremo pi\u00f9 tardi degli svantaggi di mergeset. Ora parliamo dei problemi che si sono presentati con VictoriaMetrics in produzione durante l'implementazione dell'indice invertito.<\/p>\n<p><\/p>\n<p>Perch\u00e9 sono emersi?<\/p>\n<p><\/p>\n<p>La prima ragione \u00e8 l'alto tasso di turnover. Tradotto in italiano, significa un frequente cambio di serie temporali. Questo accade quando una serie temporale termina e inizia una nuova serie, oppure iniziano molte nuove serie temporali. E questo accade spesso.<\/p>\n<p><\/p>\n<p>La seconda ragione \u00e8 il grande numero di serie temporali. Inizialmente, quando il monitoraggio ha guadagnato popolarit\u00e0, il numero di serie temporali era ridotto. Ad esempio, per ogni computer \u00e8 necessario monitorare il carico della CPU, della memoria, della rete e del disco. 4 serie temporali per ogni computer. Se hai, ad esempio, 100 computer, hai 400 serie temporali. Questo \u00e8 molto poco. <\/p>\n<p><\/p>\n<p>Con il passare del tempo, le persone hanno trovato modi per misurare informazioni pi\u00f9 dettagliate. Ad esempio, misurare il carico non dell'intera CPU, ma di ciascun core della CPU separatamente. Se hai 40 core della CPU, allora hai 40 volte pi\u00f9 serie temporali per misurare il carico della CPU. <\/p>\n<p><\/p>\n<p>Ma non \u00e8 tutto. Ogni core del processore pu\u00f2 avere pi\u00f9 stati, come idle, quando \u00e8 inattivo. Cos\u00ec come il lavoro in user space, il lavoro in kernel space e altri stati. E ogni stato pu\u00f2 essere misurato come una serie temporale separata. Questo aumenta ulteriormente il numero di serie di 7-8 volte.<\/p>\n<p><\/p>\n<p>Da una singola metrica abbiamo ottenuto 40 x 8 = 320 metriche solo per un computer. Moltiplichiamo per 100, otteniamo 32.000 anzich\u00e9 400. <\/p>\n<p><\/p>\n<p>Poi \u00e8 arrivato Kubernetes. E questo ha ulteriormente peggiorato la situazione, perch\u00e9 in Kubernetes possono essere ospitati molti servizi diversi. Ogni servizio in Kubernetes \u00e8 composto da molti pod. E tutto questo deve essere monitorato. Inoltre, abbiamo un deployment costante di nuove versioni dei vostri servizi. Per ogni nuova versione \u00e8 necessario creare nuove serie temporali. Di conseguenza, il numero di serie temporali cresce esponenzialmente e ci troviamo di fronte al problema di un gran numero di serie temporali, chiamato high-cardinality. VictoriaMetrics affronta con successo questo problema rispetto ad altri database per serie temporali. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/c11b7ce6d3294ae420243f72636af4b9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esaminiamo pi\u00f9 nel dettaglio l'high churn rate. Cosa causa l'high churn rate in produzione? Perch\u00e9 alcuni valori delle etichette e dei tag cambiano costantemente.<\/p>\n<p><\/p>\n<p>Per esempio, prendiamo Kubernetes, in cui esiste il concetto <code>deployment<\/code>, ovvero quando viene rilasciata una nuova versione della tua applicazione. Gli sviluppatori di Kubernetes hanno deciso di aggiungere l'id del deployment nell'etichetta.<\/p>\n<p><\/p>\n<p>A cosa ha portato tutto ci\u00f2? Al fatto che ad ogni nuovo deployment tutte le vecchie serie temporali vengono interrotte e al loro posto vengono avviate nuove serie temporali con un nuovo valore dell'etichetta. <code>deployment_id<\/code>. Tali serie possono essere centinaia di migliaia e anche milioni.<\/p>\n<p><\/p>\n<p>L'aspetto importante di tutto questo \u00e8 che il numero totale di serie temporali cresce, ma il numero di serie temporali attualmente attive, per le quali arrivano dati, rimane costante. Questo stato \u00e8 chiamato high churn rate.<\/p>\n<p><\/p>\n<p>Il problema principale dell'high churn rate \u00e8 garantire una velocit\u00e0 di ricerca costante per tutte le serie temporali in base a un dato insieme di etichette in un certo intervallo di tempo. Di solito, questo intervallo di tempo \u00e8 l'ultima ora o l'ultimo giorno. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/ac18482b87cc37624d163b30c68cf7be.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Come possiamo risolvere questo problema? Ecco la prima opzione. Si tratta di suddividere l'indice invertito in parti indipendenti nel tempo. Cio\u00e8, un certo intervallo di tempo passa, smettiamo di lavorare con l'indice invertito attuale. E creiamo un nuovo indice invertito. Passa un altro intervallo di tempo, creiamo ancora un altro e cos\u00ec via. <\/p>\n<p><\/p>\n<p>E durante la selezione di questi indici invertiti, troviamo un insieme di indici invertiti che rientrano nell'intervallo fornito. E, di conseguenza, selezioniamo da l\u00ec gli id delle serie temporali. <\/p>\n<p><\/p>\n<p>Questo consente di risparmiare risorse, perch\u00e9 non dobbiamo esaminare parti che non rientrano nell'intervallo fornito. Cio\u00e8, di solito, se selezioniamo i dati per l'ultima ora, saltiamo le richieste per gli intervalli temporali precedenti. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/1afa3a88caa7423941ae3494b9cec706.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>C'\u00e8 un'altra opzione per risolvere questo problema. \u00c8 memorizzare un elenco separato di id delle serie temporali per ogni giorno, che sono apparsi in quel giorno.<\/p>\n<p><\/p>\n<p>Il vantaggio di questa soluzione rispetto alla soluzione precedente \u00e8 che non dupliciamo le informazioni sulle serie temporali che non scompaiono nel tempo. Rimangono costantemente e non cambiano. <\/p>\n<p><\/p>\n<p>Lo svantaggio \u00e8 che tale soluzione \u00e8 pi\u00f9 complessa da implementare e pi\u00f9 difficile da debug. E VictoriaMetrics ha scelto questa soluzione. \u00c8 stato storico. Questa soluzione si \u00e8 dimostrata anche abbastanza valida, rispetto alla precedente. Perch\u00e9 questa soluzione non \u00e8 stata implementata a causa della necessit\u00e0 di duplicare i dati in ogni partizione per le serie temporali che non cambiano, cio\u00e8 che non scompaiono nel tempo. VictoriaMetrics \u00e8 stata prima di tutto ottimizzata per il consumo di spazio su disco, e la precedente implementazione peggiorava il consumo di spazio su disco. Mentre questa implementazione \u00e8 pi\u00f9 adatta per minimizzare il consumo di spazio su disco, perci\u00f2 \u00e8 stata scelta. <\/p>\n<p><\/p>\n<p>Abbiamo dovuto combattere con essa. La lotta consisteva nel fatto che in questa implementazione dovevamo comunque selezionare un numero molto maggiore <code>timeseries_ids<\/code> per i dati, rispetto a quando l'indice invertito \u00e8 suddiviso nel tempo.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/5b10e61aaa803428ed3bfe31815f09d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Come abbiamo risolto questo problema? L'abbiamo risolta in modo originale, memorizzando pi\u00f9 identificatori di serie temporali in ogni registrazione dell'indice invertito invece di un singolo identificatore. Cio\u00e8, abbiamo una chiave <code>label=value<\/code>, che si verifica in ogni serie temporale. E ora stiamo salvando diversi <code>timeseries_ids<\/code> in un'unica registrazione.<\/p>\n<p><\/p>\n<p>Ecco un esempio. Prima avevamo N registrazioni, ora abbiamo un'unica registrazione, il cui prefisso \u00e8 lo stesso di tutte le altre. La registrazione precedente conteneva tutti gli id delle serie temporali. <\/p>\n<p><\/p>\n<p>Questo ha permesso di aumentare la velocit\u00e0 di scansione di un tale indice invertito fino a 10 volte. E ha ridotto il consumo di memoria per la cache, perch\u00e9 ora memorizziamo la stringa <code>label=value<\/code> solo una volta nella cache insieme a N volte. E questa stringa pu\u00f2 essere grande, se nei tag e nelle etichette hai stringhe lunghe che Kubernetes ama infilare.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/ae4f38a440203bad2d03cf2abd1faafe.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Un'altra opzione per accelerare la ricerca attraverso l'indice invertito \u00e8 lo sharding. Creare pi\u00f9 indici invertiti invece di uno solo e shardare i dati tra di essi in base a una chiave. Questo \u00e8 un insieme <code>chiave=valore<\/code> di coppie. Cio\u00e8, otteniamo diversi indici invertiti indipendenti, che possiamo interrogare in parallelo su pi\u00f9 processori. Le implementazioni precedenti funzionavano solo in modalit\u00e0 single-core, cio\u00e8 scansionando i dati su un solo core. Questa soluzione consente di scansionare i dati su pi\u00f9 core contemporaneamente, come solitamente fa ClickHouse. Questo \u00e8 ci\u00f2 che pianifichiamo di implementare.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/5ced6446efbf8ded8d7fb91694a999b0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ora torniamo ai nostri argomenti \u2013 alla funzione di intersezione <code>timeseries_ids<\/code>. Vediamo quali potrebbero essere le implementazioni. Questa funzione consente di trovare <code>timeseries_ids<\/code> per un set dato <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/073a155018a91120c6996c324772f9af.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>La prima opzione \u00e8 l'implementazione naif. Due cicli annidati. Ecco che riceviamo come input della funzione <code>intersectInts<\/code> due slice \u2014 <code>a<\/code> e <code>b<\/code>. In output dovrebbe restituirci l'intersezione di questi slice.<\/p>\n<p><\/p>\n<p>L'implementazione naif appare cos\u00ec. Scorriamo tutti i valori dallo slice <code>a<\/code>, all'interno di questo ciclo scorriamo tutti i valori dello slice <code>b<\/code>. E li confrontiamo. Se coincidono, significa che abbiamo trovato l'intersezione. E lo salviamo in <code>risultato<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/b89dcbb4f6d73377f9cf4f50169d6dd7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quali sono gli svantaggi? La complessit\u00e0 quadratica \u00e8 il suo principale svantaggio. Ad esempio, se hai dimensioni dello slice <code>a<\/code> e <code>b<\/code> di un milione, questa funzione non ti restituir\u00e0 mai una risposta. Perch\u00e9 le dovrebbero essere fatte un trilione di iterazioni, che \u00e8 davvero tante anche per i computer moderni.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/bad26b0092a2fd7fc813bcb79a9138ce.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>La seconda implementazione \u00e8 basata su una mappa. Creiamo una mappa. Inseriamo in questa mappa tutti i valori dello slice <code>a<\/code>. Poi facciamo un ciclo separato sullo slice <code>b<\/code>. E verifichiamo: esiste questo valore nello slice <code>b<\/code> in map. Se esiste, lo aggiungiamo al risultato. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/1e610acf641a9cfe4c80aac1fe26044c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quali sono i vantaggi? Il vantaggio \u00e8 che qui c'\u00e8 solo complessit\u00e0 lineare. Cio\u00e8, la funzione sar\u00e0 eseguita molto pi\u00f9 velocemente per dimensioni maggiori degli slice. Per uno slice di dimensione milione, questa funzione sar\u00e0 eseguita in 2 milioni di iterazioni, rispetto a un trilione di iterazioni, come nella funzione precedente.<\/p>\n<p><\/p>\n<p>E lo svantaggio \u00e8 che questa funzione richiede pi\u00f9 memoria per creare questa mappa.<\/p>\n<p><\/p>\n<p>Il secondo svantaggio \u00e8 il grande overhead per l'hashing. Questo svantaggio non \u00e8 molto ovvio. E per noi non \u00e8 stato molto ovvio, quindi all'inizio nell'implementazione dell'intersezione in VictoriaMetrics era utilizzata una map. Ma poi il profiling ha mostrato che la maggior parte del tempo di CPU veniva speso nella scrittura nella map e nel controllo dell'esistenza di un valore in questa map.<\/p>\n<p><\/p>\n<p>Perch\u00e9 in questi casi viene speso tempo di CPU? Perch\u00e9 in queste righe Go esegue un'operazione di hashing. Cio\u00e8 calcola l'hash della chiave per poi accedere all'indice specificato in HashMap. L'operazione di calcolo dell'hash viene eseguita in decine di nanosecondi. Questo \u00e8 lento per VictoriaMetrics.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/969f10d875d88998e958894ca28b1181.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ho deciso di implementare un bitset, ottimizzato appositamente per questo caso. Ecco come appare ora l'intersezione di due slice. Qui creiamo un bitset. Aggiungiamo gli elementi dal primo slice. Poi verifichiamo la presenza di questi elementi nel secondo slice. E li aggiungiamo al risultato. Cio\u00e8, quasi non si differenzia dall'esempio precedente. L'unica cosa che abbiamo qui \u00e8 stata la sostituzione dell'accesso alla mappa con funzioni personalizzate. <code>add<\/code> e <code>has<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/b4ec0753d30d9684868a031f605632c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A prima vista sembra che dovrebbe funzionare pi\u00f9 lentamente, se prima si usava una mappa standard e qui vengono chiamate ulteriori funzioni, ma il profiling mostra che questa cosa funziona 10 volte pi\u00f9 velocemente della mappa standard per il caso di VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Inoltre, utilizza molta meno memoria rispetto all'implementazione su map. Perch\u00e9 qui conserviamo bit invece di valori a otto byte.<\/p>\n<p><\/p>\n<p>Lo svantaggio di tale implementazione \u00e8 che non \u00e8 cos\u00ec ovvia, non \u00e8 banale. <\/p>\n<p><\/p>\n<p>Un altro svantaggio che molti potrebbero non notare \u00e8 che questa implementazione potrebbe funzionare male in alcuni casi. Cio\u00e8, \u00e8 ottimizzata per un caso specifico, per questo caso di intersezione degli ids delle serie temporali di VictoriaMetrics. Ci\u00f2 non significa che sia adatta a tutti i casi. Se viene usata in modo errato, non otterremo un incremento delle prestazioni, ma un errore di out of memory e un rallentamento delle prestazioni. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/94bd174afe43e67b3cf279dc7a1be17c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esaminiamo l'implementazione di questa struttura. Se vuoi darci un'occhiata, si trova nel codice sorgente di VictoriaMetrics, nella cartella <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/uint64set\">lib\/uint64set<\/a><\/noindex>. \u00c8 ottimizzata proprio per il caso di VictoriaMetrics, dove <code>timeseries_id<\/code> \u00e8 un valore a 64 bit, dove i primi 32 bit sono principalmente costanti e cambiano solo gli ultimi 32 bit.<\/p>\n<p><\/p>\n<p>Questa struttura dati non viene memorizzata su disco, funziona solo in memoria. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/e24ca65f860e2b04037e073f7acae0c1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ecco la sua API. Non \u00e8 molto complessa. L'API \u00e8 adattata proprio per questo caso d'uso di VictoriaMetrics. Cio\u00e8, qui non ci sono funzioni superflue. Qui ci sono funzioni che vengono utilizzate esplicitamente da VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Ci sono funzioni <code>add<\/code>, che aggiungono nuovi valori. C'\u00e8 una funzione <code>has<\/code>, che verifica nuovi valori. E c'\u00e8 una funzione <code>del<\/code>, che rimuove valori. C'\u00e8 una funzione ausiliaria <code>len<\/code>, che restituisce la dimensione dell'insieme. La funzione <code>clone<\/code> clona l'insieme. E la funzione <code>appendto<\/code> trasforma questo set in uno slice <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/874b04042d13f342d39f8f393e7fb78c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ecco come appare l'implementazione di questa struttura dati. Nel set ci sono due elementi:<\/p>\n<p><\/p>\n<ul>\n<li>\n<p><code>ItemsCount<\/code> \u2013 \u00e8 un campo ausiliario, per restituire rapidamente il numero di elementi nel set. Si potrebbe fare a meno di questo campo ausiliario, ma \u00e8 stato necessario aggiungerlo qui, perch\u00e9 VictoriaMetrics interroga spesso nei suoi algoritmi la lunghezza del bitset.<\/p>\n<p>\n<\/li>\n<li>\n<p>Il secondo campo \u00e8 <code>buckets<\/code>. Si tratta di uno slice della struttura <code>bucket32<\/code>. In ogni struttura \u00e8 memorizzato <code>hi<\/code> un campo. Questi sono i 32 bit superiori. E due slice \u2014 <code>b16his<\/code> e <code>buckets<\/code> da <code>bucket16<\/code> strutture. <\/p>\n<p>\n<\/li>\n<\/ul>\n<p><\/p>\n<p>Qui sono memorizzati i 16 bit superiori della seconda parte della struttura a 64 bit. E qui sono memorizzati i bitset per i 16 bit inferiori di ciascun byte. <\/p>\n<p><\/p>\n<p><code>Bucket64<\/code> \u00e8 composto da un array <code>uint64<\/code>. La lunghezza \u00e8 calcolata usando queste costanti. In un <code>bucket16<\/code> pu\u00f2 essere memorizzato al massimo <code>2^16=65536<\/code> bit. Se si divide per 8, si ottiene 8 kilobyte. Se si divide ancora per 8, si ottiene 1000 <code>uint64<\/code> valori. Cio\u00e8, <code>Bucket16<\/code> \u00e8 una struttura da 8 kilobyte. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/1cca4e6bfafa3408a3c95e0118e26d80.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esaminiamo come \u00e8 implementato uno dei metodi di questa struttura per aggiungere un nuovo valore. <\/p>\n<p><\/p>\n<p>Tutto inizia con <code>uint64<\/code> valori. Calcoliamo i 32 bit superiori, calcoliamo i 32 bit inferiori. Controlliamo tutti <code>buckets<\/code>. Confrontiamo i 32 bit superiori in ciascun bucket con il valore da aggiungere. E se sono uguali, chiamiamo la funzione <code>add<\/code> nella struttura b32 <code>buckets<\/code>. E vi aggiungiamo i 32 bit inferiori. E se ha restituito <code>true<\/code>, significa che abbiamo aggiunto quel valore l\u00ec e non avevamo gi\u00e0 quel valore. Se restituisce <code>false<\/code>, significa che quel valore era gi\u00e0 presente. Poi aumentiamo il numero di elementi nella struttura. <\/p>\n<p><\/p>\n<p>Se non abbiamo trovato il necessario <code>bucket<\/code> con il giusto valore hi, chiamiamo la funzione <code>addAlloc<\/code>, che alloca un nuovo <code>bucket<\/code>, aggiungendolo nella struttura del bucket.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/c5d1e5de767f47c7c40e01e12b6d0617.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Questa \u00e8 l'implementazione della funzione <code>b32.add<\/code>. \u00c8 simile all'implementazione precedente. Calcoliamo i 16 bit superiori e i 16 bit inferiori.<\/p>\n<p><\/p>\n<p>Poi controlliamo tutti i 16 bit superiori. Troviamo le corrispondenze. E in caso di corrispondenza chiamiamo il metodo add, che esamineremo nella pagina successiva per <code>bucket16<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/e98c4ef316f3a894fd7019c3c85696f7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ecco il livello pi\u00f9 basso, che deve essere ottimizzato al massimo. Calcoliamo per <code>uint64<\/code> id il valore nel bit slice, cos\u00ec come <code>bitmask<\/code>. Questa \u00e8 la maschera per il dato valore a 64 bit, con la quale possiamo controllare la presenza di questo bit o impostarlo. Verifichiamo la presenza di questo bit, lo impostiamo e restituiamo la presenza. Ecco un'implementazione che ci ha permesso di accelerare l'operazione di intersezione degli ids delle serie temporali di 10 volte rispetto alle normali mappe.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/4dfa5a6142829a3551be22d6c9c3ab92.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>In VictoriaMetrics, oltre a questa ottimizzazione, ci sono molte altre ottimizzazioni. Gran parte di queste ottimizzazioni non \u00e8 stata aggiunta a caso, ma dopo aver profilato il codice in produzione.<\/p>\n<p><\/p>\n<p>Questa \u00e8 la regola principale dell'ottimizzazione: non aggiungere ottimizzazione supponendo che qui ci sar\u00e0 un collo di bottiglia, perch\u00e9 potrebbe rivelarsi che l\u00ec non ci sia. L'ottimizzazione di solito peggiora la qualit\u00e0 del codice. Perci\u00f2 \u00e8 meglio ottimizzare solo dopo aver profilato e idealmente in produzione, affinch\u00e9 si tratti di dati reali. Chi \u00e8 interessato pu\u00f2 dare un'occhiata ai sorgenti di VictoriaMetrics e studiare altre ottimizzazioni che ci sono.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Ottimizzazioni Go in VictoriaMetrics. Alexander Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/7585c4dc782627bac5b649e6a55e2ffb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><em>Ho una domanda sul bitset. Sembra molto simile all'implementazione C++ vector bool, bitset ottimizzato. Avete preso l'implementazione da l\u00ec?<\/em><\/p>\n<p><\/p>\n<p>No, non \u00e8 da l\u00ec. Nella realizzazione di questo bitset mi sono basato sulla comprensione della struttura di questi ids timeseries, che vengono utilizzati in VictoriaMetrics. La loro struttura \u00e8 tale che i 32 bit superiori sono principalmente costanti. I 32 bit inferiori possono cambiare. Pi\u00f9 il bit \u00e8 basso, pi\u00f9 frequentemente pu\u00f2 cambiare. Pertanto, questa implementazione \u00e8 ottimizzata per questa particolare struttura dati. L'implementazione in C++, per quanto ne so, \u00e8 ottimizzata per il caso generale. Se si fa un'ottimizzazione per il caso generale, significa che non sar\u00e0 la pi\u00f9 ottimale per un caso specifico.<\/p>\n<p><\/p>\n<p>Ti consiglio di dare un'occhiata anche alla presentazione di Alexey Milovid. Circa un mese fa ha parlato delle ottimizzazioni in ClickHouse per specializzazioni specifiche. Spiega proprio che, in generale, l'implementazione in C++ o un'altra qualche implementazione \u00e8 progettata per funzionare bene in media. Pu\u00f2 funzionare peggio rispetto a un'implementazione specializzata per conoscenze specifiche, come nel nostro caso, quando sappiamo che i 32 bit superiori sono principalmente costanti.<\/p>\n<p><\/p>\n<p><em>Ho una seconda domanda. Qual \u00e8 la differenza fondamentale rispetto a InfluxDB?<\/em><\/p>\n<p><\/p>\n<p>Le differenze fondamentali sono molte. Se consideriamo le prestazioni e l'utilizzo della memoria, InfluxDB nei test mostra un consumo di memoria dieci volte superiore per i time series ad alta cardinalit\u00e0, quando ne hai molti, ad esempio milioni. Ad esempio, VictoriaMetrics consuma 1 GB per un milione di serie attive, mentre InfluxDB consuma 10 Gb. E questa \u00e8 una grande differenza. <\/p>\n<p><\/p>\n<p>La seconda differenza fondamentale \u00e8 che in InfluxDB ci sono linguaggi di query strani \u2013 Flux e InfluxQL. Non sono molto comodi per lavorare con le serie temporali rispetto a <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, che \u00e8 supportato in VictoriaMetrics. PromQL \u00e8 il linguaggio di query di Prometheus.<\/p>\n<p><\/p>\n<p>E un'altra differenza \u00e8 che InfluxDB ha un modello dati un po' strano, in cui ogni riga pu\u00f2 contenere pi\u00f9 fields con diverse combinazioni di tags. Queste righe sono ulteriormente divise in diverse tabelle. Questi ulteriori complessit\u00e0 complicano il lavoro successivo con questo database. \u00c8 difficile da mantenere e comprendere.<\/p>\n<p><\/p>\n<p>In VictoriaMetrics tutto \u00e8 molto pi\u00f9 semplice. Ogni serie temporale \u00e8 rappresentata come una coppia chiave-valore. Il valore \u00e8 un insieme di punti \u2013 <code>(timestamp, valore)<\/code>, mentre la chiave \u00e8 un insieme <code>label=value<\/code>. Non c'\u00e8 alcuna divisione tra fields e measurements. Questo ti consente di selezionare qualsiasi dato e poi combinarlo, sommarlo, sottrarlo, moltiplicarlo, dividerlo, a differenza di InfluxDB, dove i calcoli tra righe diverse non sono ancora implementati, per quanto ne so. Anche se lo fossero, sarebbe complicato, poich\u00e9 bisognerebbe scrivere un sacco di codice. <\/p>\n<p><\/p>\n<p><em>Ho una domanda di chiarimento. Ho capito correttamente che c'era qualche problema di cui parlavi, ossia che questo indice invertito non riesce a stare in memoria, quindi ci sono partizioni?<\/em><\/p>\n<p><\/p>\n<p>Inizialmente ho mostrato un'implementazione ingenua di un indice invertito su una mappa standard di Go. Tale implementazione non \u00e8 adatta per le basi di dati, poich\u00e9 questo indice invertito non viene salvato su disco, e la base di dati deve salvare su disco affinch\u00e9 dopo il riavvio questi dati rimangano accessibili. In questa implementazione, al riavvio dell'applicazione, il tuo indice invertito verr\u00e0 perso. E perderai l'accesso a tutti i dati poich\u00e9 non sarai in grado di trovarli. <\/p>\n<p><\/p>\n<p><em>Salve! Grazie per la presentazione! Mi chiamo Pavel. Vengo dall'azienda Wildberries. Ho alcune domande per te. Prima domanda. Cosa ne pensi, se avessi scelto un altro principio nella costruzione dell'architettura della tua applicazione e avessi partizionato i dati in base al tempo, forse saresti riuscito a incrociare i dati nella ricerca, basandoti solo sul fatto che in una partizione ci siano dati per un determinato intervallo di tempo, e quindi non dovresti preoccuparti del fatto che i tuoi dati siano disposti in maniera diversa? Seconda domanda: visto che stai implementando un algoritmo simile con bitset e altro, hai forse provato a utilizzare le istruzioni del processore? Potrebbe essere che tu abbia provato queste ottimizzazioni?<\/em><\/p>\n<p><\/p>\n<p>Risponder\u00f2 subito alla seconda domanda. Non siamo ancora arrivati a questo punto. Ma se sar\u00e0 necessario, ci arriveremo. E la prima, qual era la domanda?<\/p>\n<p><\/p>\n<p><em>Hai discusso di due scenari. E hai detto che hai scelto il secondo con un'implementazione pi\u00f9 complessa. E non hai preferito il primo, dove i dati sono partizionati in base al tempo.<\/em> <\/p>\n<p><\/p>\n<p>S\u00ec. Nel primo caso, il volume complessivo dell'indice sarebbe stato maggiore, perch\u00e9 in ogni partizione avremmo dovuto memorizzare dati duplicati per quelle serie temporali che si estendono attraverso tutte queste partizioni. E se hai un churn rate basso per le serie temporali, cio\u00e8 vengono utilizzate costantemente le stesse serie, nel primo caso avremmo perso molto di pi\u00f9 in termini di spazio su disco rispetto al secondo caso.<\/p>\n<p><\/p>\n<p>S\u00ec, la partizionamento per tempo \u00e8 una buona opzione. \u00c8 ci\u00f2 che utilizza Prometheus. Ma in Prometheus c'\u00e8 un altro svantaggio. Durante la fusione di questi pezzi di dati, deve mantenere in memoria le informazioni meta su tutte le etichette e le serie temporali. Pertanto, se i pezzi di dati che sta fondendo sono grandi, il consumo di memoria aumenta notevolmente durante la fusione, a differenza di VictoriaMetrics. Durante la fusione, VictoriaMetrics non consuma affatto memoria, consumando solo qualche kilobyte, indipendentemente dalle dimensioni dei pezzi di dati uniti.<\/p>\n<p><\/p>\n<p><em>L'algoritmo che stai usando utilizza memoria. In essa vengono contrassegnate le etichette delle serie temporali su cui ci sono valori. In questo modo controlli la presenza di coppie in un array di dati e in un altro. E capisci se c'\u00e8 stata un'intersezione o meno. Di solito, nei database vengono implementati cursori, iteratori che mantengono il loro stato attuale e scorrono attraverso dati ordinati, consentendoti di avere una complessit\u00e0 semplice in quelle operazioni.<\/em> <\/p>\n<p><\/p>\n<p>Perch\u00e9 non usiamo cursori per l'intersezione dei dati?<\/p>\n<p><\/p>\n<p><em>S\u00ec.<\/em> <\/p>\n<p><\/p>\n<p>Abbiamo in LevelDB o nel mergeset le righe ordinate. Possiamo usare un cursore per percorrerle e trovare l'intersezione. E perch\u00e9 non lo facciamo? Perch\u00e9 \u00e8 lento. Perch\u00e9 i cursori presuppongono che per ogni riga debba essere chiamata una funzione. La chiamata di una funzione richiede 5 nanosecondi. E se hai 100.000.000 righe, significa che spendiamo mezzo secondo solo per chiamare la funzione.<\/p>\n<p><\/p>\n<p><em>C'\u00e8 questo, s\u00ec. E l'ultima domanda che ho. La domanda, forse, suoner\u00e0 un po' strana. Perch\u00e9 al momento dell'arrivo dei dati non si possono calcolare tutte le aggregazioni necessarie e salvarle nella forma necessaria? Perch\u00e9 salvare enormi volumi in sistemi come VictoriaMetrics, ClickHouse, ecc., per poi spendere molto tempo su di essi?<\/em><\/p>\n<p><\/p>\n<p><em>Porter\u00f2 un esempio per chiarire. Supponiamo, come funziona un piccolo tachimetro giocattolo? Registra la distanza percorsa, aggiungendo continuamente un valore, e nell\u2019altro valore \u2013 il tempo. E divide. E ottiene la velocit\u00e0 media. Puoi fare pi\u00f9 o meno la stessa cosa. Sommare in corsa tutti i fatti necessari.<\/em><\/p>\n<p><\/p>\n<p>Bene, ho capito la domanda. Il tuo esempio \u00e8 valido. Se sai quali aggregati ti servono, questa \u00e8 la migliore implementazione. Ma il problema \u00e8 che le persone salvano queste metriche, alcuni dati in ClickHouse e non sanno ancora come li aggregano, filtrano in futuro, quindi devono salvare tutti i dati grezzi. Ma se sai che devi calcolare qualcosa di medio, perch\u00e9 non farlo, invece di conservare l\u00ec un sacco di valori grezzi? Ma questo vale solo se sai esattamente di cosa hai bisogno.<\/p>\n<p><\/p>\n<p>A proposito, le basi per la conservazione di serie temporali supportano il conteggio degli aggregati. Ad esempio, Prometheus supporta <noindex><a rel=\"nofollow\" href=\"https:\/\/prometheus.io\/docs\/prometheus\/latest\/configuration\/recording_rules\/\">le regole di registrazione<\/a><\/noindex>. Cio\u00e8, questo pu\u00f2 essere fatto, se sai quali aggregati ti serviranno. In VictoriaMetrics questo non \u00e8 ancora presente, ma di solito c'\u00e8 Prometheus prima, dove \u00e8 possibile farlo nelle regole di registrazione.<\/p>\n<p><\/p>\n<p>Ad esempio, nel lavoro precedente era necessario contare il numero di eventi in una finestra mobile nell'ultimo ora. Il problema \u00e8 stato che ho dovuto fare un'implementazione personalizzata in Go, cio\u00e8 un servizio per contare questa cosa. Questo servizio \u00e8 stato alla fine non banale, perch\u00e9 \u00e8 difficile da calcolare. L'implementazione pu\u00f2 essere semplice se devi contare alcuni aggregati su intervalli di tempo fissi. Se invece vuoi contare eventi in una finestra mobile, non \u00e8 cos\u00ec semplice come sembra. Penso che non sia ancora realizzato in ClickHouse o nelle basi di dati temporali, perch\u00e9 \u00e8 difficile da implementare.<\/p>\n<p><\/p>\n<p><em>E un'altra domanda. Abbiamo appena parlato di media, e mi sono ricordato che un tempo esisteva una cosa chiamata Graphite con backend Carbon. E lui sapeva diradare i dati vecchi, cio\u00e8 lasciare un punto al minuto, un punto all'ora e cos\u00ec via. In effetti, \u00e8 abbastanza comodo, se abbiamo bisogno di dati grezzi, per cos\u00ec dire, per un mese, mentre tutto il resto pu\u00f2 essere diradato. Ma Prometheus e VictoriaMetrics non supportano questa funzionalit\u00e0. \u00c8 prevista in futuro? Se no, perch\u00e9?<\/em><\/p>\n<p><\/p>\n<p>Grazie per la domanda. I nostri utenti la pongono periodicamente. Chiedono quando aggiungeremo il supporto al downsampling. Ci sono diversi problemi. Prima di tutto, ogni utente ha una propria interpretazione di <code>downsampling<\/code> : qualcuno desidera ottenere un valore arbitrario in un intervallo stabilito, qualcuno cerca i valori massimi, minimi, medi. Se nel tuo database scrivono dati molti sistemi, non puoi trattarli tutti allo stesso modo. Potrebbe risultare che per ogni sistema sia necessario utilizzare un downsampling differente. E questo \u00e8 complicato da realizzare.<\/p>\n<p><\/p>\n<p>In secondo luogo, VictoriaMetrics, come ClickHouse, \u00e8 ottimizzato per lavorare su grandi volumi di dati grezzi, quindi pu\u00f2 elaborare un miliardo di righe in meno di un secondo, se hai molti core nel tuo sistema. La scansione dei punti della serie temporale in VictoriaMetrics arriva a 50.000.000 di punti al secondo per core. E questa prestazione si scala con i core disponibili. Cio\u00e8, se hai 20 core, ad esempio, puoi scansionare un miliardo di punti al secondo. E questa caratteristica di VictoriaMetrics e ClickHouse riduce la necessit\u00e0 di downsampling.<\/p>\n<p><\/p>\n<p>Un'altra caratteristica \u00e8 che VictoriaMetrics comprime efficacemente questi dati. La compressione in media in produzione va da 0,4 a 0,8 byte per punto. Ogni punto corrisponde a timestamp + valore. E viene compresso a meno di un byte in media. <\/p>\n<p><\/p>\n<p><em>Sergei. Ho una domanda. Qual \u00e8 il minimo quant di tempo per la registrazione?<\/em><\/p>\n<p><\/p>\n<p>Un millisecondo. Recentemente abbiamo avuto una conversazione con altri sviluppatori di database per serie temporali. Per loro, il minimo quant di tempo \u00e8 di un secondo. Anche in Graphite, ad esempio, \u00e8 un secondo. In OpenTSDB \u00e8 anch'esso un secondo. In InfluxDB si tratta di precisione al nanosecondo. In VictoriaMetrics \u00e8 di un millisecondo, poich\u00e9 in Prometheus \u00e8 di un millisecondo. E VictoriaMetrics \u00e8 stata sviluppata inizialmente come remote storage per Prometheus. Ma ora pu\u00f2 salvare dati anche da altri sistemi. <\/p>\n<p><\/p>\n<p>La persona con cui ho parlato dice che hanno una precisione al secondo \u2014 per loro \u00e8 sufficiente, perch\u00e9 dipende dal tipo di dati che vengono conservati nel database della serie temporali. Se si tratta di dati DevOps o dati dell'infrastruttura, dove li raccogli con un intervallo di 30 secondi o un minuto, allora la precisione al secondo \u00e8 sufficiente, meno non \u00e8 necessario. Ma se raccogli questi dati da sistemi di trading ad alta frequenza, allora \u00e8 necessaria una precisione al nanosecondo.<\/p>\n<p><\/p>\n<p>La precisione millisecondo in VictoriaMetrics \u00e8 adatta sia per il caso DevOps, sia per la maggior parte dei casi che ho menzionato all'inizio della presentazione. L'unica cosa per cui potrebbe non essere adatta \u00e8 per i sistemi di trading ad alta frequenza.<\/p>\n<p><\/p>\n<p><em>Grazie! E un'altra domanda. Qual \u00e8 la compatibilit\u00e0 in PromQL?<\/em><\/p>\n<p><\/p>\n<p>Compatibilit\u00e0 retroattiva completa. VictoriaMetrics supporta completamente PromQL. Inoltre, aggiunge ulteriori funzionalit\u00e0 avanzate su PromQL, chiamata <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/wiki\/MetricsQL\">MetricsQL<\/a><\/noindex>. Per quanto riguarda queste funzionalit\u00e0 estese, c'\u00e8 una presentazione su YouTube. Ho parlato al Monitoring Meetup in primavera a San Pietroburgo.<\/p>\n<p><\/p>\n<p>Canale Telegram <noindex><a rel=\"nofollow\" href=\"https:\/\/t.me\/VictoriaMetrics_ru1\">VictoriaMetrics<\/a><\/noindex>.<\/p>\n<p class=\"for_users_only_msg\">Solo gli utenti registrati possono partecipare al sondaggio. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/auth\/login\/\">Accedi<\/a><\/noindex>, per favore.<\/p>\n<h2 class=\"default-block__polling-title\">Cosa ti impedisce di passare a VictoriaMetrics come archivio a lungo termine per Prometheus? (Scrivi nei commenti, lo aggiunger\u00f2 al sondaggio))<\/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>Non uso Prometheus5<\/p>\n<\/li>\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent\">28,6%<\/strong>Non sapevo di VictoriaMetrics2<\/p>\n<\/li>\n<\/ul>\n<p>    Hanno votato 7 utenti. Si sono astenuti 12 utenti.<br \/>\n<br \/>Fonte: <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\/it\/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=\"it_IT\" \/>\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\/it\/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\udd47Ottimizzazioni Go in VictoriaMetrics. Alexander Valiatkin | ProHoster","description":"Ti invitiamo a dare un'occhiata alla trascrizione della relazione di fine 2019 di Aleksandr Valjalkin \"Ottimizzazioni Go in VictoriaMetrics\"","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/80733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=80733"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/80733\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/80734"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=80733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=80733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=80733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}