Използване на разделяне в MySQL за Zabbix с голям брой обекти за наблюдение

За наблюдение на сървъри и услуги ние използваме комбинирано решение, базирано на Nagios и Munin от дълго време и все още успешно. Тази комбинация обаче има редица недостатъци, така че ние, като мнозина, активно използваме Zabbix. В тази статия ще говорим за това как с минимални усилия можете да разрешите проблем с производителността, когато увеличите броя на взетите показатели и увеличите обемите на MySQL база данни

Проблеми с използването на MySQL база данни със Zabbix

Въпреки че базата данни беше малка и броят на показателите, съхранявани в нея, беше малък, всичко беше страхотно. Стандартният процес на икономка, който се стартира от самия Zabbix сървър, успешно изтри остарелите записи от базата данни, предотвратявайки нейния растеж. Въпреки това, веднага след като броят на взетите показатели се увеличи и размерът на базата данни достигна определен размер, нещата се влошиха. Housekeeper спря да може да изтрива данни в рамките на определения му интервал от време и старите данни започнаха да остават в базата данни. Докато икономката работеше, имаше повишено натоварване на Zabbix сървъра, което можеше да продължи дълго време. Стана ясно, че трябва по някакъв начин да разрешим настоящата ситуация.

Това е известен проблем, почти всеки, който е работил с големи обеми мониторинг на Zabbix, се е сблъсквал със същото. Имаше и няколко решения: например замяна на MySQL с PostgreSQL или дори Elasticsearch, но най-простото и най-доказано решение беше преходът към разделяне на таблици, съхраняващи данни от показатели в MySQL база данни. Решихме да тръгнем точно по този път.

Преход от обикновени MySQL таблици към разделени

Zabbix е добре документиран и таблиците, в които съхранява показатели, са известни. Това са таблиците: history, където се съхраняват плаващи стойности, history_str, където се съхраняват къси низови стойности, history_text, където се съхраняват дълги текстови стойности и history_uint, където се съхраняват цели числа. Има и маса trends, който съхранява динамиката на промените, но решихме да не го засягаме, защото размерът му е малък и ще се върнем към него малко по-късно.

Като цяло беше ясно кои таблици трябва да бъдат обработени. Решихме да направим дялове за всяка седмица, с изключение на последната, по числата на месеца, т.е. четири партиди на месец: от 1-ви до 7-ми, от 8-ми до 14-ти, от 15-ти до 21-ви и от 22-ри до 1-ви (на следващия месец). Трудността беше, че трябваше да превърнем таблиците, от които се нуждаехме, в разделени „в движение“, без да прекъсваме работата на Zabbix сървъра и събирането на показатели.

Колкото и да е странно, самата структура на данните на таблиците ни дойде на помощ в това. Например маса history има следната структура:

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

в този случай

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

Както можете да видите, всеки показател в крайна сметка се въвежда в таблица с две много важни и удобни полета за нас идентификатор на артикул и часовник. Така лесно можем да създадем временна таблица, например с името history_tmp, настройте разделяне за него и след това прехвърлете всички данни от таблицата там historyи след това преименувайте таблицата history в history_old, и масата history_tmp в history, след което добавяме данните, от които не сме попълнили history_old в history и изтрийте history_old. Това може да стане напълно безопасно, няма да загубим нищо, защото горните полета идентификатор на артикул и часовник осигуряват обвързване на конкретен показател към конкретно време, а не към някакъв сериен номер.

Самата процедура по прехода

внимание! Силно препоръчително е, преди да започнете каквото и да е действие, да направите пълно резервно копие на базата данни. Всички сме живи хора и можем да направим грешка в набора от команди, което може да доведе до загуба на данни. да Резервното копие няма да осигури максимална актуалност, но е по-добре да имате такъв, отколкото никакъв.

Така че ние не изключваме нищо и не спираме нищо. Основното е, че самият MySQL сървър има достатъчно свободно дисково пространство, т.е. така че за всяка от изброените по-горе таблици history, history_text, history_str, history_uint, като минимум имаше достатъчно място за създаване на таблица със суфикс „_tmp“, като се има предвид, че тя ще бъде със същия размер като оригиналната таблица.

Няма да описваме всичко няколко пъти за всяка от горните таблици и ще разгледаме всичко, използвайки примера само на една от тях - таблицата history.

И така, нека създадем празна таблица history_tmp въз основа на структурата на таблицата history.

CREATE TABLE `history_tmp` LIKE `history`;

Ние създаваме дяловете, от които се нуждаем. Например, нека направим това за един месец. Всеки дял се създава въз основа на правило за разделяне въз основа на стойността на полето часовник, което сравняваме с клеймото за време:

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

Този оператор добавя разделяне за таблицата, която създадохме history_tmp. Нека изясним тези данни, чиято стойност на полето часовник в партидата ще бъдат включени по-малко от „2019-02-01 00:00:00“ p20190201, след това данни, чиято стойност на полето часовник повече от „2019-02-01 00:00:00“, но по-малко от „2019-02-07 00:00:00“ ще бъдат включени в дяла p20190207 и така нататък.

Важна забележка: Какво се случва, ако имаме данни в разделената таблица, чиято стойност в полето на часовника е по-голяма или равна на „2019-03-01 00:00:00“? Тъй като няма подходящ дял за тези данни, те няма да се показват в таблицата и ще бъдат загубени. Следователно трябва да запомните да създавате допълнителни дялове навреме, за да избегнете такива загуби на данни (както е обсъдено по-долу).

И така, временната маса е подготвена. Попълнете данните. Процесът може да отнеме доста дълго време, но за щастие не блокира други заявки, така че просто трябва да бъдете търпеливи:

INSERT IGNORE INTO `history_tmp` SELECT * FROM history;

Ключовата дума IGNORE не е необходима при първоначалното попълване, тъй като така или иначе няма данни в таблицата, но ще ви трябва при добавяне на данни. Освен това може да е полезно, ако по време на качване на данни трябва да прекъснете този процес и да започнете отново.

И така, след известно време (може би дори няколко часа) се състоя първото качване на данни. Както разбирате, сега масата history_tmp не съдържа всички данни от таблицата history, но само тези, които са били в него по време на започване на заявката. Тук всъщност имате избор: или правим още едно преминаване (ако процесът на попълване отне много време), или веднага пристъпваме към преименуване на таблиците, което беше обсъдено по-горе. Нека първо да поговорим за второто преминаване. Първо трябва да разберем времето на последния вмъкнат запис history_tmp:

SELECT max(clock) FROM history_tmp;

Да приемем, че сте получили: 1551045645. Сега използваме получената стойност при второто преминаване на попълване на данни:

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

Този пасаж трябва да приключи много по-бързо. Но ако първото минаване отне часове, а второто също отне много време, може би е правилно да се направи трето минаване, което се изпълнява по абсолютно същия начин като второто.

Накрая отново извършваме операцията за получаване на времето на последното вмъкване на запис history_tmpкато тичам:

SELECT max(clock) FROM history_tmp;

Да кажем, че сте получили 1551085645. Запазете тази стойност - ще ни трябва за презареждане.

И сега, всъщност, когато първоначалното попълване на данни в history_tmp приключи, нека започнем да преименуваме таблиците:

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

Проектирахме този блок като една транзакция, за да избегнем вмъкването на данни в несъществуваща таблица, тъй като след първото RENAME до изпълнението на второто RENAME, таблицата history няма да съществува. Но дори ако между операциите RENAME към таблицата history някои данни ще пристигнат, но самата таблица все още няма да съществува (поради преименуване), ще получим малък брой грешки при вмъкване, които могат да бъдат пренебрегнати (имаме мониторинг, а не банка).

Сега имаме нова маса history с разделяне, но липсват данни, получени по време на последното преминаване на вмъкване на данни в таблицата history_tmp. Но имаме тези данни в таблицата history_old и сега ще ги допълним от там. За да направим това, имаме нужда от предварително запазената стойност 1551085645. Защо запазихме тази стойност и не използвахме максималното време за запълване от текущата таблица history? Защото в него вече влизат нови данни и ще получим грешен час. И така, нека добавим данните:

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

След като тази операция приключи, в нашата нова, разделена таблица history има всички данни, които са били в старата, плюс тези, които вече са пристигнали след преименуването на таблицата. Таблица history_old вече не ни трябва. Можете да го изтриете незабавно или можете да му направите резервно копие, преди да го изтриете (ако сте параноични).

Целият горен процес трябва да се повтори за таблици history_str, history_text и history_uint.

Какво трябва да се коригира в настройките на Zabbix сървъра

Сега поддръжката на бази данни по отношение на историята на данните пада върху нашите плещи. Това означава, че Zabbix вече не трябва да изтрива стари данни - ние ще направим това сами. За да попречите на Zabbix Server да се опитва да изчисти самите данни, трябва да отидете в уеб интерфейса на Zabbix, да изберете „Администриране“ в менюто, след това подменюто „Общи“, след което да изберете „Изчистване на хронологията“ в падащия списък на десния. На страницата, която се показва, трябва да премахнете отметките от всички квадратчета за групата „История“ и да кликнете върху бутона „Актуализиране“. Това ще ни попречи да изчистваме маси ненужно history* чрез икономка.

На същата страница обърнете внимание на групата „Динамика на промените“. Това е просто маса trends, на който обещахме да се върнем. Ако тя също е станала твърде голяма и се нуждае от разделяне, махнете отметките от квадратчетата в тази група и след това обработете тази таблица по същия начин, както направихте за таблиците history*.

Допълнителна поддръжка на база данни

Както беше написано по-рано, за нормална работа на разделени маси е необходимо да се създават дялове навреме. Можете да го направите така:

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

Освен това, тъй като създадохме разделени таблици и забранихме на Zabbix сървъра да ги почиства, изтриването на стари данни вече е наша грижа. За щастие тук изобщо няма проблеми. Това става просто чрез изтриване на дяла, чиито данни вече не са ни необходими.

Например:

ALTER TABLE history DROP PARTITION p20190201;

За разлика от операторите DELETE FROM, указващи диапазон от дати, DROP PARTITION се изпълнява за няколко секунди, изобщо не зарежда сървъра и работи също толкова гладко, когато се използва репликация в MySQL.

Заключение

Описаното решение е изпитано във времето. Обемът на данните нараства, но няма забележимо забавяне на производителността.

Източник: www.habr.com

Добавяне на нов коментар