За мониторинг на сървъри и услуги отдавна, и все още успешно, използваме комбинирано решение на база Nagios и Munin. Въпреки това, тази комбинация има редица недостатъци, затова ние, както и много други, активно експлоатираме . В тази статия ще разкажем как с минимални усилия можете да решите проблема с производителността при увеличаване на броя на събираемите метрики и растежа на обемите на MySQL бази данни
Проблеми с използването на MySQL база данни съвместно с Zabbix
Докато базата данни беше малка и броят на съ храняваните в нея метрики скромен, всичко беше чудесно. Вътрешният процес housekeeper, който стартира самият Zabbix Server, успешно изтриваше остарели записи от базата данни, предотвратявайки нейния растеж. Въпреки това, щом броят на събираемите метрики нарасна и обемът на базата данни достигна определен размер, всичко стана по-лошо. Housekeeperът престана да успява да изтрива данните в отреденото време, в базата данни останаха стари данни. По време на работа на housekeeper се наблюдаваше повишено натоварване на Zabbix Server, което можеше да продължи дълго време. Станаха ясни необходимите мерки за решаване на ситуацията.
Това е известен проблем, почти всеки, който е работил с големи обеми мониторинг на Zabbix, се е сблъсквал със същото. Имаше и няколко решения: например, подмяна на MySQL с PostgreSQL или дори Elasticsearch, но най-простото и изпробвано решение беше преминаването към партициониране на таблиците, съхраняващи данни за метрики в MySQL базата данни. Ние решихме да поемем точно по този път.
Преминаване от обикновени MySQL таблици към партиционирани
Zabbix е добре документиран и таблиците, където той съхранява метрики, са известни. Това са таблици: history, където се съхраняват float стойности, history_str, където се съхраняват кратки строкови стойности, history_text, където се съхраняват дълги текстови стойности и history_uint, където се съхраняват цели числа. Има и таблица trends, която съхранява динамиката на измененията, но решихме да я оставим непокътната, тъй като размерът ѝ е малък и ще се върнем към нея по-късно.
По принцип, какви таблици трябваше да бъдат обработени, беше ясно. Решихме да правим партиции за всяка седмица, с изключение на последната, въз основа на числата от месеца, т.е. по четири партиции на месец: от 1-во до 7-мо, от 8-мо до 14-то, от 15-то до 21-во и от 22-ро до 1-во (наследващия месец). Трудността беше в това, че трябваше нужните ни таблици да ги превърнем в партиционирани „на лету“, без да прекъсваме работата на Zabbix Server и събирането на метрики.
Както и да е, на помощ в това дойде самата структура на данните в таблиците. Например таблицата 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`) Както виждаме, всяка метрика в крайна сметка се записва в таблица с две много важни и удобни за нас полета itemid и clock. Така че, можем да създадем времена таблица, например, с име history_tmp, да настроим партиционирането за нея и след това да прелеем всички данни от таблицата history, а след това да прекръстим таблицата history в history_old, а таблицата history_tmp в history, след което да добавим тези данни, които не сме добавили от history_old в history и да изтрием history_old. Можем да направим това напълно безопасно, нищо няма да загубим, защото посочените по-горе полета itemid и clock осигуряват обвързване на конкретната метрика с конкретно време, а не с някакъв пореден номер.
Самата процедура на прехода
Внимание! Много е желателно, преди да започнем каквито и да е действия, да направим пълно резервно копие на базата данни. Всички сме живи хора и можем да допуснем грешка в набора от команди, което може да доведе до загуба на данни. Да. резервното копие не осигурява максимална актуалност, но е по-добре да имаме такова, отколкото никакво.
И така, нищо не изключваме и не спираме. Основното е, да има достатъчно свободно място на диска на самия MySQL-сървър, т.е. за всяка от посочените по-горе таблици history, history_text, history_str, history_uint, поне да има достатъчно място за създаване на таблица с суфикс „_tmp“, като се вземе предвид, че тя ще бъде с такъв же обем, както и изходната таблица.
Няма да описваме всичко няколко пъти за всяка от изброените таблици и ще разгледаме всичко на примера на само една от тях — таблицата history.
И така, създаваме празна таблица history_tmp на базата на структурата на таблицата history.
CREATE TABLE `history_tmp` LIKE `history`;Създаваме необходимите ни партиции. За пример, ще направим това за месец. Всяка партиция се създава на базата на правило за партициониране, основано на стойността на полето clock, която сравняваме с времевата марка:
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. Нека уточним, че данните, при които стойността на полето clock е по-малка от „2019-02-01 00:00:00“, ще попаднат в партицията p20190201, след това данни, при които стойността на полето clock е по-голяма от „2019-02-01 00:00:00“, но по-малка от „2019-02-07 00:00:00“, ще попаднат в партицията p20190207 и така нататък.
Важно забележка: Какво ще се случи, ако в партиционираната таблица имаме данни, при които стойността на полето clock е по-голяма или равна на „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 Server
Сега обслужването на базата данни относно историята на данните е на нашите плещи. Това означава, че Zabbix вече не трябва да изтрива стари данни — ние ще се заемем с това сами. За да предотвратите Zabbix Server да опитва да почиства данните сам, трябва да влезете в уеб интерфейса на Zabbix, да изберете в менюто «Администриране», след това подменюто «Общи», а след това в падащото меню вдясно да изберете «Почистване на историята». На появилата се страница трябва да махнете всички отметки за групата «История» и да натиснете бутона «Обнови». Това ще предотврати ненужното почистване на таблиците. history* чрез housekeeper.
Обърнете внимание на същата тази страница на групата «Динамика на промените». Това е точно таблицата trends, към която обещахме да се върнем. Ако тя също е станала твърде голяма и се нуждае от партициониране, махнете отметките и в тази група, след което обработете тази таблица точно както беше направено за таблиците. history*.
По-нататъшно обслужване на базата данни.
Както беше споменато по-рано, за нормалната работа на партиционираните таблици е необходимо навреме да се създават партиции. Можете да го направите така:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));Освен това, тъй като създадохме партиционирани таблици и забранихме на Zabbix Server да ги почиства, изтриването на стари данни сега е наша грижа. За щастие, тук няма никакви проблеми. Това става просто чрез изтриване на тази партиция, данните от която са ни станали ненужни.
Например:
ALTER TABLE history DROP PARTITION p20190201;В отличие от операторите DELETE FROM с указание на диапазон дати, DROP PARTITION се изпълнява за секунди, напълно без натоварване. сървър И толкова безпроблемно работи и при използване на MySQL репликация.
Заключение
Описаното решение е проверено във времето. Обемът на данните расте, но някакво забележимо забавяне на производителността не е отбелязано.
Източник: habr.com
