Для маніторынгу сервераў і службаў у нас даўно, і ўсё яшчэ паспяхова, выкарыстоўваецца камбінаванае рашэнне на базе Nagios і Munin. Аднак гэты звязак мае шэраг недахопаў, таму мы, як і многія, актыўна эксплуатуем . У гэтым артыкуле мы распавядзем пра тое, як мінімальнымі намаганнямі можна вырашыць праблему з прадукцыйнасцю пры павелічэнні колькасці якія здымаюцца метрык і росце аб'ёмаў БД MySQL
Праблемы выкарыстанне БД MySQL сумесна з Zabbix
Пакуль БД была маленькай і колькасць метрык, якія ў ёй захоўваліся, невялікім, усё было выдатна. Штатны працэс housekeeper, які запускае сам Zabbix Server паспяхова выдаляў састарэлыя запісы з БД, не даючы ёй расці. Аднак, як толькі колькасць якія здымаюцца метрык вырасла і аб'ём БД дасягнуў вызначанага памеру, усё стала горш. Houserkeeper перастаў паспяваць выдаляць дадзеныя за адведзены яму інтэрвал часу, у БД сталі заставацца старыя дадзеныя. Падчас працы 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 и гадзіны. Такім чынам, мы цалкам можам ствараць часовую табліцу, напрыклад, з імем history_tmp, наладзіць для яе партыцыянаванне і затым пераліць туды ўсе дадзеныя з табліцы history, а затым перайменаваць табліцу history в history_old, а табліцу history_tmp в history, пасля чаго дазаліць тыя дадзеныя, якія ў нас недазаліты з history_old в history і выдаліць history_old. Рабіць гэта можна зусім бяспечна, мы нічога не страцім, бо паказаныя вышэй палі itemid и гадзіны забяспечваюць прывязку канктэрнай метрыкі да пэўнага часу, а не да нейкага парадкавага нумара.
Сама працэдура пераходу
Увага! Вельмі пажадана, перад пачаткам нейкіх дзеянняў, зрабіць поўную рэзервовую копію з базы даных. Мы ўсе жывыя людзі і можам дапусціць памылку ў наборы каманд, што можа прывесці да страты даных. Так. рэзервовая копія не забяспечыць максімальнай актуальнасці, але лепш мець такую, чым ніякай.
Такім чынам, нічога не выключаем і не спыняем. Галоўнае, каб на самім 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 і гэтак далей.
Важная заўвага: А што адбудзецца, калі ў нас у партыцыянаванай табліцы з'явяцца дадзеныя, у якіх значэнне поля 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 не спрабаваў чысціць дадзеныя сам, вам неабходна зайсці ў web-інтэрфейс 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
