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.
Что нужно поправить в настройках Zabbix Server
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
