{"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\/pt\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","title":{"rendered":"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Ofere\u00e7o a voc\u00ea a transcri\u00e7\u00e3o do relat\u00f3rio de final de 2019 de Alexander Valialkin \"Otimiza\u00e7\u00f5es Go no VictoriaMetrics\"<\/strong><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/victoriametrics.com\/\">VictoriaMetrics<\/a><\/noindex> \u2014 um SGBD r\u00e1pido e escal\u00e1vel para armazenar e processar dados na forma de s\u00e9ries temporais (um registro forma o tempo e um conjunto de valores correspondentes a esse tempo, por exemplo, obtidos atrav\u00e9s de sondagem peri\u00f3dica do estado de sensores ou coleta de m\u00e9tricas).<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" 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>Aqui est\u00e1 o link para o v\u00eddeo desta apresenta\u00e7\u00e3o \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\">Slides<\/a><\/noindex><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/0073390d6dbcc6ab3e5305907ac6a229.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vou falar um pouco sobre mim. Eu sou Alexander Valyalkin. Aqui est\u00e1 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/valyala\">minha conta no GitHub<\/a><\/noindex>. Sou fascinado por Go e otimiza\u00e7\u00e3o de desempenho. Escrevi muitas bibliotecas \u00fateis e algumas n\u00e3o t\u00e3o \u00fateis. Elas come\u00e7am com <code>fast<\/code>, ou com <code>quick<\/code> o prefixo. <\/p>\n<p><\/p>\n<p>Atualmente, estou trabalhando na VictoriaMetrics. O que \u00e9 isso e o que eu fa\u00e7o l\u00e1? Vou falar sobre isso nesta apresenta\u00e7\u00e3o. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/c9194bc3fee1f615d838979bec12f7d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>O plano da apresenta\u00e7\u00e3o \u00e9 o seguinte:<\/p>\n<p><\/p>\n<ul>\n<li>Primeiro, vou explicar o que \u00e9 a VictoriaMetrics. <\/li>\n<li>Depois, falarei sobre o que s\u00e3o s\u00e9ries temporais. <\/li>\n<li>Em seguida, explicarei como funciona um banco de dados de s\u00e9ries temporais.<\/li>\n<li>Depois falarei sobre a arquitetura do banco de dados: do que ela \u00e9 composta.<\/li>\n<li>E depois passaremos para as otimiza\u00e7\u00f5es que existem na VictoriaMetrics. Isso inclui a otimiza\u00e7\u00e3o do \u00edndice invertido e a otimiza\u00e7\u00e3o para a implementa\u00e7\u00e3o de bitset em Go.<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/3e0244b6b6fe952f660e4a094778921a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Algu\u00e9m na audi\u00eancia sabe o que \u00e9 VictoriaMetrics? Que bom, muitas pessoas j\u00e1 sabem. Essa \u00e9 uma boa not\u00edcia. Para aqueles que n\u00e3o sabem \u2013 \u00e9 um banco de dados para s\u00e9ries temporais. Ele \u00e9 baseado na arquitetura ClickHouse, com alguns detalhes da implementa\u00e7\u00e3o do ClickHouse. Por exemplo: MergeTree, processamento paralelo em todos os n\u00facleos de CPU dispon\u00edveis e otimiza\u00e7\u00e3o de desempenho atrav\u00e9s do trabalho com blocos de dados que s\u00e3o colocados no cache do processador. <\/p>\n<p><\/p>\n<p>A VictoriaMetrics oferece melhor compress\u00e3o de dados em compara\u00e7\u00e3o com outros bancos de dados para s\u00e9ries temporais. <\/p>\n<p><\/p>\n<p>Ela escala verticalmente \u2014 ou seja, voc\u00ea pode adicionar mais processadores, mais mem\u00f3ria RAM em um \u00fanico computador. A VictoriaMetrics aproveitar\u00e1 com sucesso esses recursos dispon\u00edveis e aumentar\u00e1 o desempenho linearmente.<\/p>\n<p><\/p>\n<p>Al\u00e9m disso, a VictoriaMetrics escala horizontalmente \u2014 ou seja, voc\u00ea pode adicionar n\u00f3s adicionais no cluster da VictoriaMetrics, e seu desempenho aumentar\u00e1 quase linearmente.<\/p>\n<p><\/p>\n<p>Como voc\u00ea deve ter adivinhado, a VictoriaMetrics \u00e9 um banco de dados r\u00e1pido, porque n\u00e3o posso escrever sobre outros. E \u00e9 escrita em Go, por isso estou falando sobre ela neste meetup.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/0121c9baead722eb1fe22fb6f900d4ad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quem sabe o que \u00e9 uma s\u00e9rie temporal? Muitas pessoas tamb\u00e9m sabem. Uma s\u00e9rie temporal \u00e9 uma sequ\u00eancia de pares <code>(timestamp, valor)<\/code>, onde esses pares est\u00e3o ordenados no tempo. O valor \u00e9 representado como um n\u00famero de ponto flutuante \u2013 float64.<\/p>\n<p><\/p>\n<p>Cada s\u00e9rie temporal \u00e9 identificada de maneira \u00fanica por uma chave. Do que consiste essa chave? Ela consiste em um conjunto n\u00e3o vazio de pares chave-valor. <\/p>\n<p><\/p>\n<p>Aqui est\u00e1 um exemplo de s\u00e9rie temporal. A chave desta s\u00e9rie \u00e9 uma lista de pares: <code>__name__=\"cpu_usage\"<\/code> \u2013 este \u00e9 o nome da m\u00e9trica, <code>instance=\"my-server\"<\/code> \u2013 \u00e9 o computador no qual essa m\u00e9trica foi coletada, <code>datacenter=\"us-east\"<\/code> \u2013 \u00e9 o data center onde esse computador est\u00e1 localizado.<\/p>\n<p><\/p>\n<p>Obtivemos o nome da s\u00e9rie temporal, consistindo em tr\u00eas pares chave-valor. Esta chave corresponde a uma lista de pares <code>(timestamp, value)<\/code>. <code>t1, t3, t3, ..., tN<\/code> \u2013 esses s\u00e3o os timestamps, <code>10, 20, 12, ..., 15<\/code> \u2013 com os valores correspondentes. Este \u00e9 o uso de CPU no momento para esta s\u00e9rie.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/76cab9db0263318d355ac607ceb7e0f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Onde as s\u00e9ries temporais podem ser usadas? Algu\u00e9m tem ideias? <\/p>\n<p><\/p>\n<ul>\n<li>No DevOps, pode-se medir as leituras de carga da CPU, RAM, rede, rps, n\u00famero de erros, etc. <\/li>\n<li>IoT \u2013 podemos medir temperatura, press\u00e3o, coordenadas geogr\u00e1ficas e mais.<\/li>\n<li>Al\u00e9m disso, nas finan\u00e7as \u2013 podemos monitorar pre\u00e7os de a\u00e7\u00f5es e moedas. <\/li>\n<li>Al\u00e9m disso, as s\u00e9ries temporais podem ser usadas para monitorar processos de produ\u00e7\u00e3o em f\u00e1bricas. Temos usu\u00e1rios que utilizam a VictoriaMetrics para monitoramento de turbinas e\u00f3licas, para rob\u00f4s.<\/li>\n<li>As s\u00e9ries temporais tamb\u00e9m s\u00e3o \u00fateis para coletar informa\u00e7\u00f5es de sensores de v\u00e1rios dispositivos. Por exemplo, para motores; para medir press\u00e3o em pneus; para medir velocidade, dist\u00e2ncia; para medir consumo de combust\u00edvel, etc.<\/li>\n<li>As s\u00e9ries temporais tamb\u00e9m podem ser usadas para monitorar avi\u00f5es. Cada avi\u00e3o possui uma caixa preta que coleta s\u00e9ries temporais de v\u00e1rios par\u00e2metros de sa\u00fade da aeronave. As s\u00e9ries temporais tamb\u00e9m s\u00e3o utilizadas na ind\u00fastria aeroespacial. <\/li>\n<li>Sa\u00fade \u2013 isso inclui press\u00e3o arterial, pulso, etc.<\/li>\n<\/ul>\n<p><\/p>\n<p>Pode haver outras aplica\u00e7\u00f5es que eu esqueci, mas espero que voc\u00ea tenha entendido que as s\u00e9ries temporais s\u00e3o amplamente utilizadas no mundo moderno. E seu uso continua a crescer a cada ano.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/f1da29d372163751268b6a8f86836d04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Para que uma base de dados para s\u00e9ries temporais \u00e9 necess\u00e1ria? Por que n\u00e3o se pode usar um banco de dados relacional comum para armazenar s\u00e9ries temporais?<\/p>\n<p><\/p>\n<p>Porque em s\u00e9ries temporais geralmente h\u00e1 um grande volume de informa\u00e7\u00f5es que \u00e9 dif\u00edcil de armazenar e processar em bancos de dados comuns. Por isso, surgiram bancos de dados especializados para s\u00e9ries temporais. Esses bancos armazenam eficientemente os pontos <code>(timestamp, value)<\/code> com a chave especificada. Eles fornecem uma API para ler os dados armazenados pela chave, por um par chave-valor, ou por v\u00e1rios desses pares, ou por regexp. Por exemplo, se voc\u00ea quiser encontrar a carga do processador de todos os seus servi\u00e7os no data center na Am\u00e9rica, voc\u00ea deve usar esta pseudo consulta.<\/p>\n<p><\/p>\n<p>Normalmente, os bancos de dados para s\u00e9ries temporais apresentam linguagens de consulta especializadas, porque SQL n\u00e3o se adapta muito bem a s\u00e9ries temporais. Embora existam bancos de dados que suportam SQL, ele n\u00e3o \u00e9 muito adequado. Linguagens de consulta como <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>s\u00e3o mais apropriadas. Espero que algu\u00e9m tenha ouvido pelo menos uma dessas linguagens. Sobre PromQL, provavelmente muitos j\u00e1 ouviram. Esta \u00e9 a linguagem de consultas do Prometheus.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/59e5b6d82a2f7861077bc5fe34519ee4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Aqui est\u00e1 como \u00e9 a arquitetura de um banco de dados moderno para s\u00e9ries temporais usando o exemplo do VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Ele consiste em duas partes. \u00c9 um armazenamento para \u00edndice invertido e um armazenamento para valores de s\u00e9ries temporais. Esses armazenamentos s\u00e3o separados. <\/p>\n<p><\/p>\n<p>Quando uma nova grava\u00e7\u00e3o chega ao banco de dados, primeiro consultamos o \u00edndice invertido para encontrar o identificador da s\u00e9rie temporal para o conjunto especificado de <code>label=value<\/code> para essa m\u00e9trica. Encontramos esse identificador e armazenamos o valor no armazenamento de dados.<\/p>\n<p><\/p>\n<p>Quando chega algum pedido de recupera\u00e7\u00e3o de dados do TSDB, primeiro vamos ao \u00edndice invertido. Recuperamos todos os <code>timeseries_ids<\/code> as grava\u00e7\u00f5es que correspondem ao conjunto dado. <code>label=value<\/code>E ent\u00e3o recuperamos todos os dados necess\u00e1rios do armazenamento de dados, indexados por <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/eb9b36fa3c6de2188b97b56ad3839b2c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos considerar um exemplo de como um banco de dados para s\u00e9ries temporais processa uma solicita\u00e7\u00e3o 'select' de entrada.<\/p>\n<p><\/p>\n<ul>\n<li>Primeiro, ele obt\u00e9m todos os <code>timeseries_ids<\/code> do \u00edndice invertido, que cont\u00eam os pares exigidos <code>label=value<\/code>, ou que satisfazem a uma express\u00e3o regular especificada.<\/li>\n<li>Em seguida, ele recupera todos os data points do armazenamento de dados em um intervalo de tempo definido para os encontrados. <code>timeseries_ids<\/code>.<\/li>\n<li>Depois disso, o banco de dados realiza alguns c\u00e1lculos sobre esses data points, de acordo com a solicita\u00e7\u00e3o do usu\u00e1rio. E, em seguida, retorna a resposta.<\/li>\n<\/ul>\n<p><\/p>\n<p>Nesta apresenta\u00e7\u00e3o, vou falar sobre a primeira parte. Esta \u00e9 a busca <code>timeseries_ids<\/code> pelo \u00edndice invertido. Voc\u00ea pode verificar a segunda e a terceira parte depois <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">c\u00f3digo-fonte do VictoriaMetrics<\/a><\/noindex>, ou esperar at\u00e9 que eu prepare outras palestras \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/3a7dab707dd1defb9bc674a342052a03.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos come\u00e7ar com o \u00edndice invertido. Muitos podem achar que \u00e9 simples. Quem sabe o que \u00e9 um \u00edndice invertido e como ele funciona? Oh, j\u00e1 n\u00e3o s\u00e3o tantas pessoas assim. Vamos tentar entender o que \u00e9 isso. <\/p>\n<p><\/p>\n<p>Na verdade, \u00e9 tudo simples. \u00c9 apenas um dicion\u00e1rio que mapeia uma chave a um valor. O que \u00e9 uma chave? Este par <code>label=value<\/code>, onde <code>label<\/code> e <code>valor<\/code> \u2014 s\u00e3o strings. E os valores s\u00e3o um conjunto <code>timeseries_ids<\/code>, que inclui o par dado <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>O \u00edndice invertido permite encontrar rapidamente todos <code>timeseries_ids<\/code>, que t\u00eam as especifica\u00e7\u00f5es dadas <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>E tamb\u00e9m permite encontrar rapidamente <code>timeseries_ids<\/code> s\u00e9ries temporais para v\u00e1rios pares <code>label=value<\/code>, ou para pares <code>label=regexp<\/code>. Como isso acontece? Atrav\u00e9s da interse\u00e7\u00e3o de conjuntos <code>timeseries_ids<\/code> para cada par <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/837a15a078e8c9422c721f3f072c0c09.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos considerar diferentes implementa\u00e7\u00f5es de um \u00edndice invertido. Come\u00e7aremos pela implementa\u00e7\u00e3o ing\u00eanua mais simples. Ela se parece assim. <\/p>\n<p><\/p>\n<p>Fun\u00e7\u00e3o <code>getMetricIDs<\/code> obt\u00e9m uma lista de strings. Cada string cont\u00e9m <code>label=value<\/code>. Esta fun\u00e7\u00e3o retorna uma lista <code>metricIDs<\/code>.<\/p>\n<p><\/p>\n<p>Como isso funciona? Aqui temos uma vari\u00e1vel global chamada <code>invertedIndex<\/code>. Este \u00e9 um dicion\u00e1rio comum (<code>mapa<\/code>), que mapeia uma string para um slice de inteiros. A string cont\u00e9m <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Implementa\u00e7\u00e3o da fun\u00e7\u00e3o: pegamos <code>metricIDs<\/code> para o primeiro <code>label=value<\/code>, depois percorremos todos os outros <code>label=value<\/code>, pegamos <code>metricIDs<\/code> para eles. E chamamos a fun\u00e7\u00e3o <code>intersectInts<\/code>, que ser\u00e1 discutida a seguir. E essa fun\u00e7\u00e3o retorna a interse\u00e7\u00e3o dessas listas.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/709beca79884a9693098781974c26f4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Como voc\u00ea pode ver, a implementa\u00e7\u00e3o do \u00edndice invertido n\u00e3o \u00e9 muito complicada. Mas esta \u00e9 uma implementa\u00e7\u00e3o ing\u00eanua. Quais s\u00e3o suas desvantagens? A principal desvantagem da implementa\u00e7\u00e3o ing\u00eanua \u00e9 que esse \u00edndice invertido \u00e9 armazenado na mem\u00f3ria RAM. Ap\u00f3s o rein\u00edcio do aplicativo, perdemos esse \u00edndice. N\u00e3o h\u00e1 salvamento deste \u00edndice no disco. Para um banco de dados, \u00e9 improv\u00e1vel que esse \u00edndice invertido sirva.<\/p>\n<p><\/p>\n<p>A segunda desvantagem tamb\u00e9m est\u00e1 relacionada \u00e0 mem\u00f3ria. O \u00edndice invertido deve caber na mem\u00f3ria RAM. Se ele exceder o tamanho da mem\u00f3ria RAM, claramente teremos \u2013 erro de falta de mem\u00f3ria. E o programa n\u00e3o funcionar\u00e1.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/495394168ac03aa8cbcf56ccad1cb2b8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esse problema pode ser resolvido com solu\u00e7\u00f5es prontas como <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/google\/leveldb\">LevelDB<\/a><\/noindex>, ou <noindex><a rel=\"nofollow\" href=\"https:\/\/rocksdb.org\/\">RocksDB<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Resumindo, precisamos de um banco de dados que permita realizar rapidamente tr\u00eas opera\u00e7\u00f5es. <\/p>\n<p><\/p>\n<ul>\n<li>A primeira opera\u00e7\u00e3o \u00e9 a grava\u00e7\u00e3o <code>chave-valor<\/code> nesta base. Ela faz isso muito rapidamente, onde <code>chave-valor<\/code> s\u00e3o linhas arbitr\u00e1rias. <\/li>\n<li>A segunda opera\u00e7\u00e3o \u00e9 a busca r\u00e1pida de um valor por uma chave especificada.<\/li>\n<li>E a terceira opera\u00e7\u00e3o \u00e9 a busca r\u00e1pida de todos os valores por um prefixo dado. <\/li>\n<\/ul>\n<p><\/p>\n<p>LevelDB e RocksDB s\u00e3o bancos de dados desenvolvidos no Google e no Facebook. Primeiro, veio o LevelDB. Depois, o pessoal do Facebook pegou o LevelDB e come\u00e7ou a melhor\u00e1-lo, criando o RocksDB. Atualmente, quase todos os bancos de dados internos do Facebook operam em RocksDB, incluindo a migra\u00e7\u00e3o do MySQL para RocksDB. Eles o chamaram de <noindex><a rel=\"nofollow\" href=\"http:\/\/myrocks.io\/\">MyRocks<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Um \u00edndice invertido pode ser implementado com o LevelDB. Como fazer isso? N\u00f3s salvamos como chave <code>label=value<\/code>. E como valor, o identificador da s\u00e9rie temporal onde a dupla est\u00e1 presente <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Se temos muitas s\u00e9ries temporais com essa dupla <code>label=value<\/code>, haver\u00e1 muitas linhas nesta base de dados com a mesma chave e diferentes <code>timeseries_ids<\/code>. Para obter uma lista de todos os <code>timeseries_ids<\/code>, que come\u00e7am com o dado <code>label=prefix<\/code>, fazemos uma varredura de intervalo, para a qual esta base de dados \u00e9 otimizada. Ou seja, selecionamos todas as linhas que come\u00e7am com <code>label=prefix<\/code> e obtemos os necess\u00e1rios <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/a40877ecb758ac3bc1979bcd568d7e83.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Aqui est\u00e1 uma implementa\u00e7\u00e3o aproximada de como ela seria em Go. Temos um \u00edndice invertido. Este \u00e9 o LevelDB.<\/p>\n<p><\/p>\n<p>A fun\u00e7\u00e3o \u00e9 a mesma que para a implementa\u00e7\u00e3o ing\u00eanua. Ela quase se repete linha a linha da implementa\u00e7\u00e3o ing\u00eanua. A \u00fanica diferen\u00e7a \u00e9 que, em vez de chamar <code>mapa<\/code> , chamamos o \u00edndice invertido. Recuperamos todos os valores para o primeiro <code>label=value<\/code>. Em seguida, percorremos todos os pares restantes <code>label=value<\/code> e recuperamos os conjuntos correspondentes de metricIDs para eles. Ent\u00e3o encontramos a interse\u00e7\u00e3o. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/de11b15286816ada04b22727ff78e43d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Parece que tudo est\u00e1 bem, mas essa solu\u00e7\u00e3o tem desvantagens. A VictoriaMetrics inicialmente implementou um \u00edndice invertido baseado no LevelDB. Mas no final, teve que abandon\u00e1-lo.<\/p>\n<p><\/p>\n<p>Por qu\u00ea? Porque o LevelDB \u00e9 mais lento do que a implementa\u00e7\u00e3o ing\u00eanua. Na implementa\u00e7\u00e3o ing\u00eanua, por uma chave especificada, obtemos imediatamente todo o slice <code>metricIDs<\/code>. Esta \u00e9 uma opera\u00e7\u00e3o muito r\u00e1pida - todo o slice est\u00e1 pronto para uso.<\/p>\n<p><\/p>\n<p>No LevelDB, no entanto, a cada chamada da fun\u00e7\u00e3o <code>GetValues<\/code> \u00e9 necess\u00e1rio percorrer todas as linhas que come\u00e7am com <code>label=value<\/code>. E para cada linha, recuperar o valor <code>timeseries_ids<\/code>. A partir dessas <code>timeseries_ids<\/code> construir um slice desses <code>timeseries_ids<\/code>. \u00c9 evidente que isso \u00e9 muito mais lento do que simplesmente acessar um map comum pela chave.<\/p>\n<p><\/p>\n<p>A segunda desvantagem \u00e9 que o LevelDB \u00e9 escrito em C. A chamada de fun\u00e7\u00f5es C a partir do Go n\u00e3o \u00e9 muito r\u00e1pida. Isso leva centenas de nanossegundos. N\u00e3o \u00e9 muito r\u00e1pido, porque em compara\u00e7\u00e3o com uma chamada de fun\u00e7\u00e3o escrita em Go, que leva de 1 a 5 nanossegundos, a diferen\u00e7a de desempenho \u00e9 de dezenas de vezes. Para o VictoriaMetrics, isso foi uma desvantagem fatal \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/47cace825807b7b062064c2e790dc9d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Portanto, eu escrevi minha pr\u00f3pria implementa\u00e7\u00e3o de \u00edndice invertido. E o nomeei <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">mergeset<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>O mergeset \u00e9 baseado na estrutura de dados MergeTree. Essa estrutura de dados foi emprestada do ClickHouse. \u00c9 evidente que o mergeset deve ser otimizado para busca r\u00e1pida <code>timeseries_ids<\/code> por uma chave espec\u00edfica. O mergeset \u00e9 completamente escrito em Go. Voc\u00ea pode conferir <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">o c\u00f3digo-fonte do VictoriaMetrics no GitHub<\/a><\/noindex>. A implementa\u00e7\u00e3o do mergeset est\u00e1 na pasta <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">\/lib\/mergeset<\/a><\/noindex>. Voc\u00ea pode tentar entender o que est\u00e1 acontecendo l\u00e1.<\/p>\n<p><\/p>\n<p>A API do mergeset \u00e9 muito parecida com a do LevelDB e RocksDB. Ou seja, ela permite salvar rapidamente novos registros e selecionar registros rapidamente por um prefixo espec\u00edfico.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/845704ed36f70beb468907d22701af59.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos falar sobre as desvantagens do mergeset mais tarde. Agora, vamos discutir os problemas que surgiram com o VictoriaMetrics em produ\u00e7\u00e3o durante a implementa\u00e7\u00e3o do \u00edndice invertido.<\/p>\n<p><\/p>\n<p>Por que eles ocorreram?<\/p>\n<p><\/p>\n<p>A primeira raz\u00e3o \u00e9 a alta taxa de churn. Em portugu\u00eas, isso significa uma troca frequente de s\u00e9ries temporais. Isso ocorre quando uma s\u00e9rie temporal termina e uma nova come\u00e7a, ou quando muitas novas s\u00e9ries temporais come\u00e7am. E isso acontece com frequ\u00eancia.<\/p>\n<p><\/p>\n<p>A segunda raz\u00e3o \u00e9 a grande quantidade de s\u00e9ries temporais. No in\u00edcio, quando a monitoria come\u00e7ou a ganhar popularidade, havia poucas s\u00e9ries temporais. Por exemplo, para cada computador, era necess\u00e1rio monitorar o uso da CPU, mem\u00f3ria, rede e disco. Quatro s\u00e9ries temporais para cada computador. Se voc\u00ea tiver, digamos, 100 computadores, ter\u00e1 400 s\u00e9ries temporais. Isso \u00e9 muito pouco. <\/p>\n<p><\/p>\n<p>Com o tempo, as pessoas descobriram que poderiam medir informa\u00e7\u00f5es mais detalhadas. Por exemplo, medir a carga n\u00e3o de toda a CPU, mas individualmente de cada n\u00facleo do processador. Se voc\u00ea tiver 40 n\u00facleos de processador, ent\u00e3o voc\u00ea ter\u00e1 40 vezes mais s\u00e9ries temporais para medir a carga da CPU. <\/p>\n<p><\/p>\n<p>Mas isso ainda n\u00e3o \u00e9 tudo. Cada n\u00facleo de processador pode ter v\u00e1rios estados, como o idle, quando fica ocioso. Tamb\u00e9m existem estados como opera\u00e7\u00e3o em user space, opera\u00e7\u00e3o em kernel space e outros. E cada um desses estados tamb\u00e9m pode ser medido como uma s\u00e9rie temporal separada. Isso aumenta ainda mais a quantidade de s\u00e9ries em 7-8 vezes.<\/p>\n<p><\/p>\n<p>A partir de uma m\u00e9trica, obtivemos 40 x 8 = 320 m\u00e9tricas apenas para um computador. Multiplicamos por 100 e obtemos 32.000 em vez de 400. <\/p>\n<p><\/p>\n<p>Depois, apareceu o Kubernetes. E isso complicou ainda mais, pois no Kubernetes podem ser hospedados muitos servi\u00e7os diferentes. Cada servi\u00e7o no Kubernetes consiste em v\u00e1rios pods. E tudo isso precisa ser monitorado. Al\u00e9m disso, temos um deployment constante de novas vers\u00f5es dos seus servi\u00e7os. Para cada nova vers\u00e3o, \u00e9 necess\u00e1rio criar novas s\u00e9ries temporais. No final, o n\u00famero de s\u00e9ries temporais cresce exponencialmente e nos deparamos com o problema de um grande n\u00famero de s\u00e9ries temporais, conhecido como high-cardinality. O VictoriaMetrics lida com isso com sucesso em compara\u00e7\u00e3o com outros bancos de dados para s\u00e9ries temporais. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/c11b7ce6d3294ae420243f72636af4b9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos explorar mais detalhadamente o high churn rate. O que causa um high churn rate em produ\u00e7\u00e3o? Porque alguns valores de labels e tags mudam constantemente.<\/p>\n<p><\/p>\n<p>Por exemplo, vamos considerar o Kubernetes, onde existe o conceito <code>deployment<\/code>, ou seja, quando uma nova vers\u00e3o do seu aplicativo \u00e9 lan\u00e7ada. Os desenvolvedores do Kubernetes decidiram, por algum motivo, adicionar o ID do deployment ao r\u00f3tulo.<\/p>\n<p><\/p>\n<p>O que isso resultou? O fato de que em cada novo deployment nossas antigas s\u00e9ries temporais s\u00e3o interrompidas, e, em seu lugar, novas s\u00e9ries temporais come\u00e7am com um novo valor de r\u00f3tulo. <code>deployment_id<\/code>. Essas s\u00e9ries podem ser centenas de milhares e at\u00e9 milh\u00f5es.<\/p>\n<p><\/p>\n<p>Uma caracter\u00edstica importante disso tudo \u00e9 que o n\u00famero total de s\u00e9ries temporais cresce, mas a quantidade de s\u00e9ries temporais que est\u00e3o ativas no momento, para as quais os dados est\u00e3o chegando, permanece constante. Esse estado \u00e9 chamado de high churn rate.<\/p>\n<p><\/p>\n<p>O principal problema do high churn rate \u00e9 garantir uma taxa constante de busca em todas as s\u00e9ries temporais de acordo com um conjunto determinado de labels em um determinado intervalo de tempo. Normalmente, esse intervalo de tempo \u00e9 a \u00faltima hora ou o \u00faltimo dia. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/ac18482b87cc37624d163b30c68cf7be.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Como resolver esse problema? Aqui est\u00e1 a primeira op\u00e7\u00e3o. Dividir o \u00edndice invertido em partes independentes de acordo com o tempo. Ou seja, ap\u00f3s um determinado intervalo de tempo, finalizamos o trabalho com o \u00edndice invertido atual e criamos um novo \u00edndice invertido. Ap\u00f3s mais um intervalo de tempo, criamos outro e assim sucessivamente. <\/p>\n<p><\/p>\n<p>E ao consultar esses \u00edndices invertidos, encontramos um conjunto de \u00edndices invertidos que cai dentro do intervalo especificado. E, consequentemente, selecionamos a partir deles os IDs das s\u00e9ries temporais. <\/p>\n<p><\/p>\n<p>Isso permite economizar recursos, pois n\u00e3o precisamos examinar partes que n\u00e3o caem dentro do intervalo especificado. Ou seja, normalmente, se estamos selecionando dados da \u00faltima hora, ignoramos as consultas para intervalos de tempo anteriores. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/1afa3a88caa7423941ae3494b9cec706.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>H\u00e1 outra op\u00e7\u00e3o para resolver esse problema. Armazenar uma lista separada de IDs de s\u00e9ries temporais para cada dia, que apareceram naquele dia.<\/p>\n<p><\/p>\n<p>A vantagem dessa solu\u00e7\u00e3o em rela\u00e7\u00e3o \u00e0 anterior \u00e9 que n\u00e3o duplicamos informa\u00e7\u00f5es sobre s\u00e9ries temporais que n\u00e3o desaparecem com o tempo. Elas est\u00e3o sempre presentes e n\u00e3o mudam. <\/p>\n<p><\/p>\n<p>O desvantagem \u00e9 que essa solu\u00e7\u00e3o \u00e9 mais complexa de implementar e mais dif\u00edcil de debugar. E a VictoriaMetrics escolheu essa solu\u00e7\u00e3o. Isso aconteceu historicamente. Essa solu\u00e7\u00e3o tamb\u00e9m se mostra razoavelmente boa em compara\u00e7\u00e3o com a anterior. Porque essa solu\u00e7\u00e3o n\u00e3o foi implementada devido \u00e0 necessidade de duplicar dados em cada parti\u00e7\u00e3o para s\u00e9ries temporais que n\u00e3o mudam, ou seja, que n\u00e3o desaparecem com o tempo. A VictoriaMetrics foi otimizada principalmente para o consumo de espa\u00e7o em disco, e a implementa\u00e7\u00e3o anterior piorava esse consumo. J\u00e1 essa implementa\u00e7\u00e3o \u00e9 mais adequada para minimizar o consumo de espa\u00e7o em disco, portanto, foi escolhida. <\/p>\n<p><\/p>\n<p>Tivemos que lidar com isso. A luta consistia em que, nesta implementa\u00e7\u00e3o, ainda precisamos selecionar uma quantidade muito maior <code>timeseries_ids<\/code> de dados do que quando o \u00edndice invertido \u00e9 dividido por tempo.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/5b10e61aaa803428ed3bfe31815f09d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Como resolvemos esse problema? N\u00f3s o resolvemos de maneira original \u2013 salvando v\u00e1rios identificadores de s\u00e9ries temporais em cada registro do \u00edndice invertido em vez de um \u00fanico identificador. Ou seja, temos uma chave <code>label=value<\/code>, que aparece em cada s\u00e9rie temporal. E agora estamos salvando v\u00e1rios <code>timeseries_ids<\/code> em um \u00fanico registro.<\/p>\n<p><\/p>\n<p>Aqui est\u00e1 um exemplo. Antes t\u00ednhamos N registros, agora temos um registro, cujo prefixo \u00e9 o mesmo de todos os outros. O registro anterior continha todos os IDs das s\u00e9ries temporais. <\/p>\n<p><\/p>\n<p>Isso aumentou a velocidade de varredura desse \u00edndice invertido em at\u00e9 10 vezes. E reduziu o consumo de mem\u00f3ria para o cache, porque agora armazenamos a string <code>label=value<\/code> apenas uma vez no cache junto com N vezes. E essa string pode ser grande, se voc\u00ea tiver longas linhas armazenadas em tags e r\u00f3tulos, que o Kubernetes gosta de introduzir.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/ae4f38a440203bad2d03cf2abd1faafe.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Outra op\u00e7\u00e3o para acelerar a busca em um \u00edndice invertido \u00e9 o particionamento. Criar v\u00e1rios \u00edndices invertidos em vez de um \u00fanico e particionar os dados entre eles por chave. Isso \u00e9 um conjunto <code>chave=valor<\/code> de pares. Ou seja, obtemos v\u00e1rios \u00edndices invertidos independentes, que podemos consultar em paralelo em v\u00e1rios processadores. Implementa\u00e7\u00f5es anteriores permitiam o funcionamento apenas em modo de um \u00fanico processador, ou seja, s\u00f3 pod\u00edamos escanear dados em um n\u00facleo. Esta solu\u00e7\u00e3o permite escanear dados em v\u00e1rios n\u00facleos, como o ClickHouse gosta de fazer. Isso n\u00f3s planejamos implementar.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/5ced6446efbf8ded8d7fb91694a999b0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Agora voltemos ao que interessa \u2013 \u00e0 fun\u00e7\u00e3o de interse\u00e7\u00e3o <code>timeseries_ids<\/code>. Vejamos quais podem ser as implementa\u00e7\u00f5es. Essa fun\u00e7\u00e3o permite encontrar <code>timeseries_ids<\/code> para um conjunto dado <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/073a155018a91120c6996c324772f9af.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A primeira alternativa \u00e9 a implementa\u00e7\u00e3o ing\u00eanua. Dois la\u00e7os aninhados. Recebemos como entrada da fun\u00e7\u00e3o <code>intersectInts<\/code> duas fatias \u2014 <code>a<\/code> e <code>b<\/code>. Como sa\u00edda, ela deve retornar a interse\u00e7\u00e3o dessas fatias.<\/p>\n<p><\/p>\n<p>A implementa\u00e7\u00e3o ing\u00eanua parece assim. Percorremos todos os valores da fatia <code>a<\/code>, dentro desse la\u00e7o, percorremos todos os valores da fatia <code>b<\/code>. E os comparamos. Se coincidirem, significa que encontramos a interse\u00e7\u00e3o. E a armazenamos em <code>result<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/b89dcbb4f6d73377f9cf4f50169d6dd7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quais s\u00e3o as desvantagens? A complexidade quadr\u00e1tica \u2014 essa \u00e9 sua principal desvantagem. Por exemplo, se suas fatias <code>a<\/code> e <code>b<\/code> t\u00eam um milh\u00e3o de elementos, essa fun\u00e7\u00e3o nunca retornar\u00e1 uma resposta. Porque precisaria fazer um trilh\u00e3o de itera\u00e7\u00f5es, o que \u00e9 muito mesmo para computadores modernos.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/bad26b0092a2fd7fc813bcb79a9138ce.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A segunda implementa\u00e7\u00e3o \u00e9 baseada em map. Criamos um map. Colocamos todos os valores da fatia <code>a<\/code>nesse map. Em seguida, percorremos com um la\u00e7o separado pela fatia <code>b<\/code>. E verificamos \u2014 existe esse valor na fatia <code>b<\/code> no mapa. Se existir, o adicionamos ao resultado. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/1e610acf641a9cfe4c80aac1fe26044c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quais s\u00e3o as vantagens? A vantagem \u00e9 que aqui temos apenas complexidade linear. Ou seja, a fun\u00e7\u00e3o ser\u00e1 executada muito mais rapidamente para tamanhos grandes de slices. Para um slice de um milh\u00e3o de elementos, essa fun\u00e7\u00e3o ser\u00e1 executada em 2 milh\u00f5es de itera\u00e7\u00f5es, em vez de um trilh\u00e3o de itera\u00e7\u00f5es, como na fun\u00e7\u00e3o anterior.<\/p>\n<p><\/p>\n<p>E o desvantagem \u00e9 que essa fun\u00e7\u00e3o requer mais mem\u00f3ria para criar esse mapa.<\/p>\n<p><\/p>\n<p>A segunda desvantagem \u00e9 o grande overhead de hashing. Essa desvantagem n\u00e3o \u00e9 muito \u00f3bvia. E para n\u00f3s tamb\u00e9m n\u00e3o era muito clara, ent\u00e3o, inicialmente, na implementa\u00e7\u00e3o do intersection no VictoriaMetrics foi usado um mapa. Mas, depois da profilagem, ficou claro que a maior parte do tempo do processador estava sendo gasto na grava\u00e7\u00e3o no mapa e na verifica\u00e7\u00e3o da presen\u00e7a de um valor nesse mapa.<\/p>\n<p><\/p>\n<p>Por que o tempo do processador \u00e9 gasto nesses pontos? Porque, nessas linhas, o Go realiza a opera\u00e7\u00e3o de hashing. Ou seja, ele calcula o hash da chave para ent\u00e3o acessar o \u00edndice correspondente no HashMap. A opera\u00e7\u00e3o de c\u00e1lculo do hash leva dezenas de nanossegundos. Isso \u00e9 lento para o VictoriaMetrics.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/969f10d875d88998e958894ca28b1181.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Decidi implementar um bitset, otimizado especificamente para este caso. Veja como agora \u00e9 feita a interse\u00e7\u00e3o de dois slices. Aqui, criamos um bitset. Adicionamos os elementos do primeiro slice. Em seguida, verificamos a presen\u00e7a desses elementos no segundo slice e os adicionamos ao resultado. Ou seja, \u00e9 praticamente id\u00eantico ao exemplo anterior. A \u00fanica diferen\u00e7a \u00e9 que substitu\u00edmos o acesso ao mapa por fun\u00e7\u00f5es personalizadas. <code>add<\/code> e <code>has<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/b4ec0753d30d9684868a031f605632c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00c0 primeira vista, parece que isso deveria funcionar mais devagar, se anteriormente foi usada uma mapa padr\u00e3o e agora estamos chamando algumas fun\u00e7\u00f5es, mas a profilagem mostra que essa abordagem funciona 10 vezes mais r\u00e1pido do que o mapa padr\u00e3o para o caso do VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Al\u00e9m disso, usa muito menos mem\u00f3ria em compara\u00e7\u00e3o com a implementa\u00e7\u00e3o no mapa. Porque estamos armazenando bits em vez de valores de oito bytes.<\/p>\n<p><\/p>\n<p>A desvantagem dessa implementa\u00e7\u00e3o \u00e9 que ela n\u00e3o \u00e9 t\u00e3o evidente, n\u00e3o trivial. <\/p>\n<p><\/p>\n<p>Outra desvantagem que muitos podem n\u00e3o perceber \u00e9 que essa implementa\u00e7\u00e3o pode falhar em alguns casos. Ou seja, ela \u00e9 otimizada para um caso espec\u00edfico, para este caso de interse\u00e7\u00e3o de IDs de s\u00e9ries temporais do VictoriaMetrics. Isso n\u00e3o significa que ela sirva para todos os casos. Se usada incorretamente, em vez de um aumento de desempenho, teremos um erro de 'out of memory' e uma diminui\u00e7\u00e3o do desempenho. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/94bd174afe43e67b3cf279dc7a1be17c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos considerar a implementa\u00e7\u00e3o dessa estrutura. Se voc\u00ea quiser conferir, ela est\u00e1 nos fontes do VictoriaMetrics, na pasta <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/uint64set\">lib\/uint64set<\/a><\/noindex>. Ela \u00e9 otimizada especificamente para o caso do VictoriaMetrics, onde <code>timeseries_id<\/code> \u00e9 um valor de 64 bits, onde os primeiros 32 bits s\u00e3o constantes e apenas os \u00faltimos 32 bits mudam.<\/p>\n<p><\/p>\n<p>Essa estrutura de dados n\u00e3o \u00e9 armazenada em disco; ela funciona apenas na mem\u00f3ria. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/e24ca65f860e2b04037e073f7acae0c1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Aqui est\u00e1 sua API. N\u00e3o \u00e9 muito complexa. A API foi ajustada especificamente para o exemplo de uso do VictoriaMetrics. Ou seja, n\u00e3o h\u00e1 fun\u00e7\u00f5es desnecess\u00e1rias. Aqui est\u00e3o as fun\u00e7\u00f5es claramente utilizadas pelo VictoriaMetrics.<\/p>\n<p><\/p>\n<p>H\u00e1 uma fun\u00e7\u00e3o <code>add<\/code>, que adiciona novos valores. H\u00e1 uma fun\u00e7\u00e3o <code>has<\/code>, que verifica novos valores. E h\u00e1 uma fun\u00e7\u00e3o <code>del<\/code>, que remove valores. H\u00e1 uma fun\u00e7\u00e3o auxiliar <code>len<\/code>, que retorna o tamanho do conjunto. A fun\u00e7\u00e3o <code>clone<\/code> clona o conjunto. E a fun\u00e7\u00e3o <code>appendto<\/code> transforma esse conjunto em slice. <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/874b04042d13f342d39f8f393e7fb78c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Aqui est\u00e1 como a implementa\u00e7\u00e3o dessa estrutura de dados se apresenta. No conjunto, h\u00e1 dois elementos:<\/p>\n<p><\/p>\n<ul>\n<li>\n<p><code>ItemsCount<\/code> \u2013 este \u00e9 um campo auxiliar para retornar rapidamente a quantidade de elementos no conjunto. Poder\u00edamos ter dispensado esse campo auxiliar, mas foi preciso adicion\u00e1-lo aqui porque o VictoriaMetrics frequentemente consulta em seus algoritmos o comprimento do bitset.<\/p>\n<p>\n<\/li>\n<li>\n<p>O segundo campo \u00e9 <code>buckets<\/code>. Este \u00e9 um slice da estrutura <code>bucket32<\/code>. Em cada estrutura, h\u00e1 um campo. Estes s\u00e3o os 32 bits mais altos. E dois slices \u2014 <code>hi<\/code> b16his <code>bucket16<\/code> e <code>buckets<\/code> do <code>estruturas.<\/code> Aqui s\u00e3o armazenados os 16 bits mais altos da segunda parte da estrutura de 64 bits. E aqui est\u00e3o os bitsets para os 16 bits mais baixos de cada byte. <\/p>\n<p>\n<\/li>\n<\/ul>\n<p><\/p>\n<p>Bucket64 <\/p>\n<p><\/p>\n<p><code>consiste em um array de<\/code> uint64. <code>O comprimento \u00e9 calculado usando essas constantes. Em um<\/code>pode armazenar no m\u00e1ximo <code>estruturas.<\/code> bits. Se dividirmos isso por 8, teremos 8 kilobytes. Se dividirmos mais uma vez por 8, teremos 1000 <code>2^16=65536<\/code> valores. Ou seja, <code>O comprimento \u00e9 calculado usando essas constantes. Em um<\/code> Bucket16 <code>\u00e9 a nossa estrutura de 8 kilobytes.<\/code> Vamos considerar como um dos m\u00e9todos dessa estrutura de adi\u00e7\u00e3o de um novo valor \u00e9 implementado. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/1cca4e6bfafa3408a3c95e0118e26d80.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Tudo come\u00e7a com <\/p>\n<p><\/p>\n<p>Tudo come\u00e7a com <code>O comprimento \u00e9 calculado usando essas constantes. Em um<\/code> valores. Calculamos os 32 bits superiores, calculamos os 32 bits inferiores. Passamos por todos <code>buckets<\/code>. Comparamos os 32 bits superiores em cada bucket com o valor adicionado. E se eles coincidirem, chamamos a fun\u00e7\u00e3o <code>add<\/code> na estrutura b32 <code>buckets<\/code>. E adicionamos os 32 bits inferiores. E se isso retornar <code>verdadeiro<\/code>, significa que adicionamos esse valor e n\u00e3o t\u00ednhamos esse valor antes. Se retornar <code>falso<\/code>, significa que esse valor j\u00e1 estava presente. Em seguida, aumentamos a contagem de elementos na estrutura. <\/p>\n<p><\/p>\n<p>Se n\u00e3o encontramos o necess\u00e1rio <code>bucket<\/code> com o valor hi correspondente, chamamos a fun\u00e7\u00e3o <code>addAlloc<\/code>, que aloca um novo <code>bucket<\/code>, adicionando-o \u00e0 estrutura do bucket.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/c5d1e5de767f47c7c40e01e12b6d0617.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esta \u00e9 a implementa\u00e7\u00e3o da fun\u00e7\u00e3o <code>b32.add<\/code>. Ela \u00e9 semelhante \u00e0 implementa\u00e7\u00e3o anterior. Calculamos os 16 bits superiores e os 16 bits inferiores.<\/p>\n<p><\/p>\n<p>Depois, passamos por todos os 16 bits superiores. Encontramos coincid\u00eancias. E ao encontrar uma coincid\u00eancia, chamamos o m\u00e9todo add, que veremos na pr\u00f3xima p\u00e1gina para <code>estruturas.<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/e98c4ef316f3a894fd7019c3c85696f7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>E aqui est\u00e1 o n\u00edvel mais baixo, que deve ser otimizado ao m\u00e1ximo. Calculamos para <code>O comprimento \u00e9 calculado usando essas constantes. Em um<\/code> o valor id no slice bit, assim como <code>bitmask<\/code>. Esta \u00e9 uma m\u00e1scara para esse valor de 64 bits, pela qual podemos verificar a presen\u00e7a desse bit ou defini-lo. Verificamos a presen\u00e7a desse bit, o definimos, e retornamos a presen\u00e7a. Esta \u00e9 a implementa\u00e7\u00e3o que conseguimos, que permitiu acelerar a opera\u00e7\u00e3o de interse\u00e7\u00e3o de IDs de s\u00e9ries temporais em 10 vezes em compara\u00e7\u00e3o com maps normais.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/4dfa5a6142829a3551be22d6c9c3ab92.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>No VictoriaMetrics, al\u00e9m dessa otimiza\u00e7\u00e3o, existem muitas outras otimiza\u00e7\u00f5es. A maioria dessas otimiza\u00e7\u00f5es foi adicionada n\u00e3o por acaso, mas ap\u00f3s o profiling do c\u00f3digo em produ\u00e7\u00e3o.<\/p>\n<p><\/p>\n<p>Esta \u00e9 a principal regra de otimiza\u00e7\u00e3o - n\u00e3o adicionar otimiza\u00e7\u00f5es com base na suposi\u00e7\u00e3o de que haver\u00e1 um gargalo, porque pode resultar que l\u00e1 n\u00e3o haja um gargalo. A otimiza\u00e7\u00e3o geralmente degrada a qualidade do c\u00f3digo. Portanto, \u00e9 prudente otimizar apenas ap\u00f3s o profiling e, de prefer\u00eancia, em produ\u00e7\u00e3o, para que se baseie em dados reais. Para quem estiver interessado, voc\u00ea pode analisar o c\u00f3digo-fonte do VictoriaMetrics e estudar outras otimiza\u00e7\u00f5es que l\u00e1 est\u00e3o.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics. Alexander Valyalkin\" src=\"\/wp-content\/uploads\/2020\/05\/7585c4dc782627bac5b649e6a55e2ffb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><em>Eu tenho uma pergunta sobre o bitset. Parece muito com a implementa\u00e7\u00e3o do vetor bool do C++, um bitset otimizado. Voc\u00eas pegaram a implementa\u00e7\u00e3o de l\u00e1?<\/em><\/p>\n<p><\/p>\n<p>N\u00e3o, n\u00e3o \u00e9 da\u00ed. Ao implementar este bitset, baseei-me no conhecimento da estrutura desses ids de s\u00e9ries temporais que s\u00e3o utilizados no VictoriaMetrics. E a estrutura deles \u00e9 tal que os 32 bits superiores s\u00e3o principalmente constantes. Os 32 bits inferiores podem mudar. Quanto mais baixo o bit, mais frequentemente ele pode mudar. Portanto, esta implementa\u00e7\u00e3o \u00e9 otimizada especificamente para essa estrutura de dados. A implementa\u00e7\u00e3o em C++, at\u00e9 onde sei, \u00e9 otimizada para o caso geral. Se a otimiza\u00e7\u00e3o for feita para o caso geral, isso significa que ela n\u00e3o ser\u00e1 a mais otimizada para um caso espec\u00edfico.<\/p>\n<p><\/p>\n<p>Recomendo que voc\u00ea tamb\u00e9m assista \u00e0 palestra de Alexey Milovid. Ele falou sobre otimiza\u00e7\u00f5es no ClickHouse para especializa\u00e7\u00f5es espec\u00edficas h\u00e1 cerca de um m\u00eas. Ele menciona que, em geral, a implementa\u00e7\u00e3o em C++ ou qualquer outra implementa\u00e7\u00e3o \u00e9 ajustada para funcionar bem em m\u00e9dia. Ela pode funcionar pior do que uma implementa\u00e7\u00e3o especializada, como a nossa, quando sabemos que os 32 bits superiores s\u00e3o em grande parte constantes.<\/p>\n<p><\/p>\n<p><em>Eu tenho uma segunda pergunta. Qual \u00e9 a diferen\u00e7a fundamental em rela\u00e7\u00e3o ao InfluxDB?<\/em><\/p>\n<p><\/p>\n<p>Existem muitas diferen\u00e7as fundamentais. Em termos de desempenho e consumo de mem\u00f3ria, o InfluxDB em testes mostra 10 vezes mais consumo de mem\u00f3ria para s\u00e9ries temporais de alta cardinalidade, quando voc\u00ea tem muitas, por exemplo, milh\u00f5es. Por exemplo, o VictoriaMetrics consome 1 GB para um milh\u00e3o de s\u00e9ries ativas, enquanto o InfluxDB consome 10 GB. E essa \u00e9 uma grande diferen\u00e7a. <\/p>\n<p><\/p>\n<p>A segunda diferen\u00e7a fundamental \u00e9 que o InfluxDB tem linguagens de consulta estranhas \u2013 Flux e InfluxQL. Elas n\u00e3o s\u00e3o muito convenientes para trabalhar com s\u00e9ries temporais em compara\u00e7\u00e3o com <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, que \u00e9 suportado no VictoriaMetrics. PromQL \u00e9 a linguagem de consultas do Prometheus.<\/p>\n<p><\/p>\n<p>E mais uma diferen\u00e7a \u00e9 que o InfluxDB tem um modelo de dados um pouco peculiar, onde cada linha pode armazenar v\u00e1rios campos com conjuntos diferentes de tags. Essas linhas s\u00e3o ainda divididas em v\u00e1rias tabelas. Essas complica\u00e7\u00f5es adicionais dificultam o trabalho subsequente com esse banco de dados. Ele \u00e9 dif\u00edcil de manter e entender.<\/p>\n<p><\/p>\n<p>No VictoriaMetrics, tudo \u00e9 muito mais simples. Cada s\u00e9rie temporal representa uma chave-valor. O valor \u00e9 um conjunto de pontos \u2013 <code>(timestamp, value)<\/code>, e a chave \u00e9 um conjunto <code>label=value<\/code>. N\u00e3o h\u00e1 separa\u00e7\u00e3o entre campos e medi\u00e7\u00f5es. Isso permite que voc\u00ea escolha qualquer dado e, em seguida, combine, some, subtraia, multiplique e divida, ao contr\u00e1rio do InfluxDB, onde c\u00e1lculos entre diferentes s\u00e9ries ainda n\u00e3o est\u00e3o implementados, at\u00e9 onde sei. Mesmo que estivessem implementados, seriam complicados, exigindo muitos c\u00f3digos. <\/p>\n<p><\/p>\n<p><em>Eu tenho uma pergunta para esclarecer. Entendi corretamente que houve algum problema, do qual voc\u00ea falou, que esse \u00edndice invertido n\u00e3o cabe na mem\u00f3ria, ent\u00e3o o particionamento ocorre?<\/em><\/p>\n<p><\/p>\n<p>No in\u00edcio, eu mostrei uma implementa\u00e7\u00e3o ing\u00eanua de um \u00edndice invertido em um map padr\u00e3o do Go. Essa implementa\u00e7\u00e3o n\u00e3o \u00e9 adequada para bancos de dados, pois o \u00edndice invertido n\u00e3o \u00e9 salvo em disco, e um banco de dados deve armazenar em disco para que esses dados permane\u00e7am acess\u00edveis ap\u00f3s uma reinicializa\u00e7\u00e3o. Nesta implementa\u00e7\u00e3o, ao reiniciar o aplicativo, o seu \u00edndice invertido desaparecer\u00e1. E voc\u00ea perder\u00e1 acesso a todos os dados, pois n\u00e3o conseguir\u00e1 encontr\u00e1-los. <\/p>\n<p><\/p>\n<p><em>Ol\u00e1! Obrigado pela apresenta\u00e7\u00e3o! Meu nome \u00e9 Pavel. Sou da empresa Wildberries. Tenho algumas perguntas para voc\u00ea. Primeira pergunta. O que voc\u00ea acha, se voc\u00ea tivesse escolhido outro princ\u00edpio ao construir a arquitetura do seu aplicativo e particionado os dados por tempo, seria poss\u00edvel cruzar dados durante a pesquisa, baseando-se apenas no fato de que em uma parti\u00e7\u00e3o est\u00e3o os dados de um \u00fanico intervalo de tempo? Ou seja, de um \u00fanico intervalo de tempo, e voc\u00ea n\u00e3o teria que se preocupar com os fragmentos estarem espalhados de forma diferente? Segunda pergunta \u2014 uma vez que voc\u00ea est\u00e1 implementando um algoritmo semelhante com bitset e tudo mais, talvez voc\u00ea tenha tentado usar instru\u00e7\u00f5es do processador? Talvez voc\u00ea tenha experimentado essas otimiza\u00e7\u00f5es?<\/em><\/p>\n<p><\/p>\n<p>Vou responder a segunda imediatamente. Ainda n\u00e3o chegamos nisso. Mas se for necess\u00e1rio, chegaremos. E a primeira, qual era a pergunta?<\/p>\n<p><\/p>\n<p><em>Voc\u00ea discutiu dois cen\u00e1rios. E disse que escolheu o segundo com uma implementa\u00e7\u00e3o mais complexa. E n\u00e3o preferiu o primeiro, onde os dados s\u00e3o particionados por tempo.<\/em> <\/p>\n<p><\/p>\n<p>Sim. No primeiro caso, o volume total do \u00edndice seria maior, porque em cada parti\u00e7\u00e3o ter\u00edamos que armazenar dados duplicados para as s\u00e9ries temporais que se estendem por todas essas parti\u00e7\u00f5es. E se a taxa de churn das s\u00e9ries temporais for baixa, ou seja, se as mesmas s\u00e9ries forem constantemente utilizadas, no primeiro caso perder\u00edamos muito mais em volume de espa\u00e7o em disco em compara\u00e7\u00e3o com o segundo caso.<\/p>\n<p><\/p>\n<p>Nesse caso, sim, a parti\u00e7\u00e3o por tempo \u00e9 uma boa op\u00e7\u00e3o. O Prometheus utiliza isso. Mas o Prometheus tem outra desvantagem. Ao mesclar esses blocos de dados, ele precisa manter na mem\u00f3ria as meta-informa\u00e7\u00f5es de todos os r\u00f3tulos e s\u00e9ries temporais. Portanto, se os blocos de dados que ele mescla forem grandes, o consumo de mem\u00f3ria aumenta consideravelmente durante a mesclagem, diferentemente do VictoriaMetrics. Na mesclagem, o VictoriaMetrics n\u00e3o consome mem\u00f3ria; consome apenas alguns kilobytes, independentemente do tamanho dos blocos de dados que est\u00e3o sendo combinados.<\/p>\n<p><\/p>\n<p><em>O algoritmo que voc\u00ea usa consome mem\u00f3ria. Nele, s\u00e3o registradas as etiquetas das s\u00e9ries temporais nas quais h\u00e1 valores. E assim, voc\u00ea verifica a presen\u00e7a correspondente em um conjunto de dados e em outro. E entende se houve interse\u00e7\u00e3o ou n\u00e3o. Normalmente, os bancos de dados implementam cursores, iteradores, que armazenam seu estado atual e percorrem os dados ordenados, o que resulta em uma complexidade simples para essas opera\u00e7\u00f5es.<\/em> <\/p>\n<p><\/p>\n<p>Por que n\u00e3o usamos cursores para a interse\u00e7\u00e3o de dados?<\/p>\n<p><\/p>\n<p><em>Sim.<\/em> <\/p>\n<p><\/p>\n<p>Temos no LevelDB ou no mergeset as linhas devidamente ordenadas. Podemos usar um cursor para encontrar a interse\u00e7\u00e3o. Mas por que n\u00e3o usamos? Porque isso \u00e9 lento. Porque os cursores pressup\u00f5em que para cada linha precisamos chamar uma fun\u00e7\u00e3o. A chamada da fun\u00e7\u00e3o \u00e9 de 5 nanosegundos. E se voc\u00ea tiver 100.000.000 de linhas, voc\u00ea acaba gastando meio segundo apenas com as chamadas de fun\u00e7\u00e3o.<\/p>\n<p><\/p>\n<p><em>Isso existe, sim. E a minha \u00faltima pergunta. A pergunta pode parecer um pouco estranha. Por que no momento da chegada dos dados n\u00e3o podemos calcular todos os agregados necess\u00e1rios e salv\u00e1-los em um formato desejado? Por que armazenar enormes volumes em sistemas como VictoriaMetrics, ClickHouse, etc., para depois gastar muito tempo com eles?<\/em><\/p>\n<p><\/p>\n<p><em>Vou dar um exemplo para ficar mais claro. Suponha que voc\u00ea tenha um pequeno veloc\u00edmetro de brinquedo. Ele registra a dist\u00e2ncia que voc\u00ea percorreu, sempre somando em uma medida, e em outra \u2013 o tempo. E divide. E obt\u00e9m a velocidade m\u00e9dia. Voc\u00ea pode fazer algo semelhante. Juntar todos os fatos necess\u00e1rios \u00e0 medida que avan\u00e7a.<\/em><\/p>\n<p><\/p>\n<p>Certo, entendi a pergunta. Seu exemplo \u00e9 v\u00e1lido. Se voc\u00ea sabe quais agregados precisa, essa \u00e9 a melhor abordagem. Mas o problema \u00e9 que as pessoas armazenam essas m\u00e9tricas, alguns dados no ClickHouse e n\u00e3o sabem ainda como v\u00e3o agreg\u00e1-los, filtr\u00e1-los no futuro, ent\u00e3o acabam salvando todos os dados brutos. Mas se voc\u00ea sabe que precisa calcular algo m\u00e9dio, por que n\u00e3o calcular, em vez de guardar um monte de valores brutos? Mas isso s\u00f3 se voc\u00ea souber exatamente o que precisa.<\/p>\n<p><\/p>\n<p>Ali\u00e1s, bancos para armazenar s\u00e9ries temporais suportam o c\u00e1lculo de agregados. Por exemplo, o Prometheus suporta <noindex><a rel=\"nofollow\" href=\"https:\/\/prometheus.io\/docs\/prometheus\/latest\/configuration\/recording_rules\/\">regras de grava\u00e7\u00e3o<\/a><\/noindex>. Ou seja, \u00e9 poss\u00edvel fazer isso se voc\u00ea souber quais agregados v\u00e3o ser necess\u00e1rios. No VictoriaMetrics isso ainda n\u00e3o existe, mas geralmente coloca-se o Prometheus na frente, onde isso pode ser feito nas regras de grava\u00e7\u00e3o.<\/p>\n<p><\/p>\n<p>Por exemplo, no trabalho anterior eu precisava contar o n\u00famero de eventos em uma janela deslizante no \u00faltimo hora. O problema \u00e9 que tive que fazer uma implementa\u00e7\u00e3o personalizada em Go, ou seja, um servi\u00e7o para contar isso. Esse servi\u00e7o acabou sendo n\u00e3o trivial, porque \u00e9 complicado de calcular. A implementa\u00e7\u00e3o pode ser simples se voc\u00ea precisa calcular alguns agregados em intervalos de tempo fixos. Mas se voc\u00ea quer contar eventos em uma janela deslizante, n\u00e3o \u00e9 t\u00e3o simples quanto parece. Eu acho que isso ainda n\u00e3o est\u00e1 implementado no ClickHouse ou em bancos de dados de s\u00e9ries temporais, porque \u00e9 complicado de realizar.<\/p>\n<p><\/p>\n<p><em>E mais uma pergunta. Est\u00e1vamos falando sobre m\u00e9dias, e me lembrei que havia algo chamado Graphite com backend Carbon. E ele conseguia filtrar dados antigos, ou seja, deixar um ponto por minuto, um ponto por hora, etc. Isso \u00e9 bastante \u00fatil, se precisamos de dados brutos, digamos, de um m\u00eas, e tudo o mais pode ser filtrado. Mas o Prometheus e o VictoriaMetrics n\u00e3o suportam essa funcionalidade. Est\u00e1 previsto implementar? Se n\u00e3o, por qu\u00ea?<\/em><\/p>\n<p><\/p>\n<p>Obrigado pela pergunta. Nossos usu\u00e1rios a fazem de vez em quando. Eles perguntam quando iremos adicionar suporte para downsampling. Aqui existem alguns problemas. Primeiro, cada usu\u00e1rio entende por <code>downsampling<\/code> algo diferente: alguns querem obter qualquer ponto arbitr\u00e1rio em um intervalo especificado, enquanto outros desejam valores m\u00e1ximos, m\u00ednimos ou m\u00e9dios. Se muitos sistemas est\u00e3o escrevendo dados em sua base, n\u00e3o se pode tratar todos da mesma forma. Pode ser que para cada sistema seja necess\u00e1rio usar um downsampling diferente. E isso \u00e9 complicado de implementar.<\/p>\n<p><\/p>\n<p>E segundo \u2013 o VictoriaMetrics, assim como o ClickHouse, \u00e9 otimizado para trabalhar com grandes volumes de dados brutos, portanto, pode processar um bilh\u00e3o de linhas em menos de um segundo, se voc\u00ea tiver muitos n\u00facleos em seu sistema. A varredura de pontos de s\u00e9ries temporais no VictoriaMetrics \u00e9 de 50.000.000 pontos por segundo por n\u00facleo. E esse desempenho escalona-se nos n\u00facleos dispon\u00edveis. Ou seja, se voc\u00ea tiver, por exemplo, 20 n\u00facleos, poder\u00e1 varrer um bilh\u00e3o de pontos por segundo. E essa caracter\u00edstica do VictoriaMetrics e do ClickHouse reduz a necessidade de downsampling.<\/p>\n<p><\/p>\n<p>Outra caracter\u00edstica \u00e9 que o VictoriaMetrics comprime esses dados de forma eficaz. A compress\u00e3o m\u00e9dia na produ\u00e7\u00e3o \u00e9 de 0,4 a 0,8 bytes por ponto. Cada ponto consiste em um timestamp + valor. E ele \u00e9 comprimido a menos de um byte, em m\u00e9dia. <\/p>\n<p><\/p>\n<p><em>Serguei. Eu tenho uma pergunta. Qual \u00e9 o menor intervalo de tempo de grava\u00e7\u00e3o?<\/em><\/p>\n<p><\/p>\n<p>Uma milissegundo. Recentemente, tivemos uma conversa com outros desenvolvedores de bancos de dados para s\u00e9ries temporais. O intervalor m\u00ednimo deles \u00e9 de um segundo. No Graphite, por exemplo, tamb\u00e9m \u00e9 um segundo. No OpenTSDB tamb\u00e9m \u00e9 um segundo. No InfluxDB \u2013 precis\u00e3o em nanossegundos. No VictoriaMetrics \u2013 um milissegundo, porque no Prometheus \u00e9 um milissegundo. E o VictoriaMetrics foi inicialmente desenvolvido como um armazenamento remoto para o Prometheus. Mas agora ele pode armazenar dados de outros sistemas tamb\u00e9m. <\/p>\n<p><\/p>\n<p>A pessoa com quem eu conversei diz que eles t\u00eam precis\u00e3o em segundos \u2014 isso \u00e9 suficiente para eles, pois depende do tipo de dados que est\u00e3o sendo salvo na base de s\u00e9ries temporais. Se forem dados de DevOps ou dados da infraestrutura, onde voc\u00ea os coleta com um intervalo de 30 segundos a um minuto, ent\u00e3o a precis\u00e3o em segundos \u00e9 suficiente, menos que isso n\u00e3o \u00e9 necess\u00e1rio. Mas se voc\u00ea estiver coletando esses dados de sistemas de trading de alta frequ\u00eancia, ent\u00e3o uma precis\u00e3o em nanossegundos \u00e9 necess\u00e1ria.<\/p>\n<p><\/p>\n<p>A precis\u00e3o em milissegundos no VictoriaMetrics \u00e9 adequada tanto para casos de DevOps quanto para a maioria dos casos que mencionei no in\u00edcio da apresenta\u00e7\u00e3o. A \u00fanica situa\u00e7\u00e3o em que pode n\u00e3o ser adequada \u00e9 para sistemas de negocia\u00e7\u00e3o de alta frequ\u00eancia.<\/p>\n<p><\/p>\n<p><em>Obrigado! E mais uma pergunta. Qual \u00e9 a compatibilidade com o PromQL?<\/em><\/p>\n<p><\/p>\n<p>Compatibilidade total. VictoriaMetrics suporta completamente o PromQL. Al\u00e9m disso, adiciona funcionalidades avan\u00e7adas ao PromQL, chamadas de <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/wiki\/MetricsQL\">MetricsQL<\/a><\/noindex>. Quanto a essas funcionalidades avan\u00e7adas, h\u00e1 uma apresenta\u00e7\u00e3o no YouTube. Eu falei sobre isso no Monitoring Meetup na primavera em S\u00e3o Petersburgo.<\/p>\n<p><\/p>\n<p>Canal do Telegram <noindex><a rel=\"nofollow\" href=\"https:\/\/t.me\/VictoriaMetrics_ru1\">VictoriaMetrics<\/a><\/noindex>.<\/p>\n<p class=\"for_users_only_msg\">Apenas usu\u00e1rios registrados podem participar da pesquisa. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/auth\/login\/\">Entre<\/a><\/noindex>, por favor.<\/p>\n<h2 class=\"default-block__polling-title\">O que impede voc\u00ea de migrar para o VictoriaMetrics como armazenamento de longo prazo para o Prometheus? (Deixe um coment\u00e1rio, eu adicionarei na enquete))<\/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>N\u00e3o uso Prometheus5<\/p>\n<\/li>\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent\">28,6%<\/strong>N\u00e3o conhecia o VictoriaMetrics2<\/p>\n<\/li>\n<\/ul>\n<p>    7 usu\u00e1rios votaram. 12 usu\u00e1rios se abstiveram.<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.3 - 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\/pt\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"pt_PT\" \/>\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\/pt\/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\udd47Otimiza\u00e7\u00f5es em Go no VictoriaMetrics. Alexander Valyalkin | ProHoster","description":"Convido voc\u00ea a conhecer a transcri\u00e7\u00e3o da apresenta\u00e7\u00e3o de 2019 de Alexander Valyalkin \"Otimiza\u00e7\u00f5es do Go no VictoriaMetrics\"","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/80733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=80733"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/80733\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/80734"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=80733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=80733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=80733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}