Uso de particiones en MySQL para Zabbix con una gran cantidad de objetos de monitoreo

Para monitorear servidores y servicios, hemos utilizado durante mucho tiempo, y aún con éxito, una solución combinada basada en Nagios y Munin. Sin embargo, este grupo tiene una serie de inconvenientes, por lo que, como muchos, estamos explotando activamente Zabbix . En este artículo, hablaremos sobre cómo puede resolver el problema de rendimiento con un esfuerzo mínimo al aumentar la cantidad de métricas eliminadas y aumentar el volumen de la base de datos MySQL

Problemas al usar una base de datos MySQL con Zabbix


Si bien la base de datos era pequeña y la cantidad de métricas almacenadas en ella era pequeña, todo fue maravilloso. El proceso habitual de limpieza que inicia el servidor Zabbix eliminó con éxito los registros obsoletos de la base de datos, evitando que crezca. Sin embargo, tan pronto como el número de métricas capturadas aumentó y el tamaño de la base de datos alcanzó cierto tamaño, todo empeoró. Houserkeeper dejó de administrar la eliminación de datos para el intervalo de tiempo asignado, los datos antiguos comenzaron a permanecer en la base de datos. Durante la operación del ama de llaves hubo un aumento de carga en el servidor Zabbix, que podría durar mucho tiempo. Quedó claro que era necesario resolver de alguna manera la situación actual.

Este es un problema conocido, casi todos los que trabajaron con grandes volúmenes de monitoreo en Zabbix enfrentaron lo mismo. También hubo varias soluciones: por ejemplo, reemplazar MySQL con PostgreSQL o incluso Elasticsearch, pero la solución más simple y probada fue cambiar a tablas de partición que almacenan datos métricos en la base de datos MySQL. Decidimos ir por este camino.

Migración de tablas MySQL regulares a particionadas


Zabbix está bien documentado y se conocen las tablas donde almacena las métricas. Estas son tablas: history , donde se almacenan los valores flotantes, history_str , donde se almacenan los valores de cadena corta, history_text , donde se almacenan los valores de texto largos, y history_uint , donde se almacenan los valores enteros. También hay una tabla de trends que almacena la dinámica de los cambios, pero decidimos no tocarla, porque su tamaño es pequeño y un poco más tarde volveremos a ella.

En general, las tablas que se necesitaban procesar estaban claras. Decidimos hacer particiones para cada semana, con la excepción de la última, en función de los números del mes, es decir. cuatro particiones por mes: del 1 al 7, del 8 al 14, del 15 al 21 y del 22 al 1 (próximo mes). La dificultad era que necesitábamos convertir las tablas que necesitábamos en particionadas "sobre la marcha", sin interrumpir Zabbix Server y recopilar métricas.

Curiosamente, la estructura misma de estas tablas nos ayudó en esto. Por ejemplo, la tabla de history tiene la siguiente estructura:

 `itemid` bigint(20) unsigned NOT NULL, `clock` int(11) NOT NULL DEFAULT '0', `value` double(16,4) NOT NULL DEFAULT '0.0000', `ns` int(11) NOT NULL DEFAULT '0', 

mientras que

 KEY `history_1` (`itemid`,`clock`) 

Como puede ver, cada métrica eventualmente se ingresa en una tabla con dos campos itemid y clock muy importantes y convenientes para nosotros. Por lo tanto, podemos crear una tabla temporal, por ejemplo, con el nombre history_tmp , configurar una history_tmp para ella y luego transferir todos los datos de la tabla de history allí, y luego cambiar el nombre de la tabla de history a history_old y la tabla de history_tmp a history , luego agregar los datos que hemos rellenado de history_old a history y eliminar history_old . Puede hacerlo de forma completamente segura, no perderemos nada, porque los campos itemid y clock indicados anteriormente proporcionan una métrica de enlace a una hora específica y no a algún tipo de número de serie.

El procedimiento de transición en sí


Atencion Es muy deseable, antes de comenzar cualquier acción, hacer una copia de seguridad completa de la base de datos. Todos somos personas vivas y podemos cometer un error en el conjunto de comandos, lo que puede conducir a la pérdida de datos. Si una copia de seguridad no proporcionará la máxima relevancia, pero es mejor tener una que ninguna.
Por lo tanto, no apague ni pare nada. Lo principal es que en el servidor MySQL debería haber una cantidad suficiente de espacio libre en el disco, es decir, de modo que para cada una de las tablas anteriores history , history_text , history_str , history_uint , al menos, haya suficiente espacio para crear una tabla con el sufijo "_tmp", dado que será la misma cantidad que la tabla original.

No describiremos todo varias veces para cada una de las tablas anteriores y consideraremos todo con el ejemplo de solo una de ellas: la tabla de history .

Por lo tanto, cree una tabla vacía history_tmp basada en la estructura de la tabla history .

 CREATE TABLE `history_tmp` LIKE `history`; 

Creamos las particiones que necesitamos. Por ejemplo, hagámoslo por un mes. Cada partición se crea en función de la regla de partición, en función del valor del campo del reloj , que comparamos con la marca de tiempo:

 ALTER TABLE `history_tmp` PARTITION BY RANGE( clock ) ( PARTITION p20190201 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-01 00:00:00")), PARTITION p20190207 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-07 00:00:00")), PARTITION p20190214 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-14 00:00:00")), PARTITION p20190221 VALUES LESS THAN (UNIX_TIMESTAMP("2019-02-21 00:00:00")), PARTITION p20190301 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-01 00:00:00")) ); 

Este operador agrega particiones a la tabla history_tmp que creamos. Aclaremos que los datos para los cuales el valor del campo del reloj es menor que “2019-02-01 00:00:00” caerán en la partición p20190201 , luego los datos para los cuales el valor del campo del reloj es mayor que “2019-02-01 00:00:00” pero menor "2019-02-07 00:00:00" caerá en la fiesta p20190207 y así sucesivamente.
Nota importante: ¿Y qué sucede si tenemos datos en la tabla particionada donde el valor del campo del reloj es mayor o igual que "2019-03-01 00:00:00"? Como no hay una partición adecuada para estos datos, no caerán en la tabla y se perderán. Por lo tanto, no debe olvidarse de crear particiones adicionales de manera oportuna, para evitar dicha pérdida de datos (sobre lo que se detalla más adelante)
Entonces, la tabla temporal está preparada. Rellene los datos. El proceso puede llevar bastante tiempo, pero afortunadamente no bloquea ninguna otra solicitud, por lo que solo debe ser paciente:

 INSERT IGNORE INTO `history_tmp` SELECT * FROM history; 

La palabra clave IGNORE no es necesaria durante el llenado inicial, ya que todavía no hay datos en la tabla, sin embargo, la necesitará al agregar datos. Además, puede ser útil si tuviera que interrumpir este proceso y comenzar de nuevo al completar los datos.

Entonces, después de un tiempo (tal vez incluso unas pocas horas), la primera carga de datos ha pasado. Como comprenderá, ahora la tabla history_tmp no contiene todos los datos de la tabla de history , sino solo los datos que estaban en ella en el momento en que comenzó la consulta. Aquí, de hecho, tiene una opción: o hacemos un pase más (si el proceso de llenado duró mucho tiempo), o inmediatamente procedemos a cambiar el nombre de las tablas mencionadas anteriormente. Primero, tomemos el segundo pase. Primero, debemos entender la hora del último registro insertado en history_tmp :

 SELECT max(clock) FROM history_tmp; 

Supongamos que recibió: 1551045645 . Ahora usamos el valor obtenido en el segundo paso de llenado de datos:

 INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645; 

Este pasaje debería terminar mucho más rápido. Pero si el primer pase se realizó horas, y el segundo también se realizó durante mucho tiempo, puede ser correcto hacer el tercer pase, que se realiza de manera completamente similar al segundo.

Al final, nuevamente realizamos la operación de obtener el tiempo de la última inserción del registro en history_tmp haciendo:

 SELECT max(clock) FROM history_tmp; 

Digamos que tienes 1551085645 . Mantenga este valor: lo necesitaremos para rellenarlo.

Y ahora, en realidad, cuando history_tmp datos primarios en history_tmp , procedemos a cambiar el nombre de las tablas:

 BEGIN; RENAME TABLE history TO history_old; RENAME TABLE history_tmp TO history; COMMIT; 

Diseñamos este bloque como una transacción para evitar el momento de insertar datos en una tabla inexistente, porque después del primer RENAME hasta que se ejecute el segundo RENAME, la tabla de history no existirá. Pero incluso si llegan algunos datos entre las operaciones RENAME en la tabla de history y la tabla en sí aún no existe (debido al cambio de nombre), obtendremos un pequeño número de errores de inserción que pueden descuidarse (tenemos monitoreo, no el banco).

Ahora tenemos una nueva tabla de history con particiones, pero no tiene suficientes datos que se recibieron durante el último paso de inserción de datos en la tabla history_tmp . Pero tenemos estos datos en la tabla history_old y ahora los compartimos desde allí. Para esto, necesitaremos el valor previamente guardado 1551085645. ¿Por qué guardamos este valor y ya no usamos el tiempo de llenado máximo de la tabla de history actual? Porque ya hay nuevos datos y llegaremos en el momento equivocado. Entonces, medimos los datos:

 INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; 

Después del final de esta operación, tenemos en la nueva tabla de history particionada todos los datos que estaban en la anterior, más los datos que vinieron después de que la tabla fue renombrada. La tabla history_old ya no es necesaria. Puede eliminarlo inmediatamente o puede hacer una copia de seguridad (si tiene paranoia) antes de eliminarlo.

Todo el proceso descrito anteriormente debe repetirse para las history_str , history_text e history_uint .

Lo que debe arreglarse en la configuración del servidor Zabbix


Ahora el mantenimiento de la base de datos con respecto al historial de datos descansa sobre nuestros hombros. Esto significa que Zabbix ya no debería eliminar datos antiguos, lo haremos nosotros mismos. Para que el servidor Zabbix no intente limpiar los datos en sí, debe ir a la interfaz web de Zabbix, seleccionar "Administración" en el menú, luego el submenú "General", luego seleccionar "Borrar historial" en la lista desplegable de la derecha. En la página que aparece, desmarca todas las casillas de verificación para el grupo "Historial" y haz clic en el botón "Actualizar". Esto evitará que las tablas de history* sean limpiadas por nosotros a través del ama de llaves.

En la misma página, preste atención al grupo "Dinámica de los cambios". Esta es solo la tabla de trends , a la que prometimos volver. Si también se ha vuelto demasiado grande para usted y necesita particionarse, desmarque también este grupo y luego procese esta tabla exactamente como lo hizo para las tablas de history* .

Mantenimiento adicional de la base de datos


Como se escribió anteriormente, para el funcionamiento normal en tablas particionadas, es necesario crear particiones a tiempo. Puedes hacer esto así:

 ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00"))); 

Además, dado que creamos tablas particionadas y prohibimos que Zabbix Server las limpie, la eliminación de datos antiguos ahora es nuestra preocupación. Afortunadamente, no hay problemas en absoluto. Esto se hace simplemente eliminando la partición cuyos datos ya no necesitamos.

Por ejemplo:

 ALTER TABLE history DROP PARTITION p20190201; 

A diferencia de las declaraciones DELETE FROM con un rango de fechas, DROP PARTITION se completa en un par de segundos, no carga el servidor y funciona igual de bien cuando se usa la replicación en MySQL.

Conclusión


La solución descrita está probada en el tiempo. El volumen de datos está creciendo, pero no hay una desaceleración notable en el rendimiento.

Source: https://habr.com/ru/post/480082/


All Articles