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.