{"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\/fr\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","title":{"rendered":"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Je vous invite \u00e0 consulter la transcription du rapport de fin 2019 d'Alexandre Valialkin intitul\u00e9 \u00ab Optimisations Go dans VictoriaMetrics \u00bb<\/strong><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/victoriametrics.com\/\">VictoriaMetrics<\/a><\/noindex> \u2014 une base de donn\u00e9es rapide et \u00e9volutive pour stocker et traiter des donn\u00e9es sous forme de s\u00e9ries temporelles (l'enregistrement forme le temps et un ensemble de valeurs correspondantes \u00e0 ce moment, par exemple, obtenues par un sondage p\u00e9riodique de l'\u00e9tat des capteurs ou la collecte de m\u00e9triques).<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre 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>Voici le lien vers la vid\u00e9o de cette pr\u00e9sentation \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\">Diapositives<\/a><\/noindex><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/0073390d6dbcc6ab3e5305907ac6a229.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Permettez-moi de me pr\u00e9senter. Je suis Alexandre Valialkin. Voici <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/valyala\">mon compte GitHub<\/a><\/noindex>. Je suis passionn\u00e9 par Go et l'optimisation des performances. J'ai \u00e9crit de nombreuses biblioth\u00e8ques utiles et moins utiles. Elles commencent soit par <code>fast<\/code>, ou par <code>quick<\/code> comme pr\u00e9fixe. <\/p>\n<p><\/p>\n<p>Actuellement, je travaille sur VictoriaMetrics. Qu'est-ce que c'est et quel est mon r\u00f4le l\u00e0-dedans ? Je vais en parler dans cette pr\u00e9sentation. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/c9194bc3fee1f615d838979bec12f7d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Le plan de la pr\u00e9sentation est le suivant :<\/p>\n<p><\/p>\n<ul>\n<li>Dans un premier temps, je vais vous expliquer ce qu'est VictoriaMetrics. <\/li>\n<li>Ensuite, je vais vous parler des s\u00e9ries temporelles. <\/li>\n<li>Puis je vais expliquer comment fonctionne la base de donn\u00e9es des s\u00e9ries temporelles.<\/li>\n<li>Ensuite, je parlerai de l'architecture de la base de donn\u00e9es : de quoi elle est compos\u00e9e.<\/li>\n<li>Et enfin, nous aborderons les optimisations pr\u00e9sentes dans VictoriaMetrics. Cela inclut l'optimisation de l'index invers\u00e9 et l'optimisation de l'impl\u00e9mentation du bitset en Go.<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/3e0244b6b6fe952f660e4a094778921a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Est-ce que quelqu'un dans le public conna\u00eet VictoriaMetrics ? Incroyable, d\u00e9j\u00e0 beaucoup de gens savent. C'est une bonne nouvelle. Pour ceux qui ne savent pas, c'est une base de donn\u00e9es pour les s\u00e9ries temporelles. Elle est bas\u00e9e sur l'architecture de ClickHouse, sur certains d\u00e9tails d'impl\u00e9mentation de ClickHouse. Par exemple, sur des \u00e9l\u00e9ments tels que : MergeTree, le calcul parall\u00e8le sur tous les c\u0153urs de processeur disponibles et l'optimisation des performances gr\u00e2ce \u00e0 la gestion de blocs de donn\u00e9es qui sont plac\u00e9s dans le cache du processeur. <\/p>\n<p><\/p>\n<p>VictoriaMetrics offre la meilleure compression des donn\u00e9es par rapport aux autres bases de donn\u00e9es pour les s\u00e9ries temporelles. <\/p>\n<p><\/p>\n<p>Elle se scale verticalement, c'est-\u00e0-dire que vous pouvez ajouter davantage de processeurs, plus de m\u00e9moire vive sur une seule machine. VictoriaMetrics utilisera efficacement ces ressources disponibles et am\u00e9liorera la performance lin\u00e9aire.<\/p>\n<p><\/p>\n<p>VictoriaMetrics se scale \u00e9galement horizontalement, c'est-\u00e0-dire que vous pouvez ajouter des n\u0153uds suppl\u00e9mentaires au cluster VictoriaMetrics, et sa performance augmentera presque lin\u00e9airement.<\/p>\n<p><\/p>\n<p>Comme vous l'avez devin\u00e9, VictoriaMetrics est une base de donn\u00e9es rapide, car je ne peux pas \u00e9crire sur d'autres. Et elle est d\u00e9velopp\u00e9e en Go, c'est pourquoi j'en parle lors de ce meetup.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/0121c9baead722eb1fe22fb6f900d4ad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Qui sait ce qu'est une s\u00e9rie temporelle ? Beaucoup de gens le savent aussi. Une s\u00e9rie temporelle est une s\u00e9rie de paires <code>(timestamp, valeur)<\/code>, o\u00f9 ces paires sont tri\u00e9es par temps. La valeur est un nombre \u00e0 virgule flottante \u2013 float64.<\/p>\n<p><\/p>\n<p>Chaque s\u00e9rie temporelle est identifi\u00e9e de mani\u00e8re unique par une cl\u00e9. De quoi se compose cette cl\u00e9 ? Elle se compose d'un ensemble non vide de paires cl\u00e9-valeur. <\/p>\n<p><\/p>\n<p>Voici un exemple de s\u00e9rie temporelle. La cl\u00e9 de cette s\u00e9rie est une liste de paires : <code>__name__=\"cpu_usage\"<\/code> \u2013 c'est le nom de la m\u00e9trique, <code>instance=\"my-server\"<\/code> \u2013 c'est l'ordinateur sur lequel cette m\u00e9trique a \u00e9t\u00e9 collect\u00e9e, <code>datacenter=\"us-east\"<\/code> \u2013 c'est le centre de donn\u00e9es o\u00f9 se trouve cet ordinateur.<\/p>\n<p><\/p>\n<p>Nous avons obtenu un nom de s\u00e9rie temporelle compos\u00e9 de trois paires cl\u00e9-valeur. Cette cl\u00e9 correspond \u00e0 une liste de paires <code>(timestamp, value)<\/code>. <code>t1, t3, t3, ..., tN<\/code> \u2013 ce sont des timestamps, <code>10, 20, 12, ..., 15<\/code> \u2013 avec les valeurs correspondantes. C'est l'utilisation du CPU \u00e0 ce moment donn\u00e9 pour cette s\u00e9rie.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/76cab9db0263318d355ac607ceb7e0f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>O\u00f9 peuvent \u00eatre utilis\u00e9es les s\u00e9ries temporelles ? Quelqu'un a-t-il des id\u00e9es ? <\/p>\n<p><\/p>\n<ul>\n<li>Dans DevOps, nous pouvons mesurer les lectures de charge CPU, RAM, r\u00e9seau, rps, nombre d'erreurs, etc. <\/li>\n<li>IoT \u2013 nous pouvons mesurer la temp\u00e9rature, la pression, les coordonn\u00e9es g\u00e9ographiques et d'autres \u00e9l\u00e9ments.<\/li>\n<li>\u00c9galement pour les finances \u2013 nous pouvons surveiller les prix des actions et des devises. <\/li>\n<li>De plus, les s\u00e9ries temporelles peuvent \u00eatre utilis\u00e9es pour surveiller les processus de production dans les usines. Nous avons des utilisateurs qui utilisent VictoriaMetrics pour surveiller les \u00e9oliennes, pour les robots.<\/li>\n<li>Les s\u00e9ries temporelles sont \u00e9galement utiles pour collecter des informations \u00e0 partir de capteurs de divers dispositifs. Par exemple, pour les moteurs ; pour mesurer la pression dans les pneus ; pour mesurer la vitesse, la distance ; pour mesurer la consommation d'essence, etc.<\/li>\n<li>Les s\u00e9ries temporelles peuvent \u00e9galement \u00eatre utilis\u00e9es pour surveiller les avions. Chaque avion a une bo\u00eete noire qui collecte des s\u00e9ries temporelles sur diff\u00e9rents param\u00e8tres de sant\u00e9 de l'avion. Les s\u00e9ries temporelles sont \u00e9galement utilis\u00e9es dans l'industrie a\u00e9rospatiale. <\/li>\n<li>La sant\u00e9 \u2013 c'est la pression sanguine, le pouls, etc.<\/li>\n<\/ul>\n<p><\/p>\n<p>Il peut encore y avoir d'autres applications dont j'ai oubli\u00e9 de parler, mais j'esp\u00e8re que vous avez compris que les s\u00e9ries temporelles sont activement utilis\u00e9es dans le monde moderne. Et leur utilisation augmente chaque ann\u00e9e.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/f1da29d372163751268b6a8f86836d04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00c0 quoi sert une base de donn\u00e9es pour les s\u00e9ries temporelles ? Pourquoi ne pas utiliser une base de donn\u00e9es relationnelle classique pour stocker des s\u00e9ries temporelles ?<\/p>\n<p><\/p>\n<p>Parce que dans les s\u00e9ries temporelles, il y a g\u00e9n\u00e9ralement une grande quantit\u00e9 d'informations qui sont difficiles \u00e0 stocker et \u00e0 traiter dans des bases de donn\u00e9es classiques. C'est pourquoi des bases de donn\u00e9es sp\u00e9cialis\u00e9es pour les s\u00e9ries temporelles ont vu le jour. Ces bases stockent efficacement les points. <code>(timestamp, value)<\/code> avec une cl\u00e9 donn\u00e9e. Elles fournissent une API pour lire les donn\u00e9es stock\u00e9es par cl\u00e9, soit une paire cl\u00e9-valeur, soit plusieurs de ces paires, soit par regexp. Par exemple, si vous souhaitez trouver la charge du processeur de tous vos services dans un centre de donn\u00e9es en Am\u00e9rique, vous devez utiliser cette sorte de pseudointerrogation.<\/p>\n<p><\/p>\n<p>En g\u00e9n\u00e9ral, les bases de donn\u00e9es pour s\u00e9ries temporelles proposent des langages de requ\u00eate sp\u00e9cialis\u00e9s, car SQL n'est pas tr\u00e8s adapt\u00e9 aux s\u00e9ries temporelles. Bien qu'il existe des bases de donn\u00e9es qui supportent SQL, ce dernier ne convient pas vraiment. Des langages de requ\u00eate comme <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>sont mieux adapt\u00e9s. J'esp\u00e8re que quelqu'un a d\u00e9j\u00e0 entendu parler d'au moins l'un de ces langages. De nombreux gens ont s\u00fbrement entendu parler de PromQL. C'est le langage de requ\u00eates de Prometheus.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/59e5b6d82a2f7861077bc5fe34519ee4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Voici \u00e0 quoi ressemble l'architecture d'une base de donn\u00e9es moderne pour s\u00e9ries temporelles, avec VictoriaMetrics comme exemple.<\/p>\n<p><\/p>\n<p>Elle se compose de deux parties. Il y a un stockage pour l'index invers\u00e9 et un stockage pour les valeurs des s\u00e9ries temporelles. Ces stockages sont s\u00e9par\u00e9s. <\/p>\n<p><\/p>\n<p>Lorsque qu'un nouvel enregistrement arrive dans la base de donn\u00e9es, nous consultons d'abord l'index invers\u00e9 pour trouver l'identifiant de la s\u00e9rie temporelle correspondant \u00e0 un ensemble donn\u00e9 <code>label=value<\/code> pour cette m\u00e9trique. Nous trouvons cet identifiant et stockons la valeur dans le stockage de donn\u00e9es.<\/p>\n<p><\/p>\n<p>Lorsque qu'une requ\u00eate d'extraction de donn\u00e9es est faite au TSDB, nous consultons d'abord l'index invers\u00e9. Nous obtenons tous les <code>timeseries_ids<\/code> d'enregistrements, qui correspondent \u00e0 l'ensemble donn\u00e9. <code>label=value<\/code>Puis nous r\u00e9cup\u00e9rons toutes les donn\u00e9es n\u00e9cessaires du stockage de donn\u00e9es, index\u00e9es par <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/eb9b36fa3c6de2188b97b56ad3839b2c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Consid\u00e9rons un exemple o\u00f9 la base de donn\u00e9es pour s\u00e9ries temporelles traite une requ\u00eate SELECT entrante.<\/p>\n<p><\/p>\n<ul>\n<li>Dans un premier temps, elle r\u00e9cup\u00e8re tous les <code>timeseries_ids<\/code> de l'index invers\u00e9, qui contiennent les paires donn\u00e9es <code>label=value<\/code>, ou qui satisfont \u00e0 une expression r\u00e9guli\u00e8re donn\u00e9e.<\/li>\n<li>Puis elle extrait tous les points de donn\u00e9es du stockage de donn\u00e9es sur l'intervalle de temps donn\u00e9 pour les trouv\u00e9s. <code>timeseries_ids<\/code>.<\/li>\n<li>Apr\u00e8s cela, la base de donn\u00e9es effectue certains calculs sur ces points de donn\u00e9es, selon la requ\u00eate de l'utilisateur. Et ensuite, elle retourne la r\u00e9ponse.<\/li>\n<\/ul>\n<p><\/p>\n<p>Dans cette pr\u00e9sentation, je vais vous parler de la premi\u00e8re partie. C'est la recherche. <code>timeseries_ids<\/code> selon l'index invers\u00e9. Vous pouvez ensuite consulter la deuxi\u00e8me et la troisi\u00e8me partie. <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">sources de VictoriaMetrics.<\/a><\/noindex>, ou attendre que je pr\u00e9pare d'autres pr\u00e9sentations \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/3a7dab707dd1defb9bc674a342052a03.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Commen\u00e7ons par l'index invers\u00e9. Beaucoup peuvent penser que c'est simple. Qui sait ce qu'est un index invers\u00e9 et comment il fonctionne ? Oh, il n'y a d\u00e9j\u00e0 plus tant de gens. Essayons de comprendre ce que c'est. <\/p>\n<p><\/p>\n<p>En r\u00e9alit\u00e9, c'est assez simple. C'est juste un dictionnaire qui fait correspondre une cl\u00e9 \u00e0 une valeur. Qu'est-ce qu'une cl\u00e9 ? Cette paire <code>label=value<\/code>, o\u00f9 <code>label<\/code> et <code>value<\/code> \u2014 ce sont des cha\u00eenes. Et les valeurs sont un ensemble <code>timeseries_ids<\/code>, qui contient la paire donn\u00e9e. <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>L'index invers\u00e9 permet de trouver rapidement tous <code>timeseries_ids<\/code>, qui poss\u00e8dent les <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Il permet \u00e9galement de trouver rapidement des <code>timeseries_ids<\/code> s\u00e9ries temporelles pour plusieurs paires <code>label=value<\/code>, ou pour des paires <code>label=regexp<\/code>. Comment cela fonctionne-t-il ? Gr\u00e2ce \u00e0 la recherche de l'intersection d'un ensemble <code>timeseries_ids<\/code> pour chaque paire. <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/837a15a078e8c9422c721f3f072c0c09.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Examinons diff\u00e9rentes impl\u00e9mentations de l'index invers\u00e9. Commen\u00e7ons par l'impl\u00e9mentation na\u00efve la plus simple. Elle ressemble \u00e0 ceci. <\/p>\n<p><\/p>\n<p>Fonction <code>getMetricIDs<\/code> obtient une liste de cha\u00eenes. Chaque cha\u00eene contient <code>label=value<\/code>. Cette fonction renvoie une liste de <code>metricIDs.<\/code>.<\/p>\n<p><\/p>\n<p>Comment cela fonctionne-t-il ? Ici, nous avons une variable globale appel\u00e9e <code>invertedIndex.<\/code>C'est un simple dictionnaire (<code>carte<\/code>), qui associe une cha\u00eene \u00e0 un slice d'entiers. La cha\u00eene contient <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Impl\u00e9mentation de la fonction : nous obtenons <code>metricIDs.<\/code> pour le premier <code>label=value<\/code>, puis nous parcourons tous les autres <code>label=value<\/code>, nous obtenons <code>metricIDs.<\/code> pour eux. Et nous appelons la fonction <code>intersectInts,<\/code>qui sera expliqu\u00e9e plus loin. Cette fonction renvoie l'intersection de ces listes.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/709beca79884a9693098781974c26f4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Comme vous pouvez le voir, l'impl\u00e9mentation de l'index invers\u00e9 n'est pas tr\u00e8s compliqu\u00e9e. Mais c'est une impl\u00e9mentation na\u00efve. Quels en sont les inconv\u00e9nients ? Le principal inconv\u00e9nient de l'impl\u00e9mentation na\u00efve est que cet index invers\u00e9 est stock\u00e9 en m\u00e9moire vive. Apr\u00e8s le red\u00e9marrage de l'application, nous perdons cet index. Il n'y a pas de sauvegarde de cet index sur disque. Pour une base de donn\u00e9es, un tel index invers\u00e9 n'est probablement pas adapt\u00e9.<\/p>\n<p><\/p>\n<p>Le deuxi\u00e8me inconv\u00e9nient est \u00e9galement li\u00e9 \u00e0 la m\u00e9moire. L'index invers\u00e9 doit tenir dans la m\u00e9moire vive. S'il d\u00e9passe la taille de la m\u00e9moire, il est \u00e9vident que nous obtiendrons une erreur de type sorti de m\u00e9moire. Et le programme ne fonctionnera pas.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/495394168ac03aa8cbcf56ccad1cb2b8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ce probl\u00e8me peut \u00eatre r\u00e9solu gr\u00e2ce \u00e0 des solutions pr\u00eates \u00e0 l'emploi telles que <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/google\/leveldb\">LevelDB<\/a><\/noindex>, ou bien <noindex><a rel=\"nofollow\" href=\"https:\/\/rocksdb.org\/\">RocksDB.<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>En r\u00e9sum\u00e9, nous avons besoin d'une base de donn\u00e9es qui permet d'effectuer rapidement trois op\u00e9rations. <\/p>\n<p><\/p>\n<ul>\n<li>La premi\u00e8re op\u00e9ration consiste \u00e0 \u00e9crire <code>cl\u00e9-valeur<\/code> dans cette base. Elle le fait tr\u00e8s rapidement, o\u00f9 <code>cl\u00e9-valeur<\/code> ce sont des cha\u00eenes al\u00e9atoires. <\/li>\n<li>La deuxi\u00e8me op\u00e9ration consiste \u00e0 rechercher rapidement une valeur par cl\u00e9.<\/li>\n<li>Et la troisi\u00e8me op\u00e9ration consiste \u00e0 rechercher rapidement toutes les valeurs par un pr\u00e9fixe donn\u00e9. <\/li>\n<\/ul>\n<p><\/p>\n<p>LevelDB et RocksDB \u2013 ces bases ont \u00e9t\u00e9 d\u00e9velopp\u00e9es chez Google et Facebook. LevelDB est apparu en premier. Ensuite, les gars de Facebook ont pris LevelDB et l'ont am\u00e9lior\u00e9, cr\u00e9ant RocksDB. Actuellement, presque toutes les bases de donn\u00e9es internes de Facebook fonctionnent sur RocksDB, y compris MySQL, qui a \u00e9t\u00e9 transf\u00e9r\u00e9 sur RocksDB. Ils l'ont appel\u00e9 <noindex><a rel=\"nofollow\" href=\"http:\/\/myrocks.io\/\">MyRocks<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Un index invers\u00e9 peut \u00eatre r\u00e9alis\u00e9 avec LevelDB. Comment faire cela ? Nous le sauvegardons en tant que cl\u00e9 <code>label=value<\/code>. Et en tant que valeur \u2013 l'identifiant de la s\u00e9rie temporelle o\u00f9 la paire est pr\u00e9sente. <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Si nous avons plusieurs s\u00e9ries temporelles avec cette paire <code>label=value<\/code>, il y aura beaucoup de lignes dans cette base de donn\u00e9es avec la m\u00eame cl\u00e9 et diff\u00e9rentes <code>timeseries_ids<\/code>. Pour obtenir la liste de tous les <code>timeseries_ids<\/code>, qui commencent par un certain <code>label=prefix<\/code>, nous effectuons un scan de plage, pour lequel cette base de donn\u00e9es est optimis\u00e9e. C'est-\u00e0-dire que nous s\u00e9lectionnons toutes les lignes qui commencent par <code>label=prefix<\/code> et obtenons les <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/a40877ecb758ac3bc1979bcd568d7e83.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Voici une impl\u00e9mentation approximative de ce \u00e0 quoi cela ressemblerait en Go. Nous avons un index invers\u00e9. C'est LevelDB.<\/p>\n<p><\/p>\n<p>La fonction est la m\u00eame que pour l'impl\u00e9mentation na\u00efve. Elle r\u00e9p\u00e8te presque ligne par ligne l'impl\u00e9mentation na\u00efve. Le seul point, c'est qu'au lieu d'acc\u00e9der \u00e0 <code>carte<\/code> , nous acc\u00e9dons \u00e0 l'index invers\u00e9. Nous r\u00e9cup\u00e9rons toutes les valeurs pour le premier <code>label=value<\/code>. Ensuite, nous passons en revue toutes les paires restantes <code>label=value<\/code> et obtenons les ensembles correspondants de metricIDs pour elles. Puis nous trouvons l'intersection. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/de11b15286816ada04b22727ff78e43d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Tout semble bien, mais cette solution a ses inconv\u00e9nients. VictoriaMetrics a d'abord impl\u00e9ment\u00e9 l'index invers\u00e9 bas\u00e9 sur LevelDB. Mais au final, ils ont d\u00fb y renoncer.<\/p>\n<p><\/p>\n<p>Pourquoi ? Parce que LevelDB est plus lent que l'impl\u00e9mentation na\u00efve. Dans l'impl\u00e9mentation na\u00efve, pour une cl\u00e9 donn\u00e9e, nous acc\u00e9dons directement \u00e0 tout le slice <code>metricIDs.<\/code>. C'est une op\u00e9ration tr\u00e8s rapide \u2013 tout le slice est pr\u00eat \u00e0 \u00eatre utilis\u00e9.<\/p>\n<p><\/p>\n<p>Dans LevelDB, \u00e0 chaque appel de la fonction <code>GetValues<\/code> , il faut parcourir toutes les lignes qui commencent par <code>label=value<\/code>. Et pour chaque ligne, extraire la valeur <code>timeseries_ids<\/code>. De ces <code>timeseries_ids<\/code> , recueillir un slice de ces <code>timeseries_ids<\/code>. Il est \u00e9vident que cela est beaucoup plus lent que d'acc\u00e9der simplement \u00e0 une map classique par cl\u00e9.<\/p>\n<p><\/p>\n<p>Le deuxi\u00e8me inconv\u00e9nient est que LevelDB est \u00e9crit en C. L'appel des fonctions C depuis Go n'est pas tr\u00e8s rapide. Cela prend des centaines de nanosecondes. Ce n'est pas tr\u00e8s rapide, car par rapport \u00e0 un appel de fonction \u00e9crit en Go, qui prend 1 \u00e0 5 nanosecondes, l'\u00e9cart de performance est de plusieurs ordres de grandeur. Pour VictoriaMetrics, cela a \u00e9t\u00e9 un inconv\u00e9nient fatal \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/47cace825807b7b062064c2e790dc9d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>C'est pourquoi j'ai \u00e9crit ma propre impl\u00e9mentation d'un index invers\u00e9. Je l'ai appel\u00e9e <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">mergeset<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Mergeset est bas\u00e9 sur la structure de donn\u00e9es MergeTree. Cette structure de donn\u00e9es est emprunt\u00e9e \u00e0 ClickHouse. Il est \u00e9vident que mergeset doit \u00eatre optimis\u00e9 pour une recherche rapide <code>timeseries_ids<\/code> par cl\u00e9. Mergeset est enti\u00e8rement \u00e9crit en Go. Vous pouvez consulter <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">le code source de VictoriaMetrics sur GitHub<\/a><\/noindex>. L'impl\u00e9mentation de mergeset se trouve dans le dossier <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">\/lib\/mergeset<\/a><\/noindex>. Vous pouvez essayer de comprendre ce qui s'y passe.<\/p>\n<p><\/p>\n<p>L'API de mergeset est tr\u00e8s similaire \u00e0 celle de LevelDB et RocksDB. C'est-\u00e0-dire qu'elle permet de sauvegarder rapidement de nouvelles entr\u00e9es et de s\u00e9lectionner rapidement des entr\u00e9es par un pr\u00e9fixe donn\u00e9.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/845704ed36f70beb468907d22701af59.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Nous parlerons plus tard des d\u00e9fauts de mergeset. Pour l'instant, discutons des probl\u00e8mes rencontr\u00e9s avec VictoriaMetrics en production lors de l'impl\u00e9mentation de l'index invers\u00e9.<\/p>\n<p><\/p>\n<p>Pourquoi sont-ils survenus ?<\/p>\n<p><\/p>\n<p>La premi\u00e8re raison est le taux de renouvellement \u00e9lev\u00e9. En d'autres termes, il s'agit d'un changement fr\u00e9quent des s\u00e9ries temporelles. C'est lorsque une s\u00e9rie temporelle se termine et qu'une nouvelle s\u00e9rie commence, ou que de nombreuses nouvelles s\u00e9ries temporelles commencent. Et cela se produit fr\u00e9quemment.<\/p>\n<p><\/p>\n<p>La deuxi\u00e8me raison est le grand nombre de s\u00e9ries temporelles. Au d\u00e9but, lorsque la surveillance gagnait en popularit\u00e9, le nombre de s\u00e9ries temporelles \u00e9tait faible. Par exemple, pour chaque ordinateur, il faut surveiller l'utilisation du processeur, de la m\u00e9moire, du r\u00e9seau et du disque. 4 s\u00e9ries temporelles par ordinateur. Supposons que vous ayez 100 ordinateurs et 400 s\u00e9ries temporelles. C'est tr\u00e8s peu. <\/p>\n<p><\/p>\n<p>Avec le temps, les gens ont r\u00e9alis\u00e9 qu'il \u00e9tait possible de mesurer des informations plus d\u00e9taill\u00e9es. Par exemple, mesurer l'utilisation non pas de l'ensemble du processeur, mais de chaque c\u0153ur de processeur s\u00e9par\u00e9ment. Si vous avez 40 c\u0153urs de processeur, alors vous obtenez 40 fois plus de s\u00e9ries temporelles pour mesurer l'utilisation du processeur. <\/p>\n<p><\/p>\n<p>Mais ce n'est pas tout. Chaque c\u0153ur de processeur peut avoir plusieurs \u00e9tats, tels que idle, lorsqu'il est inactif. Il peut \u00e9galement fonctionner en espace utilisateur, en espace noyau et dans d'autres \u00e9tats. Chacun de ces \u00e9tats peut \u00e9galement \u00eatre mesur\u00e9 comme une s\u00e9rie temporelle distincte. Cela augmente encore le nombre de s\u00e9ries de 7 \u00e0 8 fois.<\/p>\n<p><\/p>\n<p>Nous avons obtenu 40 x 8 = 320 m\u00e9triques rien que pour un ordinateur. En multipliant par 100, nous obtenons 32 000 au lieu de 400. <\/p>\n<p><\/p>\n<p>Puis est arriv\u00e9 Kubernetes. Cela a encore aggrav\u00e9 la situation, car de nombreux services diff\u00e9rents peuvent \u00eatre h\u00e9berg\u00e9s dans Kubernetes. Chaque service dans Kubernetes est compos\u00e9 de nombreux pods. Tout cela n\u00e9cessite une surveillance. De plus, nous avons un d\u00e9ploiement constant de nouvelles versions de vos services. Pour chaque nouvelle version, nous devons cr\u00e9er de nouvelles s\u00e9ries temporelles. Au final, le nombre de s\u00e9ries temporelles augmente de mani\u00e8re exponentielle et nous faisons face \u00e0 un probl\u00e8me de nombreuses s\u00e9ries temporelles, qui est appel\u00e9 high-cardinality. VictoriaMetrics g\u00e8re cela avec succ\u00e8s par rapport \u00e0 d'autres bases de donn\u00e9es de s\u00e9ries temporelles. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/c11b7ce6d3294ae420243f72636af4b9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Examinons de plus pr\u00e8s le high churn rate. Qu'est-ce qui cause un high churn rate en production ? Parce que certaines valeurs d'\u00e9tiquettes et de tags changent constamment.<\/p>\n<p><\/p>\n<p>Par exemple, prenons Kubernetes, qui a le concept de <code>d\u00e9ploiement<\/code>, c'est-\u00e0-dire lorsque vous d\u00e9ployez une nouvelle version de votre application. Les d\u00e9veloppeurs de Kubernetes ont inexplicablement d\u00e9cid\u00e9 d'ajouter l'identifiant du d\u00e9ploiement dans l'\u00e9tiquette.<\/p>\n<p><\/p>\n<p>Quelles en sont les cons\u00e9quences ? \u00c0 chaque nouveau d\u00e9ploiement, tous nos anciens s\u00e9ries temporelles sont interrompues, et \u00e0 leur place, de nouvelles s\u00e9ries temporelles commencent avec une nouvelle valeur d'\u00e9tiquette. <code>deployment_id<\/code>. Il peut y avoir des centaines de milliers, voire des millions de telles s\u00e9ries.<\/p>\n<p><\/p>\n<p>Une caract\u00e9ristique importante de tout cela est que le nombre total de s\u00e9ries temporelles augmente, mais le nombre de s\u00e9ries temporelles qui sont actuellement actives, dont les donn\u00e9es arrivent, reste constant. Cet \u00e9tat est appel\u00e9 \u2013 high churn rate.<\/p>\n<p><\/p>\n<p>Le principal probl\u00e8me du high churn rate est d'assurer une vitesse de recherche constante pour toutes les s\u00e9ries temporelles sur la base d'un ensemble donn\u00e9 d'\u00e9tiquettes pendant un certain intervalle de temps. G\u00e9n\u00e9ralement, il s'agit d'un intervalle de temps au cours de la derni\u00e8re heure ou de la derni\u00e8re journ\u00e9e. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/ac18482b87cc37624d163b30c68cf7be.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Comment r\u00e9soudre ce probl\u00e8me ? Voici une premi\u00e8re option. Il s'agit de diviser l'index invers\u00e9 en parties ind\u00e9pendantes par p\u00e9riode. C'est-\u00e0-dire qu'apr\u00e8s un certain intervalle de temps, nous terminons notre travail avec l'index invers\u00e9 actuel et cr\u00e9ons un nouvel index invers\u00e9. Un autre intervalle de temps passe, et nous cr\u00e9ons encore un autre index invers\u00e9. <\/p>\n<p><\/p>\n<p>Et lors de la s\u00e9lection parmi ces index invers\u00e9s, nous trouvons un ensemble d'index invers\u00e9s qui tombent dans l'intervalle sp\u00e9cifi\u00e9. Nous s\u00e9lectionnons donc les identifiants des s\u00e9ries temporelles \u00e0 partir de l\u00e0. <\/p>\n<p><\/p>\n<p>Cela permet d'\u00e9conomiser des ressources, car nous n'avons pas besoin d'examiner des parties qui ne tombent pas dans l'intervalle sp\u00e9cifi\u00e9. C'est-\u00e0-dire g\u00e9n\u00e9ralement, si nous s\u00e9lectionnons des donn\u00e9es de la derni\u00e8re heure, nous ignorons les requ\u00eates des intervalles de temps pr\u00e9c\u00e9dents. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/1afa3a88caa7423941ae3494b9cec706.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Il existe une autre option pour r\u00e9soudre ce probl\u00e8me. Il s'agit de stocker pour chaque jour une liste distincte des identifiants des s\u00e9ries temporelles rencontr\u00e9es ce jour-l\u00e0.<\/p>\n<p><\/p>\n<p>L'avantage de cette solution par rapport \u00e0 la pr\u00e9c\u00e9dente est que nous ne doublons pas les informations sur les s\u00e9ries temporelles qui ne disparaissent pas avec le temps. Elles sont toujours pr\u00e9sentes et ne changent pas. <\/p>\n<p><\/p>\n<p>L'inconv\u00e9nient est que cette solution est plus complexe \u00e0 mettre en \u0153uvre et plus difficile \u00e0 d\u00e9boguer. Et VictoriaMetrics a choisi cette solution. Cela s'est install\u00e9 historiquement. Cette solution s'est \u00e9galement bien comport\u00e9e par rapport \u00e0 la pr\u00e9c\u00e9dente. Car cette solution n'a pas \u00e9t\u00e9 mise en \u0153uvre \u00e0 cause de la n\u00e9cessit\u00e9 de dupliquer les donn\u00e9es dans chaque partition pour les s\u00e9ries temporelles qui ne changent pas, c'est-\u00e0-dire qui ne disparaissent pas avec le temps. VictoriaMetrics a d'abord \u00e9t\u00e9 optimis\u00e9e pour la consommation d'espace disque, et l'impl\u00e9mentation pr\u00e9c\u00e9dente a d\u00e9grad\u00e9 la consommation d'espace disque. Mais cette impl\u00e9mentation est mieux adapt\u00e9e \u00e0 la minimisation de la consommation d'espace disque, c'est pourquoi elle a \u00e9t\u00e9 choisie. <\/p>\n<p><\/p>\n<p>Il a fallu lutter contre \u00e7a. La lutte consistait \u00e0 devoir s\u00e9lectionner un nombre beaucoup plus important <code>timeseries_ids<\/code> de donn\u00e9es, que lorsque l'index invers\u00e9 est divis\u00e9 par p\u00e9riode.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/5b10e61aaa803428ed3bfe31815f09d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Comment avons-nous r\u00e9solu ce probl\u00e8me ? Nous l'avons r\u00e9solu de mani\u00e8re originale \u2013 en sauvegardant plusieurs identifiants de s\u00e9ries temporelles dans chaque enregistrement de l'index invers\u00e9 au lieu d'un seul identifiant. C'est-\u00e0-dire que nous avons une cl\u00e9 <code>label=value<\/code>, qui est pr\u00e9sent dans chaque s\u00e9rie temporelle. Et maintenant, nous conservons plusieurs <code>timeseries_ids<\/code> dans un seul enregistrement.<\/p>\n<p><\/p>\n<p>Voici un exemple. Avant, nous avions N enregistrements, et maintenant, nous avons un enregistrement dont le pr\u00e9fixe est le m\u00eame que celui de tous les autres. L'enregistrement pr\u00e9c\u00e9dent contenait toutes les identifiants de s\u00e9ries temporelles. <\/p>\n<p><\/p>\n<p>Cela a permis d'augmenter la vitesse de consultation de cet index invers\u00e9 jusqu'\u00e0 10 fois. Et a permis de diminuer la consommation de m\u00e9moire pour le cache, car maintenant nous stockons la cha\u00eene <code>label=value<\/code> une seule fois dans le cache au lieu de N fois. Et cette cha\u00eene peut \u00eatre grande si vos tags et labels contiennent de longues cha\u00eenes que Kubernetes aime y int\u00e9grer.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/ae4f38a440203bad2d03cf2abd1faafe.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Une autre option pour acc\u00e9l\u00e9rer la recherche dans un index invers\u00e9 est le sharding. Cr\u00e9er plusieurs index invers\u00e9s au lieu d'un seul et shard les donn\u00e9es entre eux par cl\u00e9. C'est un ensemble <code>cl\u00e9=valeur<\/code> de paires. C'est-\u00e0-dire que nous obtenons plusieurs index invers\u00e9s ind\u00e9pendants que nous pouvons interroger en parall\u00e8le sur plusieurs processeurs. Les impl\u00e9mentations pr\u00e9c\u00e9dentes ne permettaient de fonctionner qu'en mode monoc\u0153ur, c'est-\u00e0-dire de scanner les donn\u00e9es sur un seul c\u0153ur. Cette solution permet de scanner les donn\u00e9es simultan\u00e9ment sur plusieurs c\u0153urs, comme ClickHouse aime le faire. C'est ce que nous pr\u00e9voyons de mettre en \u0153uvre.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/5ced6446efbf8ded8d7fb91694a999b0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Et maintenant, revenons \u00e0 nos moutons \u2013 \u00e0 la fonction d'intersection <code>timeseries_ids<\/code>. Voyons quelles pourraient \u00eatre les r\u00e9alisations. Cette fonction permet de trouver <code>timeseries_ids<\/code> pour un ensemble donn\u00e9 <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/073a155018a91120c6996c324772f9af.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>La premi\u00e8re option est l'impl\u00e9mentation na\u00efve. Deux boucles imbriqu\u00e9es. Nous recevons en entr\u00e9e de la fonction <code>intersectInts,<\/code> deux slices \u2014 <code>a<\/code> et <code>b<\/code>. En sortie, elle doit nous renvoyer l'intersection de ces slices.<\/p>\n<p><\/p>\n<p>L'impl\u00e9mentation na\u00efve ressemble \u00e0 cela. Nous passons en revue toutes les valeurs du slice <code>a<\/code>, \u00e0 l'int\u00e9rieur de cette boucle, nous parcourons toutes les valeurs du slice <code>b<\/code>. Et nous les comparons. S'ils correspondent, cela signifie que nous avons trouv\u00e9 une intersection. Et nous l'enregistrons dans <code>result<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/b89dcbb4f6d73377f9cf4f50169d6dd7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quels sont les inconv\u00e9nients ? La complexit\u00e9 quadratique \u2014 c'est son principal inconv\u00e9nient. Par exemple, si vous avez des tailles de slice <code>a<\/code> et <code>b<\/code> d'un million, cette fonction ne vous renverra jamais une r\u00e9ponse. Parce qu'il lui faudrait effectuer un trillion d'it\u00e9rations, ce qui est \u00e9norme m\u00eame pour des ordinateurs modernes.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/bad26b0092a2fd7fc813bcb79a9138ce.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>La deuxi\u00e8me r\u00e9alisation est bas\u00e9e sur une map. Nous cr\u00e9ons une map. Nous y ins\u00e9rons toutes les valeurs du slice <code>a<\/code>. Ensuite, nous parcourons le slice <code>b<\/code>. Et nous v\u00e9rifions s'il y a cette valeur du slice. <code>b<\/code> dans map. S'il existe, nous l'ajoutons au r\u00e9sultat. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/1e610acf641a9cfe4c80aac1fe26044c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quels sont les avantages ? L'avantage est qu'il s'agit uniquement d'une complexit\u00e9 lin\u00e9aire. C'est-\u00e0-dire que la fonction s'ex\u00e9cute beaucoup plus rapidement pour de grandes tailles de slices. Pour une taille de slice d'un million, cette fonction s'ex\u00e9cutera en 2 millions d'it\u00e9rations, contrairement \u00e0 un trillion d'it\u00e9rations, comme dans la fonction pr\u00e9c\u00e9dente.<\/p>\n<p><\/p>\n<p>L'inconv\u00e9nient est que cette fonction n\u00e9cessite plus de m\u00e9moire pour cr\u00e9er cette map.<\/p>\n<p><\/p>\n<p>Le deuxi\u00e8me inconv\u00e9nient est le grand overhead lors du hachage. Cet inconv\u00e9nient n'est pas tr\u00e8s \u00e9vident. Et cela n'a pas \u00e9t\u00e9 tr\u00e8s \u00e9vident pour nous non plus, donc au d\u00e9but, l'impl\u00e9mentation de l'intersection dans VictoriaMetrics \u00e9tait via map. Mais ensuite, le profilage a montr\u00e9 que le temps processeur principal \u00e9tait d\u00e9pens\u00e9 \u00e0 \u00e9crire dans la map et \u00e0 v\u00e9rifier la pr\u00e9sence d'une valeur dans cette map.<\/p>\n<p><\/p>\n<p>Pourquoi le temps processeur est-il d\u00e9pens\u00e9 ici ? Parce que dans ces lignes, Go effectue une op\u00e9ration de hachage. C'est-\u00e0-dire qu'il calcule le hash de la cl\u00e9 pour ensuite y acc\u00e9der par l'indice donn\u00e9 dans le HashMap. L'op\u00e9ration de calcul du hash est r\u00e9alis\u00e9e en quelques dizaines de nanosecondes. C'est lent pour VictoriaMetrics.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/969f10d875d88998e958894ca28b1181.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>J'ai d\u00e9cid\u00e9 d'impl\u00e9menter un bitset, optimis\u00e9 sp\u00e9cialement pour ce cas. Voici comment se pr\u00e9sente maintenant l'intersection de deux slices. Ici, nous cr\u00e9ons un bitset. Nous y ajoutons des \u00e9l\u00e9ments du premier slice. Ensuite, nous v\u00e9rifions la pr\u00e9sence de ces \u00e9l\u00e9ments dans le second slice. Et nous les ajoutons au r\u00e9sultat. C'est-\u00e0-dire que cela ne diff\u00e8re presque pas de l'exemple pr\u00e9c\u00e9dent. La seule chose que nous avons remplac\u00e9e ici, c'est l'acc\u00e8s \u00e0 la map par des fonctions personnalis\u00e9es. <code>add<\/code> et <code>has<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/b4ec0753d30d9684868a031f605632c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00c0 premi\u00e8re vue, cela semble devoir \u00eatre plus lent, si auparavant une map standard \u00e9tait utilis\u00e9e et ici des fonctions suppl\u00e9mentaires sont appel\u00e9es, mais le profilage montre que cela fonctionne 10 fois plus vite que la map standard dans le contexte de VictoriaMetrics.<\/p>\n<p><\/p>\n<p>De plus, cela utilise beaucoup moins de m\u00e9moire par rapport \u00e0 l'impl\u00e9mentation de la map. Parce que nous stockons ici des bits au lieu de valeurs de huit octets.<\/p>\n<p><\/p>\n<p>L'inconv\u00e9nient de cette impl\u00e9mentation est qu'elle n'est pas aussi \u00e9vidente, pas triviale. <\/p>\n<p><\/p>\n<p>Un autre inconv\u00e9nient que beaucoup pourraient ne pas remarquer est que cette impl\u00e9mentation peut mal fonctionner dans certains cas. C'est-\u00e0-dire qu'elle est optimis\u00e9e pour un cas particulier, celui de l'intersection des identifiants de s\u00e9ries temporelles de VictoriaMetrics. Cela ne veut pas dire qu'elle convient \u00e0 tous les cas. Si elle est mal utilis\u00e9e, nous n'obtiendrons pas d'augmentation des performances, mais une erreur de m\u00e9moire insuffisante et un ralentissement de la performance. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/94bd174afe43e67b3cf279dc7a1be17c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Examinons l'impl\u00e9mentation de cette structure. Si vous souhaitez la consulter, elle se trouve dans les sources de VictoriaMetrics, dans le dossier <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/uint64set\">lib\/uint64set<\/a><\/noindex>. Elle est optimis\u00e9e pour le cas de VictoriaMetrics, o\u00f9 <code>timeseries_id<\/code> repr\u00e9sente une valeur de 64 bits, o\u00f9 les 32 premiers bits sont principalement constants et seuls les 32 derniers bits changent.<\/p>\n<p><\/p>\n<p>Cette structure de donn\u00e9es n'est pas stock\u00e9e sur le disque, elle fonctionne uniquement en m\u00e9moire. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/e24ca65f860e2b04037e073f7acae0c1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Voici son API. Elle n'est pas tr\u00e8s compliqu\u00e9e. L'API est ajust\u00e9e sp\u00e9cifiquement pour un exemple d'utilisation de VictoriaMetrics. C'est-\u00e0-dire qu'il n'y a pas de fonctions superflues. Ici, les fonctions sont celles qui sont explicitement utilis\u00e9es par VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Il y a des fonctions <code>add<\/code>, qui ajoutent de nouvelles valeurs. Il y a une fonction <code>has<\/code>, qui v\u00e9rifie les nouvelles valeurs. Et il y a une fonction <code>del<\/code>, qui supprime des valeurs. Il y a aussi une fonction auxiliaire <code>len<\/code>, qui retourne la taille de l'ensemble. La fonction <code>clone<\/code> clone l'ensemble. Et la fonction <code>appendto<\/code> transforme cet ensemble en tranche <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/874b04042d13f342d39f8f393e7fb78c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Voici \u00e0 quoi ressemble l'impl\u00e9mentation de cette structure de donn\u00e9es. Dans l'ensemble, il y a deux \u00e9l\u00e9ments :<\/p>\n<p><\/p>\n<ul>\n<li>\n<p><code>ItemsCount<\/code> \u2013 c'est un champ auxiliaire, pour renvoyer rapidement le nombre d'\u00e9l\u00e9ments dans l'ensemble. On aurait pu se passer de ce champ auxiliaire, mais il a d\u00fb \u00eatre ajout\u00e9 ici, car VictoriaMetrics interroge souvent dans ses algorithmes la longueur du bitset.<\/p>\n<p>\n<\/li>\n<li>\n<p>Le deuxi\u00e8me champ est <code>buckets<\/code>. C'est une tranche de la structure <code>bucket32<\/code>. Chaque structure contient <code>hi<\/code> un champ. Ce sont les 32 bits sup\u00e9rieurs. Et deux tranches \u2014 <code>b16his<\/code> et <code>buckets<\/code> de <code>bucket16<\/code> de structures. <\/p>\n<p>\n<\/li>\n<\/ul>\n<p><\/p>\n<p>Ici sont stock\u00e9s les 16 bits sup\u00e9rieurs de la deuxi\u00e8me partie de la structure 64 bits. Et ici sont stock\u00e9s les bitsets pour les 16 bits inf\u00e9rieurs de chaque octet. <\/p>\n<p><\/p>\n<p><code>Bucket64<\/code> consiste en un tableau <code>uint64<\/code>. La longueur est calcul\u00e9e \u00e0 l'aide de ces constantes. Dans un <code>bucket16<\/code> maximum peut \u00eatre stock\u00e9 <code>2^16=65536<\/code> bits. Si l'on divise cela par 8, cela fait 8 ko. Si l'on divise encore par 8, cela fait 1000 <code>uint64<\/code> valeurs. C'est-\u00e0-dire que <code>Bucket16<\/code> est une structure de 8 ko. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/1cca4e6bfafa3408a3c95e0118e26d80.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Examinons comment l'une des m\u00e9thodes de cette structure pour ajouter une nouvelle valeur est impl\u00e9ment\u00e9e. <\/p>\n<p><\/p>\n<p>Tout commence par <code>uint64<\/code> valeurs. Nous calculons les 32 bits sup\u00e9rieurs, nous calculons les 32 bits inf\u00e9rieurs. Nous parcourons tous les <code>buckets<\/code>. Nous comparons les 32 bits sup\u00e9rieurs dans chaque bucket avec la valeur ajout\u00e9e. Et si elles correspondent, nous appelons la fonction <code>add<\/code> dans la structure b32 <code>buckets<\/code>. Et nous y ajoutons les 32 bits inf\u00e9rieurs. Et si cela a retourn\u00e9 <code>true<\/code>, cela signifie que nous avons ajout\u00e9 cette valeur et que nous n'avions pas cette valeur auparavant. Si elle retourne <code>faux<\/code>, cela signifie que cette valeur \u00e9tait d\u00e9j\u00e0 l\u00e0. Nous augmentons ensuite le nombre d'\u00e9l\u00e9ments dans la structure. <\/p>\n<p><\/p>\n<p>Si nous n'avons pas trouv\u00e9 le <code>bucket<\/code> avec la valeur hi-essentielle correspondante, nous appelons la fonction <code>addAlloc<\/code>, qui alloue un nouvel <code>bucket<\/code>, en l'ajoutant \u00e0 la structure de bucket.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/c5d1e5de767f47c7c40e01e12b6d0617.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>C'est l'impl\u00e9mentation de la fonction <code>b32.add<\/code>. Elle ressemble \u00e0 l'impl\u00e9mentation pr\u00e9c\u00e9dente. Nous calculons les 16 bits sup\u00e9rieurs, les 16 bits inf\u00e9rieurs.<\/p>\n<p><\/p>\n<p>Ensuite, nous parcourons tous les 16 bits sup\u00e9rieurs. Nous trouvons des correspondances. Et en cas de correspondance, nous appelons la m\u00e9thode add, que nous examinerons \u00e0 la page suivante pour <code>bucket16<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/e98c4ef316f3a894fd7019c3c85696f7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Et voici le niveau le plus bas, qui doit \u00eatre maximis\u00e9 pour l'optimisation. Nous calculons pour <code>uint64<\/code> la valeur id dans slice bit, ainsi que <code>bitmask<\/code>. C'est un masque pour cette valeur 64 bits, avec lequel nous pouvons v\u00e9rifier la pr\u00e9sence de ce bit, ou le d\u00e9finir. Nous v\u00e9rifions si ce bit est d\u00e9j\u00e0 d\u00e9fini, nous le d\u00e9finissons, et nous retournons sa pr\u00e9sence. Voici une telle impl\u00e9mentation que nous avons, qui a permis d'acc\u00e9l\u00e9rer l'op\u00e9ration d'intersection d'ids de s\u00e9ries temporelles de 10 fois par rapport aux maps ordinaires.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/4dfa5a6142829a3551be22d6c9c3ab92.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Dans VictoriaMetrics, en plus de cette optimisation, il y a beaucoup d'autres optimisations. La majorit\u00e9 de ces optimisations ont \u00e9t\u00e9 ajout\u00e9es non pas par hasard, mais apr\u00e8s profilage du code en production.<\/p>\n<p><\/p>\n<p>C'est la r\u00e8gle principale de l'optimisation - ne pas ajouter d'optimisation en supposant qu'il y aura un goulet d'\u00e9tranglement ici, car il peut s'av\u00e9rer qu'il n'y en a pas. L'optimisation d\u00e9grade g\u00e9n\u00e9ralement la qualit\u00e9 du code. Par cons\u00e9quent, il vaut mieux optimiser seulement apr\u00e8s profilage et id\u00e9alement en production, afin que ce soient des donn\u00e9es r\u00e9elles. Pour ceux qui sont int\u00e9ress\u00e9s, vous pouvez consulter les sources de VictoriaMetrics et \u00e9tudier d'autres optimisations qui s'y trouvent.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimisations de Go dans VictoriaMetrics. Alexandre Valialkin\" src=\"\/wp-content\/uploads\/2020\/05\/7585c4dc782627bac5b649e6a55e2ffb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><em>J'ai une question sur le bitset. Cela ressemble beaucoup \u00e0 l'impl\u00e9mentation du vector bool en C++, un bitset optimis\u00e9. Avez-vous pris l'impl\u00e9mentation de l\u00e0-bas?<\/em><\/p>\n<p><\/p>\n<p>Non, ce n'est pas \u00e7a. Lors de la mise en \u0153uvre de ce bitset, je me suis bas\u00e9 sur la connaissance de la structure de ces ids timeseries, utilis\u00e9s dans VictoriaMetrics. Leur structure est telle que les 32 bits sup\u00e9rieurs sont principalement constants. Les 32 bits inf\u00e9rieurs peuvent varier. Plus le bit est bas, plus il peut changer fr\u00e9quemment. Par cons\u00e9quent, cette mise en \u0153uvre est pr\u00e9cis\u00e9ment optimis\u00e9e pour cette structure de donn\u00e9es. L'impl\u00e9mentation C++, autant que je sache, est optimis\u00e9e pour le cas g\u00e9n\u00e9ral. Si l'on fait une optimisation pour le cas g\u00e9n\u00e9ral, cela signifie qu'elle ne sera pas optimale pour un cas sp\u00e9cifique.<\/p>\n<p><\/p>\n<p>Je vous conseille \u00e9galement de consulter la pr\u00e9sentation d'Alexey Milovidov. Il a parl\u00e9 des optimisations dans ClickHouse pour des sp\u00e9cialisations sp\u00e9cifiques il y a environ un mois. Il explique justement que dans le cas g\u00e9n\u00e9ral, l'impl\u00e9mentation C++ ou toute autre impl\u00e9mentation est con\u00e7ue pour bien fonctionner dans l'ensemble. Elle peut fonctionner moins bien qu'une impl\u00e9mentation sp\u00e9cialis\u00e9e en fonction de connaissances sp\u00e9cifiques, comme dans notre cas, o\u00f9 nous savons que les 32 bits sup\u00e9rieurs sont principalement constants.<\/p>\n<p><\/p>\n<p><em>J'ai une deuxi\u00e8me question. Quelle est la diff\u00e9rence fondamentale avec InfluxDB ?<\/em><\/p>\n<p><\/p>\n<p>Il y a beaucoup de diff\u00e9rences fondamentales. En termes de performance et de consommation de m\u00e9moire, InfluxDB montre dans les tests une consommation de m\u00e9moire dix fois plus \u00e9lev\u00e9e pour des s\u00e9ries temporelles \u00e0 haute cardinalit\u00e9, lorsque vous en avez beaucoup, par exemple des millions. Par exemple, VictoriaMetrics consomme 1 Go pour un million de s\u00e9ries actives, tandis qu'InfluxDB consomme 10 Go. Et c'est une grande diff\u00e9rence. <\/p>\n<p><\/p>\n<p>La deuxi\u00e8me diff\u00e9rence fondamentale est qu'InfluxDB a des langages de requ\u00eate \u00e9tranges \u2013 Flux et InfluxQL. Ils ne sont pas tr\u00e8s pratiques pour travailler avec des s\u00e9ries temporelles par rapport \u00e0 <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, qui est pris en charge dans VictoriaMetrics. PromQL est le langage de requ\u00eates de Prometheus.<\/p>\n<p><\/p>\n<p>Et une autre diff\u00e9rence est que InfluxDB a un mod\u00e8le de donn\u00e9es un peu \u00e9trange, o\u00f9 chaque ligne peut contenir plusieurs champs avec diff\u00e9rentes ensembles d'\u00e9tiquettes. Ces lignes sont \u00e9galement divis\u00e9es en diff\u00e9rentes tables. Ces complications suppl\u00e9mentaires rendent le travail ult\u00e9rieur avec cette base plus difficile. Il est difficile de la maintenir et de la comprendre.<\/p>\n<p><\/p>\n<p>Dans VictoriaMetrics, tout est beaucoup plus simple. Chaque s\u00e9rie temporelle repr\u00e9sente une cl\u00e9-valeur. La valeur est un ensemble de points \u2013 <code>(timestamp, value)<\/code>, et la cl\u00e9 est un ensemble <code>label=value<\/code>. Il n'y a pas de s\u00e9paration entre les fields et les measurements. Cela vous permet de s\u00e9lectionner n'importe quelles donn\u00e9es, puis de les combiner, d'additionner, de soustraire, de multiplier et de diviser, contrairement \u00e0 InfluxDB, o\u00f9 les calculs entre diff\u00e9rentes s\u00e9ries ne sont toujours pas r\u00e9alis\u00e9s, autant que je sache. M\u00eame s'ils l'\u00e9taient, c'est compliqu\u00e9, il faudrait \u00e9crire beaucoup de code. <\/p>\n<p><\/p>\n<p><em>J'ai une question de clarification. Ai-je bien compris qu'il y avait un probl\u00e8me dont vous parliez, \u00e0 savoir que cet index invers\u00e9 ne tient pas en m\u00e9moire, c'est pourquoi le partitionnement a lieu ?<\/em><\/p>\n<p><\/p>\n<p>Au d\u00e9but, j'ai montr\u00e9 une impl\u00e9mentation na\u00efve d'un index invers\u00e9 sur une map standard de Go. Cette impl\u00e9mentation n'est pas adapt\u00e9e aux bases de donn\u00e9es, car cet index invers\u00e9 n'est pas sauvegard\u00e9 sur disque, tandis qu'une base de donn\u00e9es doit conserver des donn\u00e9es sur disque pour que, lors d'un red\u00e9marrage, ces donn\u00e9es restent accessibles. Dans cette impl\u00e9mentation, lors du red\u00e9marrage de l'application, votre index invers\u00e9 dispara\u00eetra. Vous perdrez l'acc\u00e8s \u00e0 toutes les donn\u00e9es, car vous ne pourrez pas les retrouver. <\/p>\n<p><\/p>\n<p><em>Bonjour ! Merci pour votre pr\u00e9sentation ! Je m'appelle Pavel. Je viens de la soci\u00e9t\u00e9 Wildberries. J'ai plusieurs questions \u00e0 vous poser. Premi\u00e8re question. Que pensez-vous, si vous aviez choisi un autre principe pour construire l'architecture de votre application et partitionn\u00e9 les donn\u00e9es par temps, il est possible que vous ayez pu faire des intersections de donn\u00e9es lors des recherches, en vous basant uniquement sur le fait qu'une partition contient des donn\u00e9es pour une p\u00e9riode de temps donn\u00e9e, c'est-\u00e0-dire pour un seul intervalle de temps et que vous n'auriez pas eu \u00e0 vous soucier des morceaux dispers\u00e9s ? Question num\u00e9ro 2 - puisque vous impl\u00e9mentez un algorithme similaire avec un bitset et tout le reste, peut-\u00eatre que vous avez essay\u00e9 d'utiliser des instructions du processeur ? Peut-\u00eatre avez-vous tent\u00e9 de faire de telles optimisations ?<\/em><\/p>\n<p><\/p>\n<p>Je vais r\u00e9pondre au deuxi\u00e8me tout de suite. Nous n'en sommes pas encore l\u00e0. Mais si besoin, nous y arriverons. Et pour le premier, quelle \u00e9tait la question ?<\/p>\n<p><\/p>\n<p><em>Vous avez discut\u00e9 de deux sc\u00e9narios. Et vous avez dit que vous aviez choisi le second avec une impl\u00e9mentation plus complexe. Et que vous ne pr\u00e9f\u00e9riez pas le premier, o\u00f9 les donn\u00e9es sont partitionn\u00e9es par temps.<\/em> <\/p>\n<p><\/p>\n<p>Oui. Dans le premier cas, le volume total de l'index serait plus \u00e9lev\u00e9, car nous devrions stocker des doublons de donn\u00e9es pour les s\u00e9ries temporelles qui se poursuivent \u00e0 travers toutes ces partitions. Et si vous avez un taux de d\u00e9sabonnement des s\u00e9ries temporelles faible, c'est-\u00e0-dire que les m\u00eames s\u00e9ries sont constamment utilis\u00e9es, dans le premier cas, nous perdrions beaucoup plus en termes d'espace disque occup\u00e9 par rapport au deuxi\u00e8me cas.<\/p>\n<p><\/p>\n<p>En effet, le partitionnement temporel est une bonne option. Prometheus l'utilise. Mais Prometheus a un autre inconv\u00e9nient. Lors de la fusion de ces morceaux de donn\u00e9es, il doit garder en m\u00e9moire les m\u00e9tadonn\u00e9es de tous les labels et s\u00e9ries temporelles. Donc, si les morceaux de donn\u00e9es qu'il fusionne sont volumineux, la consommation de m\u00e9moire augmente consid\u00e9rablement, contrairement \u00e0 VictoriaMetrics. Lors de la fusion, VictoriaMetrics ne consomme pratiquement pas de m\u00e9moire, quelques kilooctets sont utilis\u00e9s, ind\u00e9pendamment de la taille des morceaux de donn\u00e9es fusionn\u00e9s.<\/p>\n<p><\/p>\n<p><em>L'algorithme que vous utilisez utilise de la m\u00e9moire. Il marque les labels des s\u00e9ries temporelles sur lesquels il y a des valeurs. Ainsi, vous v\u00e9rifiez la pr\u00e9sence paire dans un tableau de donn\u00e9es et dans un autre. Et vous comprenez s'il y a eu intersection ou non. Dans les bases de donn\u00e9es, des curseurs, des it\u00e9rateurs sont g\u00e9n\u00e9ralement impl\u00e9ment\u00e9s, qui conservent leur \u00e9tat actuel et parcourent les donn\u00e9es tri\u00e9es, permettant ainsi une complexit\u00e9 simple pour ces op\u00e9rations.<\/em> <\/p>\n<p><\/p>\n<p>Pourquoi n'utilisons-nous pas de curseurs pour l'intersection des donn\u00e9es ?<\/p>\n<p><\/p>\n<p><em>Oui.<\/em> <\/p>\n<p><\/p>\n<p>Nous avons dans LevelDB ou dans mergeset des lignes tri\u00e9es. Nous pouvons passer avec un curseur et trouver l'intersection. Mais pourquoi ne l'utilisons-nous pas ? Parce que c'est lent. Parce que les curseurs impliquent que pour chaque ligne, une fonction doit \u00eatre appel\u00e9e. L'appel de fonction prend 5 nanosecondes. Et si vous avez 100 000 000 de lignes, cela signifie que nous perdons une demi-seconde juste pour l'appel de fonction.<\/p>\n<p><\/p>\n<p><em>\u00c7a existe, oui. Et j'ai une derni\u00e8re question. Peut-\u00eatre que cette question peut sembler un peu \u00e9trange. Pourquoi, au moment de l'arriv\u00e9e des donn\u00e9es, ne peut-on pas calculer tous les agr\u00e9gats n\u00e9cessaires et les enregistrer dans le format requis ? Pourquoi stocker de gros volumes dans des syst\u00e8mes comme VictoriaMetrics, ClickHouse, etc., pour ensuite passer beaucoup de temps \u00e0 les traiter ?<\/em><\/p>\n<p><\/p>\n<p><em>Je vais donner un exemple pour que ce soit plus clair. Supposons, comment fonctionne un petit compteur de vitesse en jouet ? Il enregistre la distance que vous avez parcourue, en l'additionnant constamment \u00e0 une valeur, et le temps \u00e0 une autre. Puis il divise. Et obtient la vitesse moyenne. Vous pouvez faire \u00e0 peu pr\u00e8s la m\u00eame chose. Additionner \u00e0 la vol\u00e9e tous les faits n\u00e9cessaires.<\/em><\/p>\n<p><\/p>\n<p>Bien, j'ai compris la question. Votre exemple a du sens. Si vous savez quels agr\u00e9gats vous avez besoin, c'est la meilleure fa\u00e7on de proc\u00e9der. Mais le probl\u00e8me, c'est que les gens conservent ces m\u00e9triques, certaines donn\u00e9es dans ClickHouse, et ils ne savent pas encore comment ils vont les agr\u00e9ger, les filtrer \u00e0 l'avenir, donc ils doivent garder toutes les donn\u00e9es brutes. Mais si vous savez que vous devez calculer une moyenne, pourquoi ne pas le faire au lieu de conserver une multitude de valeurs brutes ? Mais cela n'est valable que si vous savez exactement ce dont vous avez besoin.<\/p>\n<p><\/p>\n<p>Au fait, les bases de donn\u00e9es pour le stockage de s\u00e9ries temporelles supportent le calcul d'agr\u00e9gats. Par exemple, Prometheus supporte <noindex><a rel=\"nofollow\" href=\"https:\/\/prometheus.io\/docs\/prometheus\/latest\/configuration\/recording_rules\/\">les r\u00e8gles d'enregistrement<\/a><\/noindex>. C'est-\u00e0-dire que cela peut \u00eatre fait si vous savez quels agr\u00e9gats vous aurez besoin. Dans VictoriaMetrics, ce n'est pas encore le cas, mais Prometheus est g\u00e9n\u00e9ralement plac\u00e9 devant elle, o\u00f9 cela peut \u00eatre r\u00e9alis\u00e9 dans les r\u00e8gles d'enregistrement.<\/p>\n<p><\/p>\n<p>Par exemple, dans mon pr\u00e9c\u00e9dent emploi, il fallait compter le nombre d'\u00e9v\u00e9nements dans une fen\u00eatre glissante sur la derni\u00e8re heure. Le probl\u00e8me, c'est qu'il a fallu faire une impl\u00e9mentation personnalis\u00e9e en Go, c'est-\u00e0-dire un service pour compter cela. Ce service s'est finalement r\u00e9v\u00e9l\u00e9 non trivial, car c'est difficile \u00e0 compter. L'impl\u00e9mentation peut \u00eatre simple si vous devez calculer certains agr\u00e9gats \u00e0 des intervalles de temps fixes. Mais si vous voulez compter des \u00e9v\u00e9nements dans une fen\u00eatre glissante, ce n'est pas aussi simple qu'il n'y para\u00eet. Je pense que cela n'est toujours pas impl\u00e9ment\u00e9 dans ClickHouse ou dans les bases de donn\u00e9es de s\u00e9ries temporelles, car c'est difficile \u00e0 r\u00e9aliser.<\/p>\n<p><\/p>\n<p><em>Et une autre question. Nous avons parl\u00e9 de moyennage, et je me souviens qu'il y avait autrefois quelque chose comme Graphite avec le backend Carbon. Et il savait r\u00e9duire les anciennes donn\u00e9es, c'est-\u00e0-dire laisser un point par minute, un point par heure, etc. En principe, c'est assez pratique si nous avons besoin de donn\u00e9es brutes, disons, pour un mois, et tout le reste peut \u00eatre r\u00e9duit. Mais Prometheus et VictoriaMetrics ne prennent pas en charge cette fonctionnalit\u00e9. Est-il pr\u00e9vu de le prendre en charge ? Si non, pourquoi ?<\/em><\/p>\n<p><\/p>\n<p>Merci pour votre question. Nos utilisateurs la posent r\u00e9guli\u00e8rement. Ils demandent quand nous ajouterons la prise en charge de l'\u00e9chantillonnage (downsampling). Il y a plusieurs probl\u00e8mes. Tout d'abord, chaque utilisateur comprend sous <code>downsampling<\/code> quelque chose de diff\u00e9rent : certains souhaitent obtenir n'importe quel point arbitraire sur un intervalle donn\u00e9, d'autres d\u00e9sirent des valeurs maximales, minimales ou moyennes. Si plusieurs syst\u00e8mes \u00e9crivent des donn\u00e9es dans votre base, il est impossible de tout traiter de la m\u00eame mani\u00e8re. Il se peut que chaque syst\u00e8me n\u00e9cessite un \u00e9chantillonnage diff\u00e9rent. Et cela complique la mise en \u0153uvre.<\/p>\n<p><\/p>\n<p>Deuxi\u00e8mement, VictoriaMetrics, tout comme ClickHouse, est optimis\u00e9 pour travailler avec de grands volumes de donn\u00e9es brutes, donc il peut traiter un milliard de lignes en moins d'une seconde si vous disposez de nombreux c\u0153urs dans votre syst\u00e8me. Le balayage des points de s\u00e9ries temporelles dans VictoriaMetrics atteint 50 000 000 de points par seconde par c\u0153ur. Et cette performance se scale sur les c\u0153urs disponibles. Par exemple, si vous avez 20 c\u0153urs, vous pourrez scanner un milliard de points par seconde. Cette caract\u00e9ristique de VictoriaMetrics et ClickHouse r\u00e9duit le besoin de downsampling.<\/p>\n<p><\/p>\n<p>Une autre caract\u00e9ristique est que VictoriaMetrics compresse efficacement ces donn\u00e9es. La compression est en moyenne de 0,4 \u00e0 0,8 octet par point en production. Chaque point consiste en un timestamp + une valeur, et elle est compress\u00e9e \u00e0 moins d'un octet en moyenne. <\/p>\n<p><\/p>\n<p><em>Serguei. J'ai une question. Quelle est la dur\u00e9e minimale d'un quantum d'enregistrement ?<\/em><\/p>\n<p><\/p>\n<p>Une milliseconde. Nous avons r\u00e9cemment eu une conversation avec d'autres d\u00e9veloppeurs de bases de donn\u00e9es pour s\u00e9ries temporelles. Leur quantum minimum est d'une seconde. Dans Graphite, par exemple, c'est aussi une seconde. Dans OpenTSDB \u00e9galement, c'est une seconde. Dans InfluxDB, la pr\u00e9cision est \u00e0 la nanoseconde. Dans VictoriaMetrics, c'est une milliseconde, car dans Prometheus, c'est une milliseconde. VictoriaMetrics a initialement \u00e9t\u00e9 d\u00e9velopp\u00e9e comme un stockage \u00e0 distance pour Prometheus. Mais aujourd'hui, il peut \u00e9galement stocker des donn\u00e9es provenant d'autres syst\u00e8mes. <\/p>\n<p><\/p>\n<p>La personne \u00e0 qui je parlais dit qu'ils ont une pr\u00e9cision d'une seconde \u2014 cela leur suffit, car cela d\u00e9pend du type de donn\u00e9es qui sont enregistr\u00e9es dans la base de s\u00e9ries temporelles. Si ce sont des donn\u00e9es DevOps ou des donn\u00e9es d'infrastructure que vous collectez \u00e0 des intervalles de 30 secondes ou d'une minute, alors une pr\u00e9cision d'une seconde est suffisante, moins n'est pas n\u00e9cessaire. En revanche, si vous collectez ces donn\u00e9es \u00e0 partir de syst\u00e8mes de trading haute fr\u00e9quence, alors vous avez besoin d'une pr\u00e9cision \u00e0 la nanoseconde.<\/p>\n<p><\/p>\n<p>La pr\u00e9cision millisecondes de VictoriaMetrics convient \u00e0 la fois pour les cas DevOps et peut convenir \u00e0 la plupart des cas que j'ai mentionn\u00e9s au d\u00e9but de ma pr\u00e9sentation. La seule chose pour laquelle elle peut ne pas convenir est les syst\u00e8mes de trading haute fr\u00e9quence.<\/p>\n<p><\/p>\n<p><em>Merci ! Et une autre question. Quelle est la compatibilit\u00e9 avec PromQL ?<\/em><\/p>\n<p><\/p>\n<p>Compatibilit\u00e9 compl\u00e8te. VictoriaMetrics prend enti\u00e8rement en charge PromQL. De plus, elle ajoute une fonctionnalit\u00e9 suppl\u00e9mentaire avanc\u00e9e \u00e0 PromQL, appel\u00e9e <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/wiki\/MetricsQL\">MetricsQL<\/a><\/noindex>. Concernant cette fonctionnalit\u00e9 \u00e9tendue, il y a une pr\u00e9sentation sur YouTube. J'ai parl\u00e9 lors du Monitoring Meetup au printemps \u00e0 Saint-P\u00e9tersbourg.<\/p>\n<p><\/p>\n<p>Canal Telegram <noindex><a rel=\"nofollow\" href=\"https:\/\/t.me\/VictoriaMetrics_ru1\">VictoriaMetrics<\/a><\/noindex>.<\/p>\n<p class=\"for_users_only_msg\">Seuls les utilisateurs enregistr\u00e9s peuvent participer au sondage. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/auth\/login\/\">Connectez-vous<\/a><\/noindex>, s'il vous pla\u00eet.<\/p>\n<h2 class=\"default-block__polling-title\">Qu'est-ce qui vous emp\u00eache de passer \u00e0 VictoriaMetrics comme stockage \u00e0 long terme pour Prometheus ? (\u00c9crivez dans les commentaires, je l'ajouterai au sondage))<\/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>Je n'utilise pas Prometheus5<\/p>\n<\/li>\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent\">28,6%<\/strong>Je ne savais pas pour VictoriaMetrics2<\/p>\n<\/li>\n<\/ul>\n<p>    7 utilisateurs ont vot\u00e9. 12 utilisateurs se sont abstenus.<br \/>\n<br \/>Source : <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.2 - 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\/fr\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"fr_FR\" \/>\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\/fr\/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\udd47Optimisations Go dans VictoriaMetrics. Alexandre Valialkin | ProHoster","description":"Je vous propose de d\u00e9couvrir la transcription de la pr\u00e9sentation d'Alexandre Valialkin \u00e0 la fin de l'ann\u00e9e 2019 intitul\u00e9e \"Optimisations Go dans VictoriaMetrics\"","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","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\/fr\/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\/fr\/wp-json\/wp\/v2\/posts\/80733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=80733"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/80733\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/80734"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=80733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=80733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=80733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}