För övervakning av servrar och tjÀnster har vi lÀnge, och fortfarande framgÄngsrikt, anvÀnt en kombinerad lösning baserad pÄ Nagios och Munin. Men denna kombination har en rad nackdelar, vilket gör att vi, precis som mÄnga andra, aktivt anvÀnder . I den hÀr artikeln kommer vi att berÀtta om hur man med minimala anstrÀngningar kan lösa problem med prestanda nÀr antalet mÀtvÀrden ökar och storleken pÄ MySQL-databaser vÀxer.
Problem med att anvÀnda MySQL-databaser tillsammans med Zabbix
SÄ lÀnge databasen var liten och antalet sparade mÀtvÀrden var litet, var allt bra. Den inbyggda processen housekeeper, som körs av Zabbix Server, tog effektivt bort förÄldrade poster frÄn databasen, vilket förhindrade att den vÀxte. Men sÄ snart antalet mÀtvÀrden ökade och databasen nÄdde en viss storlek blev allt sÀmre. Housekeeper kunde inte lÀngre radera data inom den tilldelade tidsramen, och gamla data blev kvar i databasen. Under tiden som housekeeper var aktiv blev belastningen pÄ Zabbix Server hög, vilket kunde pÄgÄ under lÄng tid. Det blev klart att vi pÄ nÄgot sÀtt behövde lösa den uppkomna situationen.
Detta Àr ett vÀlkÀnt problem, nÀstan alla som har arbetat med stora övervakningsvolymer i Zabbix har stött pÄ samma sak. Det fanns ocksÄ flera lösningar: till exempel att byta ut MySQL mot PostgreSQL eller till och med Elasticsearch, men den enklaste och mest beprövade lösningen var att övergÄ till partitionering av tabeller som lagrar mÀtdata i MySQL-databasen. Vi beslutade att gÄ denna vÀg.
ĂvergĂ„ng frĂ„n vanliga MySQL-tabeller till partitionerade
Zabbix Àr vÀl dokumenterat och tabellerna dÀr det lagrar mÀtvÀrden Àr kÀnda. Dessa tabeller Àr: history, dÀr float-vÀrden lagras, history_str, dÀr korta strÀngvÀrden lagras, history_text, dÀr lÄnga textvÀrden lagras, och history_uint, dÀr heltal lagras. Det finns ocksÄ en tabell trends, som lagrar förÀndringsdynamik, men den beslutade vi att inte pÄverka, eftersom dess storlek Àr liten och vi kommer att Äterkomma till den senare.
Generellt sett var det tydligt vilka tabeller som behövde bearbetas. Vi beslöt att göra partitioner varje vecka, med undantag för den sista, baserat pÄ mÄnadens datum, d.v.s. fyra partitioner per mÄnad: frÄn 1:a till 7:e, frÄn 8:e till 14:e, frÄn 15:e till 21:a och frÄn 22:a till 1:a (nÀsta mÄnad). SvÄrigheten lÄg i att vi behövde omvandla de tabeller som var relevanta för oss till partitionerade "i farten", utan att avbryta driften av Zabbix Server och insamlingen av metrik.
Konstigt nog fick vi hjÀlp av just databasstrukturen i tabellerna. Till exempel har tabellen history följande struktur:
`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',dÀr
KEY `history_1` (`itemid`,`clock`) Som vi ser lagras varje metrik i slutÀnden i en tabell med tvÄ mycket viktiga och anvÀndbara fÀlt för oss itemid och clock. SÄledes kan vi utan problem skapa en temporÀr tabell, exempelvis med namnet history_tmp, konfigurera partitionering för den och sedan överföra alla data frÄn tabellen history, och dÀrefter döpa om tabellen history pÄ history_old, och tabellen history_tmp pÄ history, efter vilket vi lÀgger till de data som vi har kvar att lÀgga till frÄn history_old pÄ history och ta bort history_old. Detta kan göras helt sÀkert, vi kommer inte att förlora nÄgot, eftersom de angivna fÀlten itemid och clock sörjer för kopplingen av den specifika metrikken till en specifik tidpunkt, inte till nÄgot ordningsnummer.
SjÀlva övergÄngsprocessen
Observera! Det Àr mycket önskvÀrt att göra en fullstÀndig sÀkerhetskopia av databasen innan man pÄbörjar nÄgra ÄtgÀrder. Vi Àr alla mÀnniskor och kan göra misstag med kommandon, vilket kan leda till dataförlust. Ja, sÀkerhetskopian garanterar inte maximal aktualitet, men det Àr bÀttre att ha en sÄdan Àn ingen alls.
SÄ, vi stÀnger inte av eller stoppar nÄgot. Det viktigaste Àr att det finns tillrÀckligt med ledigt utrymme pÄ MySQL-servern, d.v.s. att varje av de ovan nÀmnda tabellerna history, history_text, history_str, history_uint, minst, har tillrÀckligt med utrymme för att skapa en tabell med suffixet "_tmp", med hÀnsyn till att den kommer att ha samma volym som den ursprungliga tabellen.
Vi kommer inte att beskriva allt flera gĂ„nger för var och en av de ovan nĂ€mnda tabellerna, utan vi kommer att illustrera allt med bara ett exempel â tabellen history.
SÄ, vi skapar en tom tabell history_tmp baserat pÄ strukturen av tabellen history.
CREATE TABLE `history_tmp` LIKE `history`;Skapar de partitioner vi behöver. Som ett exempel kommer vi att göra detta under en mÄnad. Varje partition skapas pÄ grundval av en partitioneringsregel som baseras pÄ vÀrdet av fÀltet clock, som vi jÀmför med tidsstÀmpeln:
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"))
); Denna operator lÀgger till partitionering för den tabell vi just har skapat history_tmp. LÄt oss tydliggöra att data vars fÀltvÀrde clock Àr mindre Àn "2019-02-01 00:00:00" kommer att hamna i partitionen p20190201, följt av data vars fÀltvÀrde clock Àr större Àn "2019-02-01 00:00:00" men mindre Àn "2019-02-07 00:00:00" som hamnar i partitionen p20190207 och hÀmta bilder frÄn dab.semrush.com
Viktig notering: Vad hÀnder om vi i den partitionerade tabellen fÄr data vars fÀltvÀrde clock Àr större eller lika med "2019-03-01 00:00:00"? Eftersom det inte finns en lÀmplig partition för dessa data kommer de inte att hamna i tabellen och kommer att gÄ förlorade. DÀrför Àr det viktigt att ni inte glömmer att i tid skapa ytterligare partitioner för att undvika sÄdana dataloss (som beskrivs nedan).
SÄ, den temporÀra tabellen Àr förberedd. Vi laddar upp data. Processen kan ta ganska lÄng tid, men lyckligtvis blockerar den inga andra förfrÄgningar, sÄ man behöver bara ha tÄlamod:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;Nyckelordet IGNORE vid den första uppladdningen Àr inte obligatoriskt, eftersom det ÀndÄ inte finns nÄgra data i tabellen, men det kommer att behövas vid senare uppdateringar av data. Dessutom kan det vara anvÀndbart om du mÄste avbryta denna process under dataladdningen och starta om den.
SÄ, efter en tid (möjligen flera timmar) har den första dataladdningen genomförts. Som ni förstÄr innehÄller nu tabellen history_tmp inte alla data frÄn tabellen history, utan endast de som fanns dÀr vid tidpunkten för utförandet av förfrÄgan. HÀr har ni egentligen ett val: antingen gör vi en ny genomgÄng (om uppladdningsprocessen tog lÄng tid), eller sÄ gÄr vi direkt till omdöpningen av tabellerna som nÀmndes ovan. LÄt oss börja med den andra genomgÄngen. Först behöver vi förstÄ tiden för den senaste inskrivna posten i history_tmp:
SELECT max(clock) FROM history_tmp;Anta att ni fick: 1551045645. Nu anvÀnder vi det erhÄllna vÀrdet för den andra omgÄngen av datainflödet:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;Den hÀr omgÄngen bör avslutas betydligt snabbare. Men om den första omgÄngen tog timmar och den andra ocksÄ tog lÄng tid, kan det vara vettigt att göra en tredje omgÄng som utförs helt pÄ samma sÀtt som den andra.
I slutet genomför vi Äterigen operationen för att fÄ tiden för den senaste insÀttningen av en post i history_tmp, genom att utföra:
SELECT max(clock) FROM history_tmp;Anta att du har fĂ„tt 1551085645. Spara detta vĂ€rde â vi kommer att behöva det för efterföljande insĂ€ttningar.
Och nu, nÀr den primÀra datainflödet i history_tmp Àr avslutat, gÄr vi vidare till att byta namn pÄ tabellerna:
BEGIN;
RENAME TABLE history TO history_old;
RENAME TABLE history_tmp TO history;
COMMIT; Vi har formulerat detta block som en transaktion för att undvika att införa data i en icke-existerande tabell, eftersom efter den första RENAME till dess att den andra RENAME utförs, kommer tabellen history inte att existera. Men Àven om nÄgra data kommer in i tabellen mellan RENAME-operationerna, och tabellen Ànnu inte existerar (pÄ grund av namnbytet), kommer vi att fÄ ett minimalt antal insÀttningsfel som kan ignoreras (vi har övervakning, inte en bank). history Nu har vi en ny tabell
med partitionering, men den saknar data som erhölls under den senaste omgĂ„ngen av datainflödet till tabellen history . Men dessa data har vi i tabellen history_tmpoch vi ska nu fylla pĂ„ dem dĂ€rifrĂ„n. För detta behöver vi det tidigare sparade vĂ€rdet 1551085645. Varför sparade vi detta vĂ€rde och inte anvĂ€nde den maximala insĂ€ttningstiden frĂ„n den aktuella tabellen history_old INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; history? ĐĐŸŃĐŸĐŒŃ ŃŃĐŸ ĐœĐŸĐČŃĐ” ĐŽĐ°ĐœĐœŃĐ” ŃжД ĐČ ĐœĐ”Ń ĐżĐŸŃŃŃпаŃŃ Đž ĐŒŃ ĐżĐŸĐ»ŃŃĐžĐŒ ĐœĐ”ĐČĐ”ŃĐœĐŸĐ” ĐČŃĐ”ĐŒŃ. ĐŃаĐș, ĐŽĐŸĐ·Đ°Đ»ĐžĐČĐ°Đ”ĐŒ ĐŽĐ°ĐœĐœŃĐ”:
Efter avslutningen av denna operation har vi i den nya, partitionerade tabellen alla data som lÄg i den gamla, plus de som redan kommit efter namnbytet av tabellen. Tabellen history Àr inte lÀngre nödvÀndig. Den kan tas bort omedelbart, eller sÄ kan du göra en sÀkerhetskopia av den innan du tar bort den (om du Àr paranoid). history_old Hela den ovan beskrivna processen behöver upprepas för tabellerna
Vad som behöver Àndras i Zabbix Server-instÀllningarna history_str, history_text och history_uint.
Vad behöver justeras i Zabbix Server-instÀllningarna
Nu Ă„ligger det oss att sköta databasunderhĂ„llet av historikdata. Det innebĂ€r att Zabbix inte lĂ€ngre behöver ta bort gammal data â vi kommer att hantera detta sjĂ€lva. För att förhindra att Zabbix Server försöker rensa data sjĂ€lv, behöver du gĂ„ in i Zabbix webbgrĂ€nssnitt, vĂ€lja menyn "Administration", sedan undermenyn "AllmĂ€nt", och dĂ€refter i listrutan till höger vĂ€lja "Rensning av historik". PĂ„ den framtrĂ€dande sidan ska du avmarkera alla kryssrutor för gruppen "Historia" och trycka pĂ„ knappen "Uppdatera". Detta förhindrar onödig rensning av tabeller som vi inte behöver. historia* via housekeeper.
Observera ocksĂ„ gruppen "Ăndringsdynamik" pĂ„ samma sida. Det Ă€r just den tabell trends, som vi lovade att Ă„terkomma till. Om den har blivit för stor och behöver partitioneras, avmarkera kryssrutorna Ă€ven i denna grupp, och behandla denna tabell pĂ„ samma sĂ€tt som vi gjorde för tabellerna. historia*.
Vidare databasunderhÄll
Som tidigare nÀmnts, för att partitionerade tabeller ska fungera normalt, Àr det nödvÀndigt att skapa partitioner i tid. Det kan göras sÄ hÀr:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));Dessutom, eftersom vi har skapat partitionerade tabeller och förbjudit Zabbix Server att rensa dem, sÄ Àr borttagning av gammal data nu vÄrt ansvar. Lyckligtvis finns det inga problem med detta. Det görs enkelt genom att ta bort den partitionen vars data inte lÀngre behövs.
Till exempel:
ALTER TABLE history DROP PARTITION p20190201;Till skillnad frÄn DELETE FROM-kommandon med datumintervall, utförs DROP PARTITION pÄ nÄgra sekunder, belastar inte server och fungerar lika smidigt nÀr det anvÀnds i MySQL-replikation.
Slutsats
Den beskrivna lösningen har testats och bevisats över tid. Datavolymen ökar, men nÄgon mÀrkbar försÀmring av prestanda har inte noterats.
KĂ€lla: habr.com
