Utilisation du partitionnement dans MySQL pour Zabbix avec un grand nombre d'objets de surveillance

Pour la surveillance des serveurs et des services, nous utilisons depuis longtemps et avec succès une solution combinée basée sur Nagios et Munin. Cependant, ce groupe a un certain nombre d'inconvénients, donc nous, comme beaucoup, exploitons activement Zabbix . Dans cet article, nous expliquerons comment vous pouvez résoudre le problème de performances avec un effort minimal lors de l'augmentation du nombre de mesures supprimées et de l'augmentation du volume de la base de données MySQL

Problèmes d'utilisation d'une base de données MySQL avec Zabbix


Bien que la base de données soit petite et le nombre de métriques stockées dedans était petit, tout était merveilleux. Le processus régulier de femme de ménage qui démarre le serveur Zabbix lui-même a réussi à supprimer les enregistrements obsolètes de la base de données, l'empêchant de croître. Cependant, dès que le nombre de mesures capturées a augmenté et que la taille de la base de données a atteint une certaine taille, tout s'est aggravé. Houserkeeper a cessé de supprimer les données pendant l'intervalle de temps alloué, les anciennes données ont commencé à rester dans la base de données. Pendant le fonctionnement de la femme de ménage, il y a eu une charge accrue sur le serveur Zabbix, qui pourrait durer longtemps. Il est devenu clair qu'il fallait en quelque sorte résoudre la situation actuelle.

C'est un problème connu, presque tous ceux qui ont travaillé avec de gros volumes de surveillance sur Zabbix ont fait face à la même chose. Il y avait aussi plusieurs solutions: par exemple, remplacer MySQL par PostgreSQL ou même Elasticsearch, mais la solution la plus simple et la plus éprouvée était de passer aux tables de partitionnement qui stockent les données métriques dans la base de données MySQL. Nous avons décidé de suivre cette voie.

Migration de tables MySQL régulières vers des tables partitionnées


Zabbix est bien documenté et les tables où il stocke les métriques sont connues. Il s'agit de tableaux: history , où les valeurs flottantes sont stockées, history_str , où les valeurs de chaîne courtes sont stockées, history_text , où les valeurs de texte longues sont stockées et history_uint , où les valeurs entières sont stockées. Il y a aussi un tableau des trends qui stocke la dynamique des changements, mais nous avons décidé de ne pas y toucher, car sa taille est petite et un peu plus tard nous y reviendrons.

En général, les tables à traiter étaient claires. Nous avons décidé de faire des partitions pour chaque semaine, à l'exception de la dernière, en fonction des numéros du mois, c'est-à-dire quatre partitions par mois: du 1er au 7, du 8 au 14, du 15 au 21 et du 22 au 1er (mois prochain). La difficulté était que nous devions transformer les tables dont nous avions besoin en partitions «à la volée», sans interrompre Zabbix Server et collecter des métriques.

Curieusement, la structure même de ces tableaux nous a été utile. Par exemple, la table d' history a la structure suivante:

 `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', 

tout

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

Comme vous pouvez le voir, chaque métrique est finalement entrée dans un tableau avec deux champs itemid et clock très importants et pratiques pour nous. Ainsi, nous pouvons bien créer une table temporaire, par exemple, avec le nom history_tmp , configurer une history_tmp pour celle-ci, puis y transférer toutes les données de la table history , puis renommer la table history en history_old , et la table history_tmp en history , puis ajouter les données que nous avons remplis de history_old à history et history_old . Vous pouvez le faire en toute sécurité, nous ne perdrons rien, car les champs itemid et clock indiqués ci-dessus fournissent une métrique de lien vers une heure spécifique, et non vers une sorte de numéro de série.

La procédure de transition elle-même


Attention! Il est très souhaitable, avant de commencer toute action, d'effectuer une sauvegarde complète à partir de la base de données. Nous sommes tous des êtres vivants et nous pouvons commettre une erreur dans l'ensemble des commandes, ce qui peut entraîner une perte de données. Oui une copie de sauvegarde ne fournira pas une pertinence maximale, mais il vaut mieux en avoir une qu’aucune.
Donc, ne rien éteindre ou arrêter. L'essentiel est que sur le serveur MySQL lui-même, il devrait y avoir une quantité suffisante d'espace disque libre, c'est-à-dire de sorte que pour chacune des tables ci-dessus history , history_text , history_str , history_uint , au moins, il y ait suffisamment d'espace pour créer une table avec le suffixe "_tmp", étant donné que ce sera le même montant que la table d'origine.

Nous ne décrirons pas tout plusieurs fois pour chacun des tableaux ci-dessus et considérerons tout avec l'exemple d'un seul d'entre eux - le tableau history .

Donc, créez une table history_tmp vide basée sur la structure de la table history .

 CREATE TABLE `history_tmp` LIKE `history`; 

Nous créons les partitions dont nous avons besoin. Par exemple, faisons-le pendant un mois. Chaque partition est créée en fonction de la règle de partition, en fonction de la valeur du champ d' horloge , que nous comparons avec l'horodatage:

 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")) ); 

Cet opérateur ajoute un partitionnement à la table history_tmp nous avons créée. Précisons que les données dont la valeur du champ d' horloge est inférieure à «2019-02-01 00:00:00» tomberont dans la partition p20190201 , puis les données dont la valeur du champ d' horloge est supérieure à «2019-02-01 00:00:00» mais moins "2019-02-07 00:00:00" tombera dans la fête p20190207 et ainsi de suite.
Remarque importante: Et que se passe-t-il si nous avons des données dans la table partitionnée où la valeur du champ d'horloge est supérieure ou égale à "2019-03-01 00:00:00"? Puisqu'il n'y a pas de partition appropriée pour ces données, elles ne tomberont pas dans la table et seront perdues. Par conséquent, vous ne devez pas oublier de créer des partitions supplémentaires en temps opportun, afin d'éviter une telle perte de données (dont ci-dessous).
Ainsi, la table temporaire est préparée. Remplissez les données. Le processus peut prendre un certain temps, mais heureusement, il ne bloque aucune autre requête, il suffit donc d'être patient:

 INSERT IGNORE INTO `history_tmp` SELECT * FROM history; 

Le mot clé IGNORE n'est pas requis lors du remplissage initial, car il n'y a toujours pas de données dans le tableau, cependant, vous en aurez besoin lors de l'ajout de données. De plus, il peut être utile si vous deviez interrompre ce processus et recommencer lors du remplissage des données.

Ainsi, après un certain temps (peut-être même quelques heures), le premier téléchargement de données est passé. Comme vous le comprenez, la table history_tmp ne contient plus toutes les données de la table d' history , mais uniquement les données qui s'y history_tmp au history_tmp la requête. Ici, en fait, vous avez le choix: soit nous effectuons un autre passage (si le processus de remplissage a duré longtemps), soit nous procédons immédiatement au changement de nom des tableaux mentionnés ci-dessus. Prenons d'abord le deuxième passage. Tout d'abord, nous devons comprendre l'heure du dernier enregistrement inséré dans history_tmp :

 SELECT max(clock) FROM history_tmp; 

Supposons que vous ayez reçu: 1551045645 . Maintenant, nous utilisons la valeur obtenue dans la deuxième passe de remplissage des données:

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

Ce passage devrait se terminer beaucoup plus rapidement. Mais si la première passe a été exécutée pendant des heures et que la seconde a également été effectuée pendant une longue période, il peut être correct d'effectuer la troisième passe, qui est effectuée de manière complètement similaire à la seconde.

A la fin, nous effectuons à nouveau l'opération d'obtention de l'heure de la dernière insertion de l'enregistrement dans history_tmp en faisant:

 SELECT max(clock) FROM history_tmp; 

Disons que vous avez obtenu 1551085645 . Conservez cette valeur - nous en aurons besoin pour le remplissage.

Et maintenant, en fait, lorsque les données primaires remplissant history_tmp terminées, nous procédons à renommer les tables:

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

Nous avons conçu ce bloc comme une transaction afin d'éviter le moment d'insérer des données dans une table inexistante, car après le premier RENAME jusqu'à ce que le deuxième RENAME soit exécuté, la table d' history n'existera pas. Mais même si certaines données arrivent entre les opérations RENAME dans la table d' history et que la table elle-même n'existe pas encore (en raison d'un changement de nom), nous obtiendrons un petit nombre d'erreurs d'insertion qui peuvent être négligées (nous avons la surveillance, pas la banque).

Nous avons maintenant une nouvelle table d' history avec partitionnement, mais elle n'a pas assez de données reçues lors de la dernière passe d'insertion de données dans la table history_tmp . Mais nous avons ces données dans la table history_old et nous les partageons à partir de là. Pour cela, nous aurons besoin de la valeur précédemment enregistrée 1551085645. Pourquoi avons-nous enregistré cette valeur et n'avons pas déjà utilisé le temps de remplissage maximal de la table d' history actuelle? Parce que de nouvelles données y entrent déjà et nous aurons le mauvais moment. Donc, nous mesurons les données:

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

Après la fin de cette opération, nous avons dans la nouvelle table d' history partitionnée toutes les données qui étaient dans l'ancienne, plus les données qui sont venues après que la table a été renommée. La table history_old n'est plus nécessaire. Vous pouvez le supprimer immédiatement ou en faire une copie de sauvegarde (si vous souffrez de paranoïa) avant de le supprimer.

L'ensemble du processus décrit ci-dessus doit être répété pour les history_str , history_text et history_uint .

Ce qui doit être corrigé dans les paramètres du serveur Zabbix


Maintenant, la maintenance de la base de données concernant l'historique des données repose sur nos épaules. Cela signifie que Zabbix ne devrait plus supprimer les anciennes données - nous le ferons nous-mêmes. Pour que Zabbix Server n'essaye pas de nettoyer les données elles-mêmes, vous devez vous rendre sur l'interface Web de Zabbix, sélectionner le menu "Administration", puis le sous-menu "Général", puis sélectionner "Effacer l'historique" dans la liste déroulante à droite. Sur la page qui apparaît, décochez toutes les cases du groupe "Historique" et cliquez sur le bouton "Mettre à jour". Cela évitera que nous nettoyions les tables d' history* par le biais de la femme de ménage.

Faites attention sur la même page au groupe «Dynamique des changements». Ce n'est que le tableau des trends , sur lequel nous avons promis de revenir. S'il est également devenu trop volumineux pour vous et doit être partitionné, décochez également ce groupe, puis traitez cette table exactement comme pour les tables history* .

Maintenance supplémentaire de la base de données


Comme il a été écrit précédemment, pour un fonctionnement normal sur des tables partitionnées, il est nécessaire de créer des partitions à temps. Vous pouvez faire ceci comme ceci:

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

De plus, depuis que nous avons créé des tables partitionnées et interdit au serveur Zabbix de les nettoyer, la suppression des anciennes données est désormais notre préoccupation. Heureusement, il n'y a aucun problème. Cela se fait simplement en supprimant la partition dont nous n'avons plus besoin des données.

Par exemple:

 ALTER TABLE history DROP PARTITION p20190201; 

Contrairement aux instructions DELETE FROM avec une plage de dates, la DROP PARTITION est effectuée en quelques secondes, ne charge pas du tout le serveur et fonctionne tout aussi bien lors de l'utilisation de la réplication dans MySQL.

Conclusion


La solution décrite est éprouvée dans le temps. Le volume de données augmente, mais il n'y a pas de ralentissement notable des performances.

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


All Articles