{"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\/es\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","title":{"rendered":"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Le invito a revisar la transcripci\u00f3n del informe de finales de 2019 de Alexander Valyalkin titulado \"Optimizaci\u00f3n de Go en VictoriaMetrics\"<\/strong><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/victoriametrics.com\/\">VictoriaMetrics<\/a><\/noindex> \u2014 una base de datos SQL r\u00e1pida y escalable para el almacenamiento y procesamiento de datos en forma de series temporales (un registro que forma el tiempo y un conjunto de valores correspondientes a ese tiempo, por ejemplo, obtenidos a trav\u00e9s de encuestas peri\u00f3dicas sobre el estado de sensores o la recopilaci\u00f3n de m\u00e9tricas).<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" 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>Aqu\u00ed est\u00e1 el enlace al video de esta conferencia \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\">Diapositivas<\/a><\/noindex><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/0073390d6dbcc6ab3e5305907ac6a229.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Perm\u00edtanme contarles un poco sobre m\u00ed. Soy Alexander Valyalkin. Aqu\u00ed est\u00e1 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/valyala\">mi cuenta de GitHub<\/a><\/noindex>. Me apasiona Go y la optimizaci\u00f3n del rendimiento. He escrito muchas bibliotecas \u00fatiles y otras no tanto. Comienzan con <code>fast<\/code>, o con <code>quick<\/code> de prefijo. <\/p>\n<p><\/p>\n<p>En este momento, estoy trabajando en VictoriaMetrics. \u00bfQu\u00e9 es eso y qu\u00e9 hago all\u00ed? De esto hablar\u00e9 en esta presentaci\u00f3n. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/c9194bc3fee1f615d838979bec12f7d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>El plan de la conferencia es el siguiente:<\/p>\n<p><\/p>\n<ul>\n<li>Primero, les contar\u00e9 qu\u00e9 es VictoriaMetrics. <\/li>\n<li>Luego explicar\u00e9 qu\u00e9 son las series temporales. <\/li>\n<li>Despu\u00e9s hablar\u00e9 sobre c\u00f3mo funciona una base de datos de series temporales.<\/li>\n<li>Luego, discutir\u00e9 la arquitectura de la base de datos: de qu\u00e9 se compone.<\/li>\n<li>Y finalmente, pasaremos a las optimizaciones que existen en VictoriaMetrics. Esta es la optimizaci\u00f3n del \u00edndice invertido y la optimizaci\u00f3n para la implementaci\u00f3n de bitset en Go.<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/3e0244b6b6fe952f660e4a094778921a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfAlguien en la audiencia sabe qu\u00e9 es VictoriaMetrics? Vaya, mucha gente sabe. Esa es una buena noticia. Para quienes no lo saben, es una base de datos para series temporales. Est\u00e1 basada en la arquitectura de ClickHouse, en algunos detalles de la implementaci\u00f3n de ClickHouse. Por ejemplo, detalles como: MergeTree, c\u00e1lculo paralelo en todos los n\u00facleos de CPU disponibles y optimizaci\u00f3n de rendimiento mediante el trabajo en bloques de datos que se colocan en la cach\u00e9 del procesador. <\/p>\n<p><\/p>\n<p>VictoriaMetrics ofrece la mejor compresi\u00f3n de datos en comparaci\u00f3n con otras bases de datos para series temporales. <\/p>\n<p><\/p>\n<p>Se escala verticalmente, es decir, pueden agregarse m\u00e1s procesadores, m\u00e1s memoria RAM en una sola m\u00e1quina. VictoriaMetrics utilizar\u00e1 con \u00e9xito estos recursos disponibles y aumentar\u00e1 su rendimiento lineal.<\/p>\n<p><\/p>\n<p>Adem\u00e1s, VictoriaMetrics se escala horizontalmente, es decir, pueden agregarse nodos adicionales al cl\u00faster de VictoriaMetrics y su rendimiento crecer\u00e1 casi de forma lineal.<\/p>\n<p><\/p>\n<p>Como habr\u00e1n adivinado, VictoriaMetrics es una base de datos r\u00e1pida, porque no puedo escribir sobre otras. Y est\u00e1 escrita en Go, por eso hablo de ella en este meetup.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/0121c9baead722eb1fe22fb6f900d4ad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfQui\u00e9n sabe qu\u00e9 es una serie temporal? Tambi\u00e9n muchas personas lo saben. Una serie temporal es una serie de pares <code>(timestamp, valor)<\/code>, donde estos pares est\u00e1n ordenados por tiempo. El valor consiste en un n\u00famero de punto flotante \u2013 float64.<\/p>\n<p><\/p>\n<p>Cada serie temporal est\u00e1 identificada de manera \u00fanica por una clave. \u00bfDe qu\u00e9 consiste esta clave? Consiste en un conjunto no vac\u00edo de pares clave-valor. <\/p>\n<p><\/p>\n<p>Aqu\u00ed hay un ejemplo de una serie temporal. La clave de esta serie es una lista de pares: <code>__name__=\"cpu_usage\"<\/code> \u2013 este es el nombre de la m\u00e9trica, <code>instance=\"my-server\"<\/code> \u2014 este es el ordenador en el que se recopil\u00f3 esta m\u00e9trica, <code>datacenter=\"us-east\"<\/code> \u2014 este es el centro de datos donde se encuentra este ordenador.<\/p>\n<p><\/p>\n<p>Hemos obtenido el nombre de la serie temporal, que consiste en tres pares clave-valor. Esta clave corresponde a una lista de pares <code>(timestamp, value)<\/code>. <code>t1, t3, t3, ..., tN<\/code> \u2014 estos son los timestamps, <code>10, 20, 12, ..., 15<\/code> \u2014 los valores correspondientes. Este es el uso de CPU en un momento dado para esta serie.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/76cab9db0263318d355ac607ceb7e0f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfD\u00f3nde pueden ser utilizados las series temporales? \u00bfAlguien tiene ideas? <\/p>\n<p><\/p>\n<ul>\n<li>En DevOps se pueden medir las lecturas de carga de CPU, RAM, red, rps, n\u00famero de errores, etc. <\/li>\n<li>IoT \u2013 podemos medir la temperatura, la presi\u00f3n, coordenadas geogr\u00e1ficas y algo m\u00e1s.<\/li>\n<li>Tambi\u00e9n en finanzas \u2013 podemos monitorear precios de diversas acciones y divisas. <\/li>\n<li>Adem\u00e1s, las series temporales pueden usarse para monitorear procesos de producci\u00f3n en f\u00e1bricas. Tenemos usuarios que utilizan VictoriaMetrics para monitorear turbinas e\u00f3licas, para robots.<\/li>\n<li>Las series temporales tambi\u00e9n son \u00fatiles para recoger informaci\u00f3n de sensores de diversos dispositivos. Por ejemplo, para un motor; para medir la presi\u00f3n en los neum\u00e1ticos; para medir velocidad, distancia; para medir el consumo de gasolina, etc.<\/li>\n<li>Tambi\u00e9n se pueden utilizar series temporales para monitorear aviones. Cada avi\u00f3n tiene una caja negra que recopila series temporales sobre diferentes par\u00e1metros de salud del avi\u00f3n. Las series temporales tambi\u00e9n se utilizan en la industria aeroespacial. <\/li>\n<li>Salud \u2013 esto incluye la presi\u00f3n arterial, el pulso, etc.<\/li>\n<\/ul>\n<p><\/p>\n<p>Quiz\u00e1s haya otras aplicaciones que he olvidado, pero espero que hayan entendido que las series temporales se utilizan activamente en el mundo moderno. Y su volumen de uso aumenta cada a\u00f1o.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/f1da29d372163751268b6a8f86836d04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfPara qu\u00e9 se necesita una base de datos para series temporales? \u00bfPor qu\u00e9 no se puede utilizar una base de datos relacional normal para almacenar series temporales?<\/p>\n<p><\/p>\n<p>Porque en las series temporales suele haber una gran cantidad de informaci\u00f3n que es dif\u00edcil de almacenar y procesar en bases de datos convencionales. Por eso surgieron bases de datos especializadas para series temporales. Estas bases almacenan eficientemente los puntos <code>(timestamp, value)<\/code> con una clave determinada. Proporcionan una API para leer los datos almacenados por clave, ya sea de un par clave-valor, de varios pares, o mediante expresiones regulares. Por ejemplo, si deseas encontrar la carga del CPU de todos tus servicios en el centro de datos en Am\u00e9rica, deber\u00edas usar una consulta pseudocode como esta.<\/p>\n<p><\/p>\n<p>Normalmente, las bases de datos para series temporales presentan lenguajes de consulta especializados, porque SQL no se adapta muy bien a las series temporales. Aunque hay bases de datos que soportan SQL, no es la mejor opci\u00f3n. Lenguajes 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>. Espero que alguien haya o\u00eddo al menos uno de estos lenguajes. Muchos probablemente han o\u00eddo hablar de PromQL. Este es el lenguaje de consulta de Prometheus.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/59e5b6d82a2f7861077bc5fe34519ee4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>As\u00ed es como se ve la arquitectura de una base de datos moderna para series temporales, usando VictoriaMetrics como ejemplo.<\/p>\n<p><\/p>\n<p>Consiste en dos partes. Un almacenamiento para \u00edndices invertidos y otro para valores de series temporales. Estos almacenes est\u00e1n separados. <\/p>\n<p><\/p>\n<p>Cuando llega un nuevo registro a la base de datos, primero accedemos al \u00edndice invertido para encontrar el identificador de la serie temporal correspondiente al conjunto dado de <code>label=value<\/code> para esta m\u00e9trica. Encontramos este identificador y guardamos el valor en el almacenamiento de datos.<\/p>\n<p><\/p>\n<p>Cuando se recibe una consulta para la extracci\u00f3n de datos de la TSDB, primero accedemos al \u00edndice invertido. Obtenemos todos los <code>timeseries_ids<\/code> que corresponden al conjunto dado. <code>label=value<\/code>Luego, extraemos todos los datos necesarios del almacenamiento de datos, indexados por <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/eb9b36fa3c6de2188b97b56ad3839b2c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Consideremos un ejemplo de c\u00f3mo una base de datos para series temporales procesa una consulta de selecci\u00f3n entrante.<\/p>\n<p><\/p>\n<ul>\n<li>Primero recupera todos los <code>timeseries_ids<\/code> del \u00edndice invertido que contienen los pares dados <code>label=value<\/code>, o que satisfacen una expresi\u00f3n regular determinada.<\/li>\n<li>Luego extrae todos los puntos de datos del almacenamiento de datos en el intervalo de tiempo especificado para los que fueron encontrados. <code>timeseries_ids<\/code>.<\/li>\n<li>Despu\u00e9s de eso, la base de datos realiza algunos c\u00e1lculos sobre estos puntos de datos, de acuerdo con la consulta del usuario. Y finalmente devuelve la respuesta.<\/li>\n<\/ul>\n<p><\/p>\n<p>En esta presentaci\u00f3n, les hablar\u00e9 sobre la primera parte. Esto es la b\u00fasqueda. <code>timeseries_ids<\/code> a trav\u00e9s del \u00edndice invertido. Puedes ver la segunda y tercera parte m\u00e1s tarde. <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">c\u00f3digos fuente de VictoriaMetrics<\/a><\/noindex>, o esperar a que prepare otras presentaciones \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/3a7dab707dd1defb9bc674a342052a03.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos a empezar con el \u00edndice invertido. A muchos les puede parecer simple. \u00bfQui\u00e9n sabe qu\u00e9 es un \u00edndice invertido y c\u00f3mo funciona? Oh, ya no son muchas las personas. Intentemos entender qu\u00e9 es. <\/p>\n<p><\/p>\n<p>En realidad, es todo bastante simple. Es solo un diccionario que mapea claves a valores. \u00bfQu\u00e9 es una clave? Este par <code>label=value<\/code>, donde <code>label<\/code> y <code>value<\/code> \u2013 son cadenas. Y los valores son un conjunto <code>timeseries_ids<\/code>, que incluye el par dado <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>El \u00edndice invertido permite encontrar r\u00e1pidamente todas <code>timeseries_ids<\/code>, que tienen las <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>As\u00ed como permite encontrar r\u00e1pidamente <code>timeseries_ids<\/code> series temporales para varios pares <code>label=value<\/code>, o para los pares <code>label=regexp<\/code>. \u00bfC\u00f3mo funciona esto? A trav\u00e9s de la intersecci\u00f3n de conjuntos <code>timeseries_ids<\/code> para cada par <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/837a15a078e8c9422c721f3f072c0c09.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Consideremos diferentes implementaciones del \u00edndice invertido. Empezaremos con la implementaci\u00f3n ingenua m\u00e1s simple. Se ve as\u00ed. <\/p>\n<p><\/p>\n<p>La funci\u00f3n <code>getMetricIDs<\/code> devuelve una lista de cadenas. Cada cadena contiene <code>label=value<\/code>. Esta funci\u00f3n devuelve una lista <code>metricIDs<\/code>.<\/p>\n<p><\/p>\n<p>\u00bfC\u00f3mo funciona esto? Aqu\u00ed tenemos una variable global llamada <code>invertedIndex<\/code>. Es un diccionario normal (<code>map<\/code>), que mapea una cadena a un slice de int. La cadena contiene <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Implementaci\u00f3n de la funci\u00f3n: obtenemos <code>metricIDs<\/code> para el primero <code>label=value<\/code>, luego recorremos todos los dem\u00e1s <code>label=value<\/code>, obtenemos <code>metricIDs<\/code> para ellos. Y llamamos a la funci\u00f3n <code>intersectInts<\/code>, que se explicar\u00e1 m\u00e1s adelante. Y esta funci\u00f3n devuelve la intersecci\u00f3n de estas listas.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/709beca79884a9693098781974c26f4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Como pueden ver, la implementaci\u00f3n del \u00edndice invertido no es muy compleja. Pero esta es una implementaci\u00f3n ingenua. \u00bfCu\u00e1les son sus desventajas? La principal desventaja de la implementaci\u00f3n ingenua es que este \u00edndice invertido se almacena en la memoria RAM. Despu\u00e9s de reiniciar la aplicaci\u00f3n, perdemos este \u00edndice. No hay guardado de este \u00edndice en disco. Para una base de datos, este \u00edndice invertido probablemente no sirva.<\/p>\n<p><\/p>\n<p>La segunda desventaja tambi\u00e9n est\u00e1 relacionada con la memoria. El \u00edndice invertido debe caber en la memoria RAM. Si supera el tama\u00f1o de la memoria RAM, es evidente que obtendremos un error de falta de memoria. Y el programa no funcionar\u00e1.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/495394168ac03aa8cbcf56ccad1cb2b8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Este problema se puede resolver utilizando soluciones ya existentes como <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/google\/leveldb\">LevelDB<\/a><\/noindex>, o <noindex><a rel=\"nofollow\" href=\"https:\/\/rocksdb.org\/\">RocksDB<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>En resumen, necesitamos una base de datos que permita realizar r\u00e1pidamente tres operaciones. <\/p>\n<p><\/p>\n<ul>\n<li>La primera operaci\u00f3n es la escritura <code>clave-valor<\/code> en esta base. Lo hace muy r\u00e1pido, donde <code>clave-valor<\/code> son cadenas arbitrarias. <\/li>\n<li>La segunda operaci\u00f3n es la b\u00fasqueda r\u00e1pida de un valor por una clave dada.<\/li>\n<li>Y la tercera operaci\u00f3n es la b\u00fasqueda r\u00e1pida de todos los valores por un prefijo dado. <\/li>\n<\/ul>\n<p><\/p>\n<p>LevelDB y RocksDB son bases de datos desarrolladas por Google y Facebook. Primero apareci\u00f3 LevelDB. Luego, el equipo de Facebook tom\u00f3 LevelDB y comenz\u00f3 a mejorarlo, creando RocksDB. Actualmente, casi todas las bases de datos internas en Facebook funcionan sobre RocksDB, incluso migraron MySQL a RocksDB. Lo llamaron <noindex><a rel=\"nofollow\" href=\"http:\/\/myrocks.io\/\">MyRocks<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>El \u00edndice invertido se puede implementar con LevelDB. \u00bfC\u00f3mo hacerlo? Guardamos como clave <code>label=value<\/code>. Y como valor, el identificador de la serie temporal donde est\u00e1 presente el par <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Si tenemos muchas series temporales con este par <code>label=value<\/code>, habr\u00e1 muchas l\u00edneas en esta base de datos con la misma clave y diferentes <code>timeseries_ids<\/code>. Para obtener una lista de todos los <code>timeseries_ids<\/code>, que comienzan con el dado <code>label=prefix<\/code>, hacemos un escaneo de rango, para lo cual esta base de datos est\u00e1 optimizada. Es decir, elegimos todas las l\u00edneas que comienzan con <code>label=prefix<\/code> y obtenemos los necesarios <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/a40877ecb758ac3bc1979bcd568d7e83.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Aqu\u00ed hay una implementaci\u00f3n aproximada de c\u00f3mo se ver\u00eda en Go. Tenemos un \u00edndice invertido. Esto es LevelDB.<\/p>\n<p><\/p>\n<p>La funci\u00f3n es la misma que para la implementaci\u00f3n ingenua. Casi repite l\u00ednea por l\u00ednea la implementaci\u00f3n ingenua. El \u00fanico detalle es que en lugar de acceder a <code>map<\/code> accedemos al \u00edndice invertido. Sacamos todos los valores para la primera <code>label=value<\/code>. Luego recorrremos todos los pares restantes <code>label=value<\/code> y extraemos los conjuntos correspondientes de metricIDs para ellos. Luego encontramos la intersecci\u00f3n. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/de11b15286816ada04b22727ff78e43d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Parece que todo est\u00e1 bien, pero en esta soluci\u00f3n hay desventajas. VictoriaMetrics inicialmente implement\u00f3 un \u00edndice invertido basado en LevelDB. Pero al final tuvo que abandonarlo.<\/p>\n<p><\/p>\n<p>\u00bfPor qu\u00e9? Porque LevelDB es m\u00e1s lento que la implementaci\u00f3n ingenua. En la implementaci\u00f3n ingenua, al dado clave, obtenemos inmediatamente todo el slice <code>metricIDs<\/code>. Esta es una operaci\u00f3n muy r\u00e1pida: todo el slice est\u00e1 listo para su uso.<\/p>\n<p><\/p>\n<p>En LevelDB, sin embargo, en cada llamada a la funci\u00f3n <code>GetValues<\/code> hay que recorrer todas las l\u00edneas que comienzan con <code>label=value<\/code>. Y para cada l\u00ednea obtener el valor <code>timeseries_ids<\/code>. A partir de esos <code>timeseries_ids<\/code> reunir el slice de estos <code>timeseries_ids<\/code>. Es evidente que esto es mucho m\u00e1s lento que simplemente acceder a un mapa com\u00fan por clave.<\/p>\n<p><\/p>\n<p>La segunda desventaja es que LevelDB est\u00e1 escrito en C. Llamar funciones de C desde Go no es muy r\u00e1pido. Toma cientos de nanosegundos. Esto no es muy r\u00e1pido, ya que, en comparaci\u00f3n con una llamada a una funci\u00f3n escrita en Go, que toma de 1 a 5 nanosegundos, la diferencia en el rendimiento es de decenas de veces. Para VictoriaMetrics, esta fue una desventaja fatal \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/47cace825807b7b062064c2e790dc9d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Por lo tanto, escrib\u00ed mi propia implementaci\u00f3n de un \u00edndice invertido. La llam\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">mergeset<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Mergeset se basa en la estructura de datos MergeTree. Esta estructura de datos se toma de ClickHouse. Es obvio que mergeset debe estar optimizado para b\u00fasqueda r\u00e1pida <code>timeseries_ids<\/code> por una clave espec\u00edfica. Mergeset est\u00e1 completamente escrito en Go. Puedes ver <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">el c\u00f3digo fuente de VictoriaMetrics en GitHub<\/a><\/noindex>. La implementaci\u00f3n de mergeset se encuentra en la carpeta <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">\/lib\/mergeset<\/a><\/noindex>. Puedes intentar averiguar qu\u00e9 est\u00e1 sucediendo all\u00ed.<\/p>\n<p><\/p>\n<p>La API de mergeset es muy similar a la de LevelDB y RocksDB. Es decir, permite guardar r\u00e1pidamente nuevos registros y seleccionar registros r\u00e1pidamente por un prefijo dado.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/845704ed36f70beb468907d22701af59.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Hablaremos de las desventajas de mergeset m\u00e1s adelante. Ahora vamos a hablar sobre los problemas que surgieron con VictoriaMetrics en producci\u00f3n al implementar el \u00edndice invertido.<\/p>\n<p><\/p>\n<p>\u00bfPor qu\u00e9 surgieron?<\/p>\n<p><\/p>\n<p>La primera raz\u00f3n es la alta tasa de cambio. En espa\u00f1ol, esto se traduce como la frecuente modificaci\u00f3n de series temporales. Esto ocurre cuando una serie temporal termina y comienza una nueva serie, o cuando comienzan muchas nuevas series temporales. Y esto sucede con frecuencia.<\/p>\n<p><\/p>\n<p>La segunda raz\u00f3n es la gran cantidad de series temporales. Al principio, cuando el monitoreo comenz\u00f3 a ganar popularidad, el n\u00famero de series temporales era peque\u00f1o. Por ejemplo, para cada computadora se necesita monitorear la carga de la CPU, memoria, red y disco. 4 series temporales por cada computadora. Supongamos que tienes 100 computadoras y 400 series temporales. Eso es muy poco. <\/p>\n<p><\/p>\n<p>Con el tiempo, las personas idearon que se pod\u00eda medir informaci\u00f3n m\u00e1s detallada. Por ejemplo, medir la carga no solo de toda la CPU, sino de cada n\u00facleo de procesador por separado. Si tienes 40 n\u00facleos de procesador, entonces, consecuentemente, tienes 40 veces m\u00e1s series temporales para medir la carga del procesador. <\/p>\n<p><\/p>\n<p>Pero eso no es todo. Cada n\u00facleo de procesador puede tener varios estados, como el estado idle, cuando est\u00e1 inactivo. Tambi\u00e9n hay funcionamiento en el espacio de usuario, funcionamiento en el espacio del kernel y otros estados. Y cada uno de estos estados tambi\u00e9n se puede medir como una serie temporal separada. Esto aumenta adicionalmente la cantidad de series en 7-8 veces.<\/p>\n<p><\/p>\n<p>De una m\u00e9trica obtuvimos 40 x 8 = 320 m\u00e9tricas solo para un ordenador. Multiplicamos por 100 y obtenemos 32,000 en lugar de 400. <\/p>\n<p><\/p>\n<p>Luego apareci\u00f3 Kubernetes. Y esto empeor\u00f3 a\u00fan m\u00e1s, porque en Kubernetes pueden alojarse muchos servicios diferentes. Cada servicio en Kubernetes consta de muchos pods. Y todo esto necesita ser monitoreado. Adem\u00e1s, tenemos constantes despliegues de nuevas versiones de sus servicios. Para cada nueva versi\u00f3n, se deben crear nuevas series temporales. Como resultado, la cantidad de series temporales crece exponencialmente y nos encontramos con el problema de la gran cantidad de series temporales, llamado high-cardinality. VictoriaMetrics lo maneja con \u00e9xito en comparaci\u00f3n con otras bases de datos para series temporales. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/c11b7ce6d3294ae420243f72636af4b9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Veamos m\u00e1s de cerca el high churn rate. \u00bfPor qu\u00e9 aparece el high churn rate en producci\u00f3n? Porque algunos valores de etiquetas y tags cambian constantemente.<\/p>\n<p><\/p>\n<p>Por ejemplo, tomemos Kubernetes, en el que hay un concepto de <code>deployment<\/code>, es decir, cuando se despliega una nueva versi\u00f3n de su aplicaci\u00f3n. Por alguna raz\u00f3n, los desarrolladores de Kubernetes decidieron agregar el id del deployment en la etiqueta.<\/p>\n<p><\/p>\n<p>\u00bfA qu\u00e9 llev\u00f3 esto? A que en cada nuevo deployment, todos nuestros viejos series temporales se interrumpen, y en su lugar comienzan nuevas series temporales con un nuevo valor de etiqueta. <code>deployment_id<\/code>. Puede haber cientos de miles e incluso millones de tales series.<\/p>\n<p><\/p>\n<p>Una caracter\u00edstica importante de todo esto es que el n\u00famero total de series temporales est\u00e1 en aumento, pero el n\u00famero de series temporales que est\u00e1n activas en este momento, a las que llegan datos, se mantiene constante. Este estado se llama high churn rate.<\/p>\n<p><\/p>\n<p>El principal problema del high churn rate es garantizar una velocidad de b\u00fasqueda constante de todas las series temporales seg\u00fan un conjunto dado de etiquetas durante un intervalo de tiempo determinado. Generalmente, este intervalo de tiempo es el \u00faltimo hora o el \u00faltimo d\u00eda. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/ac18482b87cc37624d163b30c68cf7be.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfC\u00f3mo resolver este problema? Aqu\u00ed hay una primera opci\u00f3n. Se trata de dividir el \u00edndice inverso en partes independientes por tiempo. Es decir, despu\u00e9s de un cierto intervalo de tiempo, dejamos de trabajar con el \u00edndice inverso actual y creamos un nuevo \u00edndice inverso. Pasa otro intervalo de tiempo, creamos otro y otro m\u00e1s. <\/p>\n<p><\/p>\n<p>Y al seleccionar de estos \u00edndices inversos, encontramos un conjunto de \u00edndices inversos que caen dentro del intervalo especificado. Y, por lo tanto, elegimos de all\u00ed los ID de las series temporales. <\/p>\n<p><\/p>\n<p>Esto permite ahorrar recursos, porque no tenemos que revisar partes que no caen dentro del intervalo especificado. Es decir, normalmente, si seleccionamos datos de la \u00faltima hora, pasamos por alto las solicitudes de los intervalos temporales anteriores. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/1afa3a88caa7423941ae3494b9cec706.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Hay otra opci\u00f3n para resolver este problema. Es almacenar para cada d\u00eda una lista separada de ID de series temporales que se encontraron durante ese d\u00eda.<\/p>\n<p><\/p>\n<p>La ventaja de esta soluci\u00f3n en comparaci\u00f3n con la anterior es que no duplicamos la informaci\u00f3n sobre las series temporales que no desaparecen con el tiempo. Est\u00e1n presentes de manera constante y no cambian. <\/p>\n<p><\/p>\n<p>La desventaja es que esta soluci\u00f3n es m\u00e1s complicada de implementar y m\u00e1s dif\u00edcil de depurar. Y VictoriaMetrics eligi\u00f3 esta soluci\u00f3n. Esto sucedi\u00f3 hist\u00f3ricamente. Esta soluci\u00f3n tambi\u00e9n se desempe\u00f1a bastante bien, en comparaci\u00f3n con la anterior. Porque esta soluci\u00f3n no se implement\u00f3 debido a que hab\u00eda que duplicar datos en cada partici\u00f3n para las series temporales que no cambian, es decir, que no desaparecen con el tiempo. VictoriaMetrics fue optimizada principalmente para el consumo de espacio en disco, y la implementaci\u00f3n anterior deterioraba el consumo de espacio en disco. En cambio, esta implementaci\u00f3n es m\u00e1s adecuada para minimizar el consumo de espacio en disco, por lo que fue elegida. <\/p>\n<p><\/p>\n<p>Tuve que luchar con eso. La lucha consisti\u00f3 en que en esta implementaci\u00f3n a\u00fan necesita seleccionar una cantidad mucho mayor <code>timeseries_ids<\/code> de datos que cuando el \u00edndice inverso est\u00e1 dividido por tiempo.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/5b10e61aaa803428ed3bfe31815f09d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfC\u00f3mo resolvimos este problema? Lo resolvimos de una manera original: guardando m\u00faltiples identificadores de series temporales en cada registro del \u00edndice inverso en lugar de un \u00fanico identificador. Es decir, tenemos una clave <code>label=value<\/code>, que se encuentra en cada serie temporal. Y ahora estamos guardando varios <code>timeseries_ids<\/code> en un solo registro.<\/p>\n<p><\/p>\n<p>Aqu\u00ed hay un ejemplo. Antes ten\u00edamos N registros, y ahora tenemos un registro con un prefijo igual al de todos los dem\u00e1s. El valor del registro anterior contiene todos los id de las series temporales. <\/p>\n<p><\/p>\n<p>Esto ha permitido aumentar la velocidad de escaneo de dicho \u00edndice invertido hasta 10 veces. Y ha permitido reducir el consumo de memoria para el cach\u00e9, porque ahora almacenamos la cadena <code>label=value<\/code> solo una vez en el cach\u00e9 junto con N veces. Y esta cadena puede ser grande si tienes largas cadenas en las etiquetas y labels que Kubernetes tiende a introducir all\u00ed.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/ae4f38a440203bad2d03cf2abd1faafe.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Otra opci\u00f3n para acelerar la b\u00fasqueda en el \u00edndice invertido es el sharding. La creaci\u00f3n de varios \u00edndices invertidos en lugar de uno y el sharding de datos entre ellos por clave. Esto es un conjunto <code>clave=valor<\/code> de pares. Es decir, obtenemos varios \u00edndices invertidos independientes que podemos consultar en paralelo en varios procesadores. Las implementaciones anteriores solo permit\u00edan operar en modo de un solo procesador, es decir, escanear datos solo en un n\u00facleo. Esta soluci\u00f3n permite escanear datos simult\u00e1neamente en varios n\u00facleos, como a ClickHouse le gusta hacer. Esto planeamos implementar.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/5ced6446efbf8ded8d7fb91694a999b0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Y ahora volvamos a lo nuestro \u2013 a la funci\u00f3n de intersecci\u00f3n <code>timeseries_ids<\/code>. Veamos qu\u00e9 implementaciones podr\u00edan existir. Esta funci\u00f3n permite encontrar <code>timeseries_ids<\/code> para un conjunto dado <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/073a155018a91120c6996c324772f9af.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>La primera opci\u00f3n es una implementaci\u00f3n na\u00efve. Dos ciclos anidados. Aqu\u00ed recibimos como entrada las funciones <code>intersectInts<\/code> dos slices \u2013 <code>a<\/code> y <code>b<\/code>. En la salida, debe devolvernos la intersecci\u00f3n de estos slices.<\/p>\n<p><\/p>\n<p>La implementaci\u00f3n na\u00efve se ve as\u00ed. Recorremos todos los valores del slice <code>a<\/code>, dentro de este ciclo recorremos todos los valores del slice <code>b<\/code>. Y los comparamos. Si coinciden, significa que hemos encontrado la intersecci\u00f3n. Y lo guardamos en <code>resultado<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/b89dcbb4f6d73377f9cf4f50169d6dd7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfCu\u00e1les son las desventajas? La complejidad cuadr\u00e1tica es su principal desventaja. Por ejemplo, si los tama\u00f1os del slice <code>a<\/code> y <code>b<\/code> son de un mill\u00f3n, esta funci\u00f3n nunca te devolver\u00e1 una respuesta. Porque necesitar\u00eda hacer un bill\u00f3n de iteraciones, lo cual es demasiado incluso para las computadoras modernas.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/bad26b0092a2fd7fc813bcb79a9138ce.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>La segunda implementaci\u00f3n se basa en un map. Creamos un map. Colocamos en este map todos los valores del slice <code>a<\/code>. Luego, recorremos con un ciclo separado el slice <code>b<\/code>. Y verificamos si este valor del slice <code>b<\/code> en map. Si existe, lo a\u00f1adimos al resultado. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/1e610acf641a9cfe4c80aac1fe26044c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00bfCu\u00e1les son las ventajas? La ventaja es que aqu\u00ed solo hay complejidad lineal. Es decir, la funci\u00f3n se ejecutar\u00e1 mucho m\u00e1s r\u00e1pido para tama\u00f1os grandes de slices. Para un slice de un mill\u00f3n de tama\u00f1o, esta funci\u00f3n se ejecutar\u00e1 en 2 millones de iteraciones, a diferencia de un bill\u00f3n de iteraciones, como en la funci\u00f3n anterior.<\/p>\n<p><\/p>\n<p>Sin embargo, la desventaja es que esta funci\u00f3n requiere m\u00e1s memoria para crear este map.<\/p>\n<p><\/p>\n<p>La segunda desventaja es el gran overhead en el hashing. Esta desventaja no es muy obvia. Y para nosotros tampoco fue muy obvia, por eso al principio en VictoriaMetrics la implementaci\u00f3n de la intersecci\u00f3n se hac\u00eda a trav\u00e9s de map. Pero luego el perfilado mostr\u00f3 que la mayor parte del tiempo del procesador se gasta en escribir en el map y en verificar la existencia de un valor en este map.<\/p>\n<p><\/p>\n<p>\u00bfPor qu\u00e9 se gasta tiempo de procesador en estos lugares? Porque en esas l\u00edneas Go realiza la operaci\u00f3n de hashing. Es decir, calcula el hash de la clave para luego acceder por el \u00edndice dado en el HashMap. La operaci\u00f3n de c\u00e1lculo del hash se realiza en decenas de nanosegundos. Esto es lento para VictoriaMetrics.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/969f10d875d88998e958894ca28b1181.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Decid\u00ed implementar un bitset, optimizado espec\u00edficamente para este caso. As\u00ed es como se ve ahora la intersecci\u00f3n de dos slices. Aqu\u00ed creamos un bitset. A\u00f1adimos elementos del primer slice. Luego verificamos la existencia de esos elementos en el segundo slice. Y los a\u00f1adimos al resultado. Es decir, no se diferencia mucho del ejemplo anterior. La \u00fanica diferencia es que aqu\u00ed hemos reemplazado el acceso al map por funciones personalizadas. <code>add<\/code> y <code>has<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/b4ec0753d30d9684868a031f605632c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A primera vista parece que esto deber\u00eda funcionar m\u00e1s lento, si antes se utilizaba un map est\u00e1ndar y aqu\u00ed se llaman a algunas funciones, pero el perfilado muestra que esta cosa funciona 10 veces m\u00e1s r\u00e1pido que un map est\u00e1ndar para el caso de VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Adem\u00e1s, utiliza mucho menos memoria en comparaci\u00f3n con la implementaci\u00f3n en map. Porque aqu\u00ed almacenamos bits en lugar de valores de ocho bytes.<\/p>\n<p><\/p>\n<p>La desventaja de esta implementaci\u00f3n es que no es tan obvia, no es trivial. <\/p>\n<p><\/p>\n<p>Otra desventaja que muchos pueden no notar es que esta implementaci\u00f3n puede fallar en algunos casos. Es decir, est\u00e1 optimizada para un caso espec\u00edfico, para este caso de intersecci\u00f3n de ids de series temporales de VictoriaMetrics. Esto no significa que sea adecuada para todos los casos. Si se utiliza incorrectamente, obtendremos no un aumento de rendimiento, sino un error de falta de memoria y una disminuci\u00f3n en el rendimiento. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/94bd174afe43e67b3cf279dc7a1be17c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Consideremos la implementaci\u00f3n de esta estructura. Si deseas verlo, se encuentra en las fuentes de VictoriaMetrics, en la carpeta <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/uint64set\">lib\/uint64set<\/a><\/noindex>. Est\u00e1 optimizada espec\u00edficamente para el caso de VictoriaMetrics, donde <code>timeseries_id<\/code> es un valor de 64 bits, donde los primeros 32 bits son constantes y solo cambian los \u00faltimos 32 bits.<\/p>\n<p><\/p>\n<p>Esta estructura de datos no se almacena en disco, solo funciona en memoria. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/e24ca65f860e2b04037e073f7acae0c1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Aqu\u00ed est\u00e1 su API. No es muy complicada. La API est\u00e1 ajustada espec\u00edficamente para el ejemplo de uso de VictoriaMetrics. Es decir, no hay funciones innecesarias. Aqu\u00ed est\u00e1n las funciones que se utilizan expl\u00edcitamente en VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Hay una funci\u00f3n <code>add<\/code>, que agrega nuevos valores. Hay una funci\u00f3n <code>has<\/code>, que verifica nuevos valores. Y hay una funci\u00f3n <code>del<\/code>, que elimina valores. Hay una funci\u00f3n auxiliar <code>len<\/code>, que devuelve el tama\u00f1o del conjunto. La funci\u00f3n <code>clone<\/code> clona el conjunto. Y la funci\u00f3n <code>appendto<\/code> transforma este conjunto en un slice <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/874b04042d13f342d39f8f393e7fb78c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>As\u00ed es como se ve la implementaci\u00f3n de esta estructura de datos. En el conjunto hay dos elementos:<\/p>\n<p><\/p>\n<ul>\n<li>\n<p><code>ItemsCount<\/code> \u2013 es un campo auxiliar para devolver r\u00e1pidamente el n\u00famero de elementos en el conjunto. Podr\u00eda prescindirse de este campo auxiliar, pero se tuvo que a\u00f1adir aqu\u00ed porque VictoriaMetrics solicita a menudo la longitud del bitset en sus algoritmos.<\/p>\n<p>\n<\/li>\n<li>\n<p>El segundo campo es <code>buckets<\/code>. Es un slice de la estructura <code>bucket32<\/code>. En cada estructura se almacena <code>hi<\/code> el campo. Estos son los 32 bits superiores. Y dos slices \u2014 <code>b16his<\/code> y <code>buckets<\/code> de <code>bucket16<\/code> estructuras. <\/p>\n<p>\n<\/li>\n<\/ul>\n<p><\/p>\n<p>Aqu\u00ed se almacenan los 16 bits superiores de la segunda parte de la estructura de 64 bits. Y aqu\u00ed se almacenan los bitsets para los 16 bits inferiores de cada byte. <\/p>\n<p><\/p>\n<p><code>Bucket64<\/code> consiste en un arreglo <code>uint64<\/code>. La longitud se calcula usando estas constantes. En uno <code>bucket16<\/code> se pueden almacenar como m\u00e1ximo <code>2^16=65536<\/code> bits. Si eso se divide entre 8, son 8 kilobytes. Si se divide nuevamente entre 8, son 1000 <code>uint64<\/code> valores. Es decir, <code>Bucket16<\/code> \u2013 es nuestra estructura de 8 kilobytes. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/1cca4e6bfafa3408a3c95e0118e26d80.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Veamos c\u00f3mo se implementa uno de los m\u00e9todos de esta estructura para agregar un nuevo valor. <\/p>\n<p><\/p>\n<p>Todo comienza con <code>uint64<\/code> valores. Calculamos los 32 bits superiores, calculamos los 32 bits inferiores. Revisamos todos <code>buckets<\/code>. Comparamos los 32 bits superiores en cada bucket con el valor que estamos a\u00f1adiendo. Y si coinciden, llamamos a la funci\u00f3n <code>add<\/code> en la estructura b32 <code>buckets<\/code>. Y a\u00f1adimos all\u00ed los 32 bits inferiores. Y si esto devolvi\u00f3 <code>true<\/code>, entonces significa que hemos a\u00f1adido ese valor all\u00ed y no ten\u00edamos ese valor previamente. Si devuelve <code>false<\/code>, entonces ese valor ya exist\u00eda. Luego incrementamos la cantidad de elementos en la estructura. <\/p>\n<p><\/p>\n<p>Si no encontramos el necesario <code>bucket<\/code> con el valor hi correspondiente, entonces llamamos a la funci\u00f3n <code>addAlloc<\/code>, que asigna un nuevo <code>bucket<\/code>, a\u00f1adi\u00e9ndolo a la estructura de buckets.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/c5d1e5de767f47c7c40e01e12b6d0617.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esta es la implementaci\u00f3n de la funci\u00f3n <code>b32.add<\/code>. Es similar a la implementaci\u00f3n anterior. Calculamos los 16 bits superiores, los 16 bits inferiores.<\/p>\n<p><\/p>\n<p>Luego revisamos todos los 16 bits superiores. Buscamos coincidencias. Y al coincidir, llamamos al m\u00e9todo add, que revisaremos en la siguiente p\u00e1gina para <code>bucket16<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/e98c4ef316f3a894fd7019c3c85696f7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Y aqu\u00ed est\u00e1 el nivel m\u00e1s bajo, que debe estar optimizado al m\u00e1ximo. Calculamos para <code>uint64<\/code> el valor id en el slice bit, as\u00ed como <code>bitmask<\/code>. Esta es una m\u00e1scara para este valor de 64 bits, que puede verificar la presencia de este bit, o establecerlo. Comprobamos la existencia de este bit, lo establecemos y devolvemos su presencia. Esta es nuestra implementaci\u00f3n, que ha permitido acelerar la operaci\u00f3n de intersecci\u00f3n de ids de series temporales por 10 veces en comparaci\u00f3n con los mapas normales.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/4dfa5a6142829a3551be22d6c9c3ab92.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>En VictoriaMetrics, adem\u00e1s de esta optimizaci\u00f3n, hay muchas otras optimizaciones. La mayor\u00eda de estas optimizaciones no se a\u00f1adieron al azar, sino despu\u00e9s de perfilar el c\u00f3digo en producci\u00f3n.<\/p>\n<p><\/p>\n<p>Esta es la regla principal de la optimizaci\u00f3n: no a\u00f1adir optimizaci\u00f3n suponiendo que aqu\u00ed habr\u00e1 un cuello de botella, porque podr\u00eda resultarse que no hay ning\u00fan cuello de botella. La optimizaci\u00f3n generalmente empeora la calidad del c\u00f3digo. Por lo tanto, vale la pena optimizar solo despu\u00e9s de perfilar y, preferiblemente, en producci\u00f3n, para que sean datos reales. A quienes les interese, pueden ver las fuentes de VictoriaMetrics y estudiar otras optimizaciones que hay all\u00ed.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Vyalakin\" src=\"\/wp-content\/uploads\/2020\/05\/7585c4dc782627bac5b649e6a55e2ffb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><em>Tengo una pregunta sobre bitset. Muy parecido a la implementaci\u00f3n de C++ vector bool, bitset optimizado. \u00bfTomaron la implementaci\u00f3n de all\u00ed?<\/em><\/p>\n<p><\/p>\n<p>No, no es de all\u00ed. Al implementar este bitset, me gui\u00e9 por el conocimiento de la estructura de estos ids de series temporales que se utilizan en VictoriaMetrics. La estructura es tal que los 32 bits superiores son en su mayor parte constantes. Los 32 bits inferiores pueden cambiar. Cuanto m\u00e1s bajo es el bit, m\u00e1s a menudo puede cambiar. Por lo tanto, esta implementaci\u00f3n est\u00e1 optimizada para esta estructura de datos. La implementaci\u00f3n en C++, hasta donde s\u00e9, est\u00e1 optimizada para el caso general. Si se hace una optimizaci\u00f3n para el caso general, eso significa que no ser\u00e1 la m\u00e1s \u00f3ptima para un caso espec\u00edfico.<\/p>\n<p><\/p>\n<p>Te aconsejo que mires la presentaci\u00f3n de Alexey Milovid. Hace aproximadamente un mes habl\u00f3 sobre optimizaciones en ClickHouse para especializaciones concretas. \u00c9l menciona que, en general, la implementaci\u00f3n en C++ o cualquier otra implementaci\u00f3n est\u00e1 dise\u00f1ada para un buen funcionamiento promedio. Puede funcionar peor que una implementaci\u00f3n especializada basada en conocimientos concretos, como en nuestro caso, cuando sabemos que los 32 bits superiores son en su mayor\u00eda constantes.<\/p>\n<p><\/p>\n<p><em>Tengo una segunda pregunta. \u00bfCu\u00e1l es la diferencia fundamental con InfluxDB?<\/em><\/p>\n<p><\/p>\n<p>Hay muchas diferencias fundamentales. En t\u00e9rminos de rendimiento y consumo de memoria, InfluxDB muestra en las pruebas un consumo de memoria 10 veces mayor para series temporales de alta cardinalidad, cuando tienes muchas, por ejemplo, millones. Por ejemplo, VictoriaMetrics consume 1 GB por mill\u00f3n de series activas, mientras que InfluxDB consume 10 GB. Y esa es una gran diferencia. <\/p>\n<p><\/p>\n<p>La segunda diferencia fundamental es que InfluxDB tiene lenguajes de consulta extra\u00f1os: Flux e InfluxQL. No son muy convenientes para trabajar con series temporales en comparaci\u00f3n con <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, que es soportado en VictoriaMetrics. PromQL es el lenguaje de consultas de Prometheus.<\/p>\n<p><\/p>\n<p>Y otra diferencia es que InfluxDB tiene un modelo de datos algo extra\u00f1o, donde cada l\u00ednea puede contener varios fields con diferentes conjuntos de etiquetas. Estas l\u00edneas se dividen adem\u00e1s en varias tablas. Estas complicaciones adicionales hacen que el trabajo posterior con esta base sea m\u00e1s complicado. Es dif\u00edcil de mantener y entender.<\/p>\n<p><\/p>\n<p>En VictoriaMetrics todo es mucho m\u00e1s simple. Cada serie temporal se representa como una clave-valor. El valor es un conjunto de puntos - <code>(timestamp, value)<\/code>, y la clave es un conjunto <code>label=value<\/code>. No hay divisi\u00f3n entre fields y measurements. Esto permite elegir cualquier dato y luego combinarlos, sumarlos, restarlos, multiplicarlos, dividirlos, a diferencia de InfluxDB, donde los c\u00e1lculos entre diferentes series a\u00fan no est\u00e1n implementados, hasta donde yo s\u00e9. Incluso si est\u00e1n implementados, es complicado; hay que escribir mucho c\u00f3digo. <\/p>\n<p><\/p>\n<p><em>Tengo una pregunta aclaratoria. \u00bfEntend\u00ed correctamente que hubo alg\u00fan problema del que hablaste, que este \u00edndice invertido no cabe en memoria, por lo que se est\u00e1 particionando?<\/em><\/p>\n<p><\/p>\n<p>Al principio, mostr\u00e9 una implementaci\u00f3n ingenua de un \u00edndice invertido en un mapa est\u00e1ndar de Go. Esta implementaci\u00f3n no es adecuada para bases de datos, porque este \u00edndice invertido no se guarda en el disco, y una base de datos debe guardar en disco para que los datos permanezcan accesibles tras un reinicio. En esta implementaci\u00f3n, al reiniciar la aplicaci\u00f3n, perder\u00e1 el \u00edndice invertido. Y perder\u00e1 acceso a todos los datos, porque no podr\u00e1 encontrarlos. <\/p>\n<p><\/p>\n<p><em>\u00a1Hola! Gracias por la presentaci\u00f3n. Me llamo Pavel. Soy de la empresa Wildberries. Tengo varias preguntas para ti. La primera pregunta. \u00bfCrees que si hubieras elegido otro principio al construir la arquitectura de tu aplicaci\u00f3n y particionado los datos por tiempo, tal vez habr\u00edas podido hacer intersecciones de datos al buscar, bas\u00e1ndote solo en que en una partici\u00f3n se encuentran datos de un mismo per\u00edodo de tiempo, es decir, de un mismo intervalo de tiempo, y no tendr\u00edas que preocuparte de que tus trozos est\u00e1n distribuidos de manera diferente? La pregunta n\u00famero 2: dado que implementas un algoritmo similar con bitset y todo lo dem\u00e1s, \u00bfquiz\u00e1s has intentado usar instrucciones del procesador? \u00bfQuiz\u00e1s has intentado tales optimizaciones?<\/em><\/p>\n<p><\/p>\n<p>Responder\u00e9 inmediatamente a la segunda. A\u00fan no hemos llegado a eso. Pero si es necesario, llegaremos. \u00bfY la primera, cu\u00e1l era la pregunta?<\/p>\n<p><\/p>\n<p><em>Discutiste dos escenarios. Y dijiste que elegiste el segundo con una implementaci\u00f3n m\u00e1s complicada. Y no preferiste el primero, donde los datos est\u00e1n particionados por tiempo.<\/em> <\/p>\n<p><\/p>\n<p>S\u00ed. En el primer caso, el volumen total del \u00edndice ser\u00eda mayor, porque en cada partici\u00f3n tendr\u00edamos que almacenar duplicados de datos para aquellas series temporales que se extienden a trav\u00e9s de todas estas particiones. Y si su tasa de cambio en las series temporales es baja, es decir, se utilizan constantemente las mismas series, entonces en el primer caso perder\u00edamos mucho m\u00e1s en t\u00e9rminos de espacio en disco en comparaci\u00f3n con el segundo caso.<\/p>\n<p><\/p>\n<p>As\u00ed es, la partici\u00f3n temporal es una buena opci\u00f3n. Prometheus la utiliza. Pero Prometheus tiene otra desventaja. Al fusionar estos fragmentos de datos, necesita mantener en memoria la metainformaci\u00f3n de todas las etiquetas y series temporales. Por lo tanto, si los fragmentos de datos que est\u00e1 fusionando son grandes, el consumo de memoria aumenta mucho durante la fusi\u00f3n, a diferencia de VictoriaMetrics. Durante la fusi\u00f3n, VictoriaMetrics no consume memoria, solo se utilizan unos pocos kilobytes, independientemente del tama\u00f1o de los fragmentos de datos que se est\u00e1n fusionando.<\/p>\n<p><\/p>\n<p><em>El algoritmo que est\u00e1 utilizando consume memoria. Se marcan las etiquetas de serie temporal en las que hay valores. As\u00ed es como verifica la existencia coincidente en un conjunto de datos y en otro. Y entiende si ha habido intersecci\u00f3n o no. Normalmente, en las bases de datos se implementan cursores, iteradores que mantienen su estado actual y recorren los datos ordenados, lo que le permite tener una complejidad simple en esas operaciones.<\/em> <\/p>\n<p><\/p>\n<p>\u00bfPor qu\u00e9 no usamos cursores para la intersecci\u00f3n de datos?<\/p>\n<p><\/p>\n<p><em>S\u00ed.<\/em> <\/p>\n<p><\/p>\n<p>En LevelDB o en mergeset, se almacenan precisamente las filas ordenadas. Podemos usar un cursor para pasar y encontrar la intersecci\u00f3n. Pero, \u00bfpor qu\u00e9 no lo utilizamos? Porque es lento. Porque los cursores implican que para cada fila hay que llamar a una funci\u00f3n. La llamada a la funci\u00f3n toma 5 nanosegundos. Y si tiene 100,000,000 filas, eso significa que gastamos medio segundo solo en llamar a la funci\u00f3n.<\/p>\n<p><\/p>\n<p><em>S\u00ed, eso existe. Y tengo una \u00faltima pregunta. Puede que esta pregunta suene un poco extra\u00f1a. \u00bfPor qu\u00e9 en el momento de la llegada de los datos no se pueden calcular todos los agregados necesarios y guardarlos en la forma necesaria? \u00bfPor qu\u00e9 almacenar enormes vol\u00famenes en sistemas como VictoriaMetrics, ClickHouse, etc., solo para gastar luego mucho tiempo en ellos?<\/em><\/p>\n<p><\/p>\n<p><em>Voy a dar un ejemplo para que sea m\u00e1s claro. Supongamos, \u00bfc\u00f3mo funciona un peque\u00f1o veloc\u00edmetro de juguete? Registra la distancia que has recorrido, sumando continuamente a una medida el tiempo. Y divide. Y obtiene la velocidad promedio. Se puede hacer algo parecido. Acumular sobre la marcha todos los hechos necesarios.<\/em><\/p>\n<p><\/p>\n<p>Bien, entiendo la pregunta. Tu ejemplo tiene sentido. Si sabes qu\u00e9 agregados necesitas, esa es la mejor implementaci\u00f3n. Pero el problema es que las personas almacenan estas m\u00e9tricas, algunos datos en ClickHouse y no saben a\u00fan c\u00f3mo van a agregarlos o filtrarlos en el futuro, por lo que tienen que guardar todos los datos en crudo. Pero si sabes que necesitas calcular algo promedio, \u00bfpor qu\u00e9 no calcularlo, en lugar de guardar un mont\u00f3n de valores en crudo? Pero esto solo se aplica si sabes exactamente lo que necesitas.<\/p>\n<p><\/p>\n<p>Por cierto, las bases de datos para el almacenamiento de series temporales soportan el conteo de agregados. Por ejemplo, Prometheus soporta <noindex><a rel=\"nofollow\" href=\"https:\/\/prometheus.io\/docs\/prometheus\/latest\/configuration\/recording_rules\/\">reglas de grabaci\u00f3n<\/a><\/noindex>. Es decir, se puede hacer esto si sabes qu\u00e9 agregados necesitar\u00e1s. En VictoriaMetrics esto actualmente no est\u00e1 disponible, pero normalmente se coloca Prometheus delante, en el que se puede hacer esto con las reglas de grabaci\u00f3n.<\/p>\n<p><\/p>\n<p>Por ejemplo, en mi trabajo anterior necesitaba contar la cantidad de eventos en una ventana deslizante durante la \u00faltima hora. El problema era que tuve que hacer una implementaci\u00f3n personalizada en Go, es decir, un servicio para contar esto. Este servicio result\u00f3 ser no trivial, porque es complicado de calcular. La implementaci\u00f3n puede ser sencilla si necesitas contar algunos agregados en intervalos de tiempo fijos. Sin embargo, si quieres contar eventos en una ventana deslizante, no es tan f\u00e1cil como parece. Creo que esto a\u00fan no se ha implementado en ClickHouse o en bases de datos de series temporales, porque es complicado de implementar.<\/p>\n<p><\/p>\n<p><em>Y otra pregunta. Ahora hemos hablado sobre el promedio, y record\u00e9 que alguna vez existi\u00f3 algo llamado Graphite con un backend Carbon. Y pod\u00eda reducir los datos antiguos, es decir, dejar un punto por minuto, un punto por hora, etc. En principio, esto es bastante conveniente si necesitamos datos en crudo, digamos, durante un mes, y todo lo dem\u00e1s se puede reducir. Pero Prometheus y VictoriaMetrics no soportan esta funcionalidad. \u00bfEst\u00e1 previsto soportarlo? Si no, \u00bfpor qu\u00e9?<\/em><\/p>\n<p><\/p>\n<p>Gracias por su pregunta. Nuestros usuarios la hacen peri\u00f3dicamente. Preguntan cu\u00e1ndo a\u00f1adiremos soporte para el muestreo (downsampling). Aqu\u00ed hay varios problemas. En primer lugar, cada usuario entiende por <code>downsampling<\/code> algo diferente: algunos quieren obtener cualquier punto arbitrario en un intervalo determinado, otros desean valores m\u00e1ximos, m\u00ednimos o promedios. Si en su base escriben datos muchos sistemas, no se pueden agrupar todos de la misma manera. Puede suceder que para cada sistema se necesite utilizar un muestreo diferente. Y eso es complicado de implementar.<\/p>\n<p><\/p>\n<p>Y segundo, es que VictoriaMetrics, al igual que ClickHouse, est\u00e1 optimizada para trabajar con grandes vol\u00famenes de datos en bruto, por lo que puede procesar mil millones de filas en menos de un segundo, si tiene muchos n\u00facleos en su sistema. El escaneo de puntos de una serie temporal en VictoriaMetrics es de 50,000,000 de puntos por segundo por n\u00facleo. Y este rendimiento se escala en los n\u00facleos disponibles. Es decir, si tiene 20 n\u00facleos, por ejemplo, puede escanear mil millones de puntos por segundo. Y esta caracter\u00edstica de VictoriaMetrics y ClickHouse reduce la necesidad de muestreo.<\/p>\n<p><\/p>\n<p>Otra propiedad es que VictoriaMetrics comprime los datos de manera efectiva. La compresi\u00f3n, en promedio, en producci\u00f3n es de 0.4 a 0.8 bytes por punto. Cada punto es un timestamp + valor. Y se comprime en menos de un byte en promedio. <\/p>\n<p><\/p>\n<p><em>Sergiy. Tengo una pregunta. \u00bfCu\u00e1l es el m\u00ednimo intervalo de tiempo de registro?<\/em><\/p>\n<p><\/p>\n<p>Una mil\u00e9sima de segundo. Recientemente tuvimos una conversaci\u00f3n con otros desarrolladores de bases de datos para series temporales. Su m\u00ednimo intervalo de tiempo es de un segundo. En Graphite, por ejemplo, tambi\u00e9n es un segundo. En OpenTSDB tambi\u00e9n es un segundo. En InfluxDB, la precisi\u00f3n es de nanosegundos. En VictoriaMetrics, es de una mil\u00e9sima de segundo, porque en Prometheus es de una mil\u00e9sima de segundo. Y VictoriaMetrics se desarroll\u00f3 inicialmente como almacenamiento remoto para Prometheus. Pero ahora puede almacenar datos de otros sistemas. <\/p>\n<p><\/p>\n<p>La persona con la que habl\u00e9 dice que ellos tienen precisi\u00f3n de un segundo \u2014 eso les basta, porque depende del tipo de datos que se guardan en la base de datos de series temporales. Si son datos de DevOps o datos de infraestructura, donde se recogen a intervalos de 30 segundos o un minuto, entonces la precisi\u00f3n de un segundo es suficiente, no necesitan menos. Pero si recoge estos datos de sistemas de trading de alta frecuencia, entonces se necesita precisi\u00f3n de nanosegundos.<\/p>\n<p><\/p>\n<p>La precisi\u00f3n en milisegundos de VictoriaMetrics es adecuada tanto para casos de DevOps como para la mayor\u00eda de los casos que mencion\u00e9 al principio de la presentaci\u00f3n. La \u00fanica excepci\u00f3n podr\u00eda ser para sistemas de trading de alta frecuencia.<\/p>\n<p><\/p>\n<p><em>\u00a1Gracias! Y otra pregunta. \u00bfCu\u00e1l es la compatibilidad en PromQL?<\/em><\/p>\n<p><\/p>\n<p>Compatibilidad total. VictoriaMetrics ofrece soporte completo para PromQL. Adem\u00e1s, agrega una funcionalidad extendida adicional a PromQL, que se llama <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/wiki\/MetricsQL\">MetricsQL<\/a><\/noindex>. En relaci\u00f3n a esta funcionalidad extendida, hay una presentaci\u00f3n en YouTube. Habl\u00e9 en el Monitoring Meetup que se llev\u00f3 a cabo en primavera en San Petersburgo.<\/p>\n<p><\/p>\n<p>Canal de Telegram <noindex><a rel=\"nofollow\" href=\"https:\/\/t.me\/VictoriaMetrics_ru1\">VictoriaMetrics<\/a><\/noindex>.<\/p>\n<p class=\"for_users_only_msg\">Solo los usuarios registrados pueden participar en la encuesta. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/auth\/login\/\">Inicie sesi\u00f3n<\/a><\/noindex>, por favor.<\/p>\n<h2 class=\"default-block__polling-title\">\u00bfQu\u00e9 te impide migrar a VictoriaMetrics como almacenamiento a largo plazo para Prometheus? (Escribe en los comentarios, lo agregar\u00e9 a la encuesta))<\/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>No utilizo Prometheus5<\/p>\n<\/li>\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent\">28,6%<\/strong>No sab\u00eda sobre VictoriaMetrics2<\/p>\n<\/li>\n<\/ul>\n<p>    7 usuarios votaron. 12 usuarios se abstuvieron.<br \/>\n<br \/>Fuente: <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\/es\/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=\"es_ES\" \/>\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\/es\/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\udd47Optimizaci\u00f3n de Go en VictoriaMetrics. Alexander Valyalkin | ProHoster","description":"Les invito a revisar la transcripci\u00f3n de la conferencia de fin de 2019 de Alexander Valyalkin \"Optimizaci\u00f3n de Go en VictoriaMetrics\"","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","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\/es\/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\/es\/wp-json\/wp\/v2\/posts\/80733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=80733"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/80733\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/80734"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=80733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=80733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=80733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}