Për monitorimin e serverëve dhe shërbimeve, ne kemi përdorur prej kohësh, dhe vazhdojmë me sukses, një zgjidhje të kombinuar bazuar në Nagios dhe Munin. Megjithatë, kjo kombinim ka disa disavantazhe, prandaj ne, ashtu si shumë të tjerë, e exploatojmë aktivisht . Në këtë artikull, ne do të flasim për mënyrën si mund të zgjidhet problemi me performancën me përpjekje minimale në rritjen e numrit të metrikave që merren dhe rritjen e volumit të bazës së të dhënave MySQL
Problemet e përdorimit të bazës së të dhënave MySQL në bashkëpunim me Zabbix
Ndërsa baza e të dhënave ishte e vogël dhe numri i metrikave të ruajtura në të ishte i vogël, gjithçka ishte shkëlqyer. Procesi standard housekeeper, i cili aktivizonte vetë Zabbix Server, suksesshëm fshinte rekordet e vjetra nga baza e të dhënave, duke mos e lejuar atë të rritej. Megjithatë, sapo numri i metrikave të marrura u rrit dhe volumi i bazës së të dhënave arriti një madhësi të caktuar, gjithçka u bë më e vështirë. Housekeeper nuk mund të përfundonte të fshirë të dhënat brenda intervalit të caktuar të kohës, dhe në bazën e të dhënave filluan të mbeteshin të dhëna të vjetra. Gjatë punës së housekeeper, kishte një ngarkesë të shtuar në Zabbix Server, e cila mund të zgjaste për një kohë të gjatë. U bë e qartë se duhej të zgjidheshin ndonjëherë sfidat e krijuara.
Ky është një problem i njohur; praktikisht çdo kush që ka punuar me volumet e mëdha të monitorimit në Zabbix është zhvilluar nga të njëjtët. Zgjidhjet ishin gjithashtu disa: për shembull, zëvendësimi i MySQL me PostgreSQL ose madje Elasticsearch, por zgjidhja më e lehtë dhe e provuar ishte kalimi në partitizimin e tabelave që ruajnë të dhënat e metrikave në bazën e të dhënave MySQL. Ne vendosëm të ndjekim këtë rrugë.
Kalimi nga tabelat e zakonshme MySQL në tabelat e partitizuara
Zabbix është mirë dokumentuar dhe tabelat ku ai ruan metrikat janë të njohura. Këto janë tabelat: history, ku ruhen vlerat float, history_str, ku ruhen vlerat e shkurtra të stringjeve, history_text, ku ruhen vlerat e gjata të teksteve dhe history_uint, ku ruhen vlerat e numrave të plotë. Ka gjithashtu një tabelë trends, e cila ruan dinamikën e ndryshimeve, por ne vendosëm ta lëmë të paprekur, sepse madhësia e saj është e vogël dhe më vonë do t'i kthehemi.
Në përgjithësi, ishte e qartë se cilat tabela duhej trajtuar. Ne vendosëm të krijonim parti për çdo javë, përveç javës së fundit, mbi bazën e numrave të muajit, dmth. katër parti për muaj: nga 1 deri në 7, nga 8 deri në 14, nga 15 deri në 21 dhe nga 22 deri në 1 (të muajit pasues). Vështirësia ishte se duhej të transformonim tabelat që na duheshin në tabela të partitizuara "në fluturim", pa ndërprerë punën e Zabbix Server dhe grumbullimin e metrikave.
Siç është e çuditshme, struktura e dhënave të tabelave na ndihmoi në këtë. Për shembull, tabela history ka strukturën e mëposhtme:
`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',për më tepër
KEY `history_1` (`itemid`,`clock`) Siç shohim, çdo metrikë në fund regjistrohet në tabelë me dy fusha shumë të rëndësishme dhe të dobishme për ne itemid dhe clock. Kështu, ne mund të krijojmë një tabelë temporare, për shembull, me emrin history_tmp, të vendosim për të partitizimin dhe pastaj të transferojmë të gjitha të dhënat nga tabela history, dhe pas kësaj ta ribllokojmë tabelën history në history_old, dhe tabelën history_tmp në history, pas së cilës do të shtojmë të dhënat që na kanë mbetur të pashkruara nga history_old në history dhe do ta fshijmë history_old. Kjo mund të bëhet në mënyrë të sigurt, ne nuk do të humbasim asgjë, sepse fushat e mësipërme itemid dhe clock sigurojnë lidhjen e metrikës konkrete me një kohë të caktuar dhe jo me ndonjë numër rendor.
Procedura e kalimit
Kujdes! Është shumë e dëshirueshme që para fillimit të ndonjë veprimi, të bëni një kopje rezervë të plotë të bazës së të dhënave. Ne të gjithë jemi njerëz të gjallë dhe mund të bëjmë gabime në komandat e shkruara, gjë që mund të çojë në humbjen e të dhënave. Po. një kopje rezervë nuk do ta sigurojë maksimale të aktualitetit, por është më mirë ta kemi një të tillë sesa asnjë.
Pra, asgjë nuk përjashtohet dhe as nuk ndalohet. Më e rëndësishme është që në serverin MySQL të ketë mjaft hapësirë të lirë në disk, dmth. që për secilën nga tabelat e përmendura më sipër history, history_text, history_str, history_uint, të paktën të ketë hapësirë për krijimin e tabelave me sufixin "_tmp", duke marrë parasysh se ajo do të ketë të njëjtin volum si tabela origjinale.
Ne nuk do të përshkruajmë gjithçka disa herë për secilën nga tabelat e përmendura dhe do të shqyrtojmë të gjitha në shembuj të vetëm nga ajo - tabela history.
Pra, krijojmë një tabelë të zbrazët history_tmp në bazë të strukturës së tabelës history.
Krijoni TABELË `history_tmp` SI `history`;Krijojmë partitë që na duhen. Për shembuj, le të bëjmë këtë për një muaj. Çdo parti krijohet mbi bazën e rregullit të partitizimit, të cilin e bazojmë në vlerën e fushës clock, të cilin e krahasojmë me shënimin e kohës:
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"))
); Ky ky operator, shtonë ndarjen për tabelën që kemi krijuar. history_tmp. Të sqarojmë, që të dhënat për të cilat vlera e fushës clock është më e vogël se "2019-02-01 00:00:00" do të shkojnë në ndarjen p20190201, pastaj të dhënat për të cilat vlera e fushës clock është më e madhe se "2019-02-01 00:00:00" por më e vogël se "2019-02-07 00:00:00" do të shkojnë në ndarjen p20190207 etj.
Shënim i rëndësishëm: Çfarë do të ndodhë nëse kemi të dhëna në tabelën e ndarë për të cilat vlera e fushës clock do të jetë më e madhe ose e barabartë me "2019-03-01 00:00:00"? Për shkak se për këto të dhëna nuk ka një ndarje të përshtatshme, ato nuk do të hyjnë në tabelë dhe do të humbasin. Prandaj, ju duhet të mos harroni të krijoni ndarjet shtesë në kohë për të shmangur humbjen e të dhënave (për të cilat flitet më poshtë).
Pra, tabela përkohshme është përgatitur. Po ngarkohet të dhënat. Procesi mund të marrë një kohë të gjatë, por për fat të mirë nuk bllokon asnjë kërkesë tjetër, kështu që është e nevojshme thjesht të bëni durim:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;Fjalëkyçi IGNORE gjatë ngarkesës fillestare nuk është i detyrueshëm, pasi të dhënat nuk ekzistojnë ende në tabelë, megjithatë do t'ju nevojitet gjatë ngarkesave shtesë të të dhënave. Për më tepër, ai mund të jetë i dobishëm nëse ju keni ndalur këtë proces gjatë ngarkesës dhe duhet ta filloni përsëri.
Pra, pas një kohe (ndoshta edhe disa orë), ngarkesa e parë e të dhënave është përfunduar. Siç e kuptoni, tani tabela history_tmp përmban jo të gjitha të dhënat nga tabela history, por vetëm ato që ishin në të në momentin e fillimit të ekzekutimit të kërkesës. Këtu ju keni një zgjedhje: ose bëni një kalim tjetër (nëse procesi i ngarkesës ka zgjatur shumë), ose kaloni menjëherë në rinovimin e tabelave, për të cilin është folur më lart. Le të flasim fillimisht për kalimin e dytë. Për të filluar, na nevojitet të kuptojmë kohën e regjistrimit të fundit të futur në history_tmp:
SELECT max(clock) FROM history_tmp;Supozoni se keni marrë: 1551045645. Tani përdorni vlerën e marrë për kalimin e dytë të ngarkesës së të dhënave:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;Ky kalim duhet të përfundojë ndjeshëm më shpejt. Por nëse kalimi i parë ka zgjatur orë, dhe kalimi i dytë gjithashtu ka marrë një kohë të gjatë, ndoshta do të ishte e arsyeshme të bëni edhe një kalim të tretë, i cili ekzekutohet krejtësisht si kalimi i dytë.
Në fund, ne përsëri realizojmë operacionin e marrjes së kohës së regjistrimit të fundit të futur në history_tmp, duke ekzekutuar:
SELECT max(clock) FROM history_tmp;Supozoni se keni marrë 1551085645. Ruani këtë vlerë — do na nevojitet për ngarkesën shtesë.
Dhe tani, kur ngarkesa fillestare e të dhënave në history_tmp ka përfunduar, ne kalojmë në rinovimin e tabelave:
BEGIN;
RENAME TABLE history TO history_old;
RENAME TABLE history_tmp TO history;
COMMIT; E kemi paraqitur këtë bllok si një transaksion, për të shmangur momentin e futjes së të dhënave në një tabelë që nuk ekziston, sepse pas RENAME të parë deri në momentin e ekzekutimit të RENAME të dytë, tabela history nuk do të ekzistojë. Por edhe nëse midis operacioneve RENAME në tabelë history kanë ardhur disa të dhëna, dhe tabela vetë ende nuk do të ekzistojë (për shkak të rinovimit), do të marrim një numër të vogël gabimesh të futjes, të cilat mund të injorohen (ne kemi monitorim, jo bankë).
Tani kemi një tabelë të re history me ndarjen, por mungojnë të dhënat që janë marrë gjatë kalimit të fundit të futjes së të dhënave në tabelë history_tmp. Por këto të dhëna i kemi në tabelën history_old dhe tani do t'i ngarkojmë aty. Për këtë, do të na nevojitet vlera e ruajtur 1551085645. Pse e ruam këtë vlerë dhe jo të përdorim kohën maksimale të ngarkesës nga tabela aktuale history? Потому что новые данные уже в неё поступают и мы получим неверное время. Итак, дозаливаем данные:
INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; Pas përfundimit të kësaj operacioni, tabela e re, e ndarë, history ka të gjitha të dhënat që ishin në të vjetër, plus ato që kanë ardhur tashmë pas rinovimit të tabelës. Tabela history_old nuk na nevojitet më. Mund ta fshijmë menjëherë, ose mund të bëjmë një kopje rezervë para fshirjes (nëse jeni paranojak).
Procesi i përshkruar më sipër duhet të përsëritet për tabelat history_str, history_text dhe history_uint.
Çfarë duhet të korrigjojmë në konfigurimin e Zabbix Server
Tani mirëmbajtja e bazës së të dhënave në pjesën e historisë së të dhënave bie mbi shpatullat tona. Kjo do të thotë që Zabbix nuk duhet më të fshijë të dhënat e vjetra — ne do të merremi me këtë vetë. Për të parandaluar që Zabbix Server të përpiqet të pastrojë të dhënat vetë, duhet të hyni në ndërfaqen web të Zabbix, të zgjidhni në menu "Administrim", pastaj nënmenu "Të Përgjithshme", pastaj në listën e shfaqur njëkënd me djathtas të zgjidhni "Pastrimi i historisë". Në faqen që shfaqet, hiqni të gjitha shenjat për grupin "Historik" dhe klikoni në butonin "Rifresko". Kjo do të parandalojë pastrimin e panevojshëm të tabelave history* nëpërmjet housekeeper.
Vini re në këtë faqe grupin "Dinamikat e ndryshimeve". Ky është inxhinierias trends, të cilën premtuam se do të kthehemi. Nëse po ashtu është bërë shumë e madhe dhe kërkon ndarje, hiqni shenjat nga kjo grup dhe më pas trajtoni këtë tabelë ashtu siç është bërë për tabelat e tjera. history*.
Mirëmbajtja e mëtejshme e bazës së të dhënave
Siç u shkrua më parë, për funksionimin normal të tabelave të ndara, është e nevojshme të krijoni në kohë ndarjet. Kjo mund të bëhet kështu:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));Për më tepër, pasi krijuam tabela të ndara dhe ndaluam Zabbix Server që t'i pastrojë ato, eliminimi i të dhënave të vjetra tani është përgjegjësia jonë. Fatmirësisht, këtu nuk ka asnjë problem. Kjo bëhet thjesht duke hequr atë ndarje, të dhënat e së cilës nuk na duhen më.
Për shembull:
ALTER TABLE history DROP PARTITION p20190201;Ndryshe nga operatorët DELETE FROM me specifikimin e intervalit të datave, DROP PARTITION ekzekutohet brenda disa sekondave, pa ngarkuar aspak server dhe po ashtu funksionon pa probleme në rastin e përdorimit në replikimin e MySQL.
Përfundimi
Zgjidhja e përshkruar është provuar me kohën. Vëllimi i të dhënave po rritet, por nuk është vënë re ndonjë ngadalësim të dukshëm të performancës.
Burimi: habr.com
