Gebruik van partitionering in MySQL voor Zabbix met een groot aantal monitorobjecten

Voor het monitoren van servers en diensten maken we al lange tijd gebruik van een gecombineerde oplossing op basis van Nagios en Munin, en dat nog steeds met succes. Echter, deze combinatie kent een aantal tekortkomingen, daarom maken wij, net als velen anderen, actief gebruik van Zabbix. In dit artikel zullen we uitleggen hoe je met minimale inspanning de prestatieproblemen kunt oplossen die ontstaan bij het toenemen van de gemeten metrics en de groei van MySQL-databasevolumes.

Problemen met het gebruik van MySQL-databases in combinatie met Zabbix

Zolang de database klein was en het aantal opgeslagen metrics beperkt, was alles geweldig. Het standaard proces housekeeper, dat op Zabbix Server draait, verwijderde succesvol verouderde records uit de database, waardoor deze niet bleef groeien. Maar zodra het aantal gemeten metrics toenam en het volume van de database een bepaalde grootte bereikte, ging het mis. De housekeeper had niet genoeg tijd om data te verwijderen binnen de toegewezen tijdsinterval, waardoor oude gegevens in de database bleven. Tijdens het functioneren van de housekeeper was er een verhoogde belasting op de Zabbix Server die langdurig aanhield. Het was duidelijk dat we op een of andere manier de ontstane situatie moesten oplossen.

Dit is een bekend probleem; vrijwel iedereen die met grote hoeveelheden monitoring op Zabbix heeft gewerkt, heeft ditzelfde ervaren. Er zijn ook verschillende oplossingen voorgesteld: bijvoorbeeld de vervanging van MySQL door PostgreSQL of zelfs Elasticsearch, maar de meest eenvoudige en beproefde oplossing was de overgang naar partitionering van de tabellen die de metricgegevens in de MySQL-database opslaan. We besloten precies deze weg in te slaan.

De overstap van gewone MySQL-tabellen naar partitionering

Zabbix is goed gedocumenteerd en de tabellen waarin het metrics opslaat zijn bekend. Dit zijn de tabellen: history, waarin float-waarden worden opgeslagen, history_str, waarin korte stringwaarden worden opgeslagen, history_text, waarin lange tekstwaarden worden opgeslagen en history_uint, waarin gehele getallen worden opgeslagen. Er is ook een tabel trends, die veranderingen in dynamiek opslaat, maar deze hebben we niet aangeraakt omdat de grootte ervan klein is en we er later op terug zullen komen.

In het algemeen was het duidelijk welke tabellen verwerkt moesten worden. We hebben besloten om partities te maken voor elke week, met uitzondering van de laatste, op basis van de dagen van de maand, dat wil zeggen, vier partities per maand: van de 1e tot de 7e, van de 8e tot de 14e, van de 15e tot de 21e en van de 22e tot de 1e (van de volgende maand). De moeilijkheid zat hem erin dat we de tabellen die we nodig hadden, 'on-the-fly' moesten omzetten naar gepartitioneerde tabellen, zonder de werking van de Zabbix Server en het verzamelen van metrics te onderbreken.

Vreemd genoeg kwam de structuur van de tabellen ons hierbij te hulp. Bijvoorbeeld, de tabel history heeft de volgende structuur:

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

bij deze

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

Zoals we zien, wordt elke metric uiteindelijk in de tabel ingevoerd met twee zeer belangrijke en handige velden voor ons, itemid en clock. Op deze manier kunnen we gemakkelijk een tijdelijke tabel creëren, bijvoorbeeld met de naam history_tmp, deze partitioneren en vervolgens al de gegevens uit de tabel history, en daarna de tabel hernoemen naar history in history_old, en de tabel history_tmp in history, waarna we de gegevens die nog niet zijn ingevoerd vanuit history_old in history kunnen toevoegen en history_oldverwijderen. Dit kan absoluut veilig worden gedaan, we verliezen niets, want de hierboven genoemde velden itemid en clock garanderen de koppeling van een specifieke metric aan een bepaald tijdstip, en niet aan een willekeurig volgnummer.

De procedure voor de overgang

Let op! Het is zeer wenselijk om, voordat je met enige actie begint, een volledige back-up van de database te maken. We zijn allemaal mensen en kunnen fouten maken bij het invoeren van commando's, wat kan leiden tot gegevensverlies. Ja, een back-up biedt misschien niet de meest actuele informatie, maar het is beter om er een te hebben dan helemaal geen.

Dus, we schakelen niets uit en stoppen niets. Het belangrijkste is dat er op de MySQL-server voldoende vrije schijfruimte beschikbaar is, dat wil zeggen dat er voor elke van de hierboven genoemde tabellen history, history_text, history_str, history_uint, minstens voldoende ruimte is voor het aanmaken van de tabel met de suffix '_tmp', rekening houdend met het feit dat deze dezelfde grootte zal hebben als de oorspronkelijke tabel.

We zullen niet alles meerdere keren voor elke van de bovengenoemde tabellen beschrijven en bekijken alles aan de hand van slechts één van hen — de tabel history.

Dus, we creëren een lege tabel history_tmp op basis van de structuur van de tabel history.

CREATE TABLE `history_tmp` LIKE `history`;

We create the necessary partitions. For example, we will do this for a month. Each partition is created based on a partitioning rule derived from the value of the field clock, which we compare with a timestamp:

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

This operator adds partitioning to the table we created history_tmp. It's important to note that data with the field value clock less than "2019-02-01 00:00:00" will go into partition p20190201, then data with the field value clock greater than "2019-02-01 00:00:00" but less than "2019-02-07 00:00:00" will go into partition p20190207 en zo verder.

Important note: What happens if we have data in the partitioned table where the clock field value is greater than or equal to "2019-03-01 00:00:00"? Since there is no suitable partition for this data, it will not enter the table and will be lost. Therefore, you need to remember to timely create additional partitions to avoid such data loss (as mentioned below).

So, the temporary table is ready. Let's load the data. This process may take quite a while, but fortunately, it doesn't block any other queries, so you just need to be patient:

INSERT IGNORE INTO `history_tmp` SELECT * FROM history;

The IGNORE keyword during the initial load is not mandatory, as there is still no data in the table, but you will need it when reloading data. Additionally, it can be useful if you had to interrupt the loading process and start over.

So, after some time (possibly even several hours), the first data load was completed. As you can understand, now the table history_tmp contains not all the data from the table history, but only those that were in it at the moment the query started executing. Here you actually have a choice: either we make another pass (if the loading process took a long time), or we immediately proceed to renaming the tables, as mentioned above. Let's first talk about the second pass. For starters, we need to understand the time of the last inserted record in history_tmp:

SELECT max(clock) FROM history_tmp;

Suppose you received: 1551045645Laten we nu de verkregen waarde gebruiken tijdens de tweede gegevensinvoerpoging:

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

Deze poging zou aanzienlijk sneller moeten eindigen. Maar als de eerste poging uren duurde en de tweede ook enige tijd kostte, kan het verstandig zijn om een derde poging te doen die volledig vergelijkbaar is met de tweede.

Aan het einde voeren we opnieuw de operatie uit om de tijd van de laatste invoer van een record in history_tmp, door:

SELECT max(clock) FROM history_tmp;

Stel dat u heeft verkregen 1551085645. Bewaar deze waarde — we hebben deze nodig voor de vervolginvoeren.

En nu, wanneer de initiële gegevensinvoer in history_tmp is voltooid, gaan we de tabellen hernoemen:

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

We hebben dit blok als één transactie ingericht om te voorkomen dat er gegevens in een niet-bestaande tabel worden ingevoegd, aangezien na de eerste RENAME, tot het moment van de tweede RENAME, de tabel history niet zal bestaan. Maar zelfs als er tussen de RENAME-operaties enkele gegevens in de tabel komen, en de tabel zelf nog niet bestaat (door de hernoeming), zullen we een klein aantal invoerfouten krijgen die we kunnen negeren (het is een monitoring en geen bank). history Nu hebben we een nieuwe tabel

met partitionering, maar deze heeft niet de gegevens die zijn verkregen tijdens de laatste invoer in de tabel history . Maar deze gegevens hebben we in de tabel history_tmpen we zullen ze daar nu vanuit aanvullen. Hiervoor hebben we de eerder opgeslagen waarde 1551085645 nodig. Waarom hebben we deze waarde opgeslagen en niet de maximale invoertijd uit de huidige tabel gebruikt? history_old INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; history? Потому что новые данные уже в неё поступают и мы получим неверное время. Итак, дозаливаем данные:

Na het voltooien van deze operatie hebben we in de nieuwe, gepartitioneerde tabel

alle gegevens die in de oude waren, plus die welke zijn binnengekomen na de hernoeming van de tabel. De tabel history is niet meer nodig. We kunnen deze meteen verwijderen, of vóór verwijdering een back-up maken (mocht je paranoia hebben). history_old Het bovenstaande proces moet worden herhaald voor de tabellen

Wat moet er aangepast worden in de instellingen van de Zabbix Server history_str, history_text en history_uint.

Что нужно поправить в настройках Zabbix Server

Nu valt het onderhoud van de database met betrekking tot de gegevensgeschiedenis op onze schouders. Dit betekent dat Zabbix geen oude gegevens meer hoeft te verwijderen - dat doen wij zelf. Om ervoor te zorgen dat de Zabbix Server de gegevens niet zelf probeert schoon te maken, moet je in de webinterface van Zabbix naar het menu 'Administratie' gaan, vervolgens naar het submenu 'Algemeen', en daarna in de dropdown rechts 'Geschiedenis opschonen' kiezen. Op de pagina die verschijnt, moet je alle vakjes voor de groep 'Geschiedenis' uitschakelen en op de knop 'Bijwerken' klikken. Dit voorkomt onnodige opschoning van tabellen. geschiedenis* via housekeeper.

Let op de groep 'Veranderdynamiek' op dezelfde pagina. Dit is de tabel waar we eerder op terugkwamen. trendsAls deze ook te groot is geworden en partitionering nodig heeft, haal dan ook hier de vinkjes weg en behandel deze tabel op dezelfde manier als de tabellen. geschiedenis*.

Voortdurend onderhoud van de database

Zoals eerder vermeld, is het voor een goede werking met gepartitioneerde tabellen noodzakelijk om op tijd partities te maken. Dit kan als volgt:

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

Bovendien, aangezien we gepartitioneerde tabellen hebben gemaakt en Zabbix Server verboden hebben ze op te schonen, is het verwijderen van oude gegevens nu onze verantwoordelijkheid. Gelukkig zijn er hier helemaal geen problemen. Dit gebeurt eenvoudigweg door de partitie te verwijderen waarvan de gegevens niet meer nodig zijn.

Bijvoorbeeld:

ALTER TABLE geschiedenis DROP PARTITION p20190201;

In tegenstelling tot de DELETE FROM-operators met een datumbereik, wordt DROP PARTITION binnen enkele seconden uitgevoerd, zonder enige belasting. server En werkt even probleemloos bij gebruik in MySQL-replicatie.

Conclusie

De beschreven oplossing heeft de tand des tijds doorstaan. Het volume van de gegevens groeit, maar er wordt geen merkbare vertraging in de prestaties opgemerkt.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster