A szerverek és szolgáltatások monitorozásához már régóta, és továbbra is sikeresen, egy kombinált megoldást használunk, amely a Nagiosra és a Muninra épül. Ez a páros azonban számos hátránnyal jár, ezért mi, mint sokan mások, aktívan kihasználjuk . Ebben a cikkben arról fogunk beszélni, hogy minimális erőfeszítések árán hogyan lehet megoldani a teljesítménnyel kapcsolatos problémákat a mért metrikák számának növekedésével és a MySQL adatbázis méretének növekedésével.
A MySQL adatbázis Zabbixszal való használatának problémái
Amíg az adatbázis kicsi volt és a benne tárolt metrikák száma is alacsony, minden csodálatosan működött. A Zabbix Server által futtatott housekeeper ütemezett folyamat sikeresen törölte az elavult rekordokat az adatbázisból, megakadályozva annak növekedését. Azonban, ahogy a mért metrikák száma megnőtt, és az adatbázis elérte egy bizonyos méretet, a helyzet romlani kezdett. A housekeeper nem tudta időben törölni az adatokat, és az adatbázisban régi adatok maradtak. A housekeeper működése alatt megnövekedett terhelés nehezedett a Zabbix Serverre, ami hosszabb ideig is eltartott. Nyilvánvalóvá vált, hogy valamilyen módon meg kell oldani a kialakult helyzetet.
Ez egy általánosan ismert probléma, szinte mindenki, aki nagy mennyiségű Zabbix monitorozással foglalkozott, ugyanezzel a helyzettel szembesült. Számos megoldás létezett: például a MySQL PostgreSQL-re vagy akár Elasticsearch-ra való cserélése, de a legegyszerűbb és legjobban bevált megoldás a MySQL adatbázisban a metrikákat tároló táblák particionálására való átállás volt. Mi is éppen ezen az úton indultunk el.
Átállás a hagyományos MySQL táblákról a particionáltakra
A Zabbix jól dokumentált, és ismertek azok a táblák, ahol a metrikákat tárolja. Ezek a táblák: history, ahol a float értékeket tárolják, history_str, ahol a rövid szöveges értékek találhatók, history_text, ahol a hosszú szöveges értékek találhatók, és history_uint, ahol az egész számú értékeket tárolják. Van még egy tábla, trends, amely a változások dinamikáját tárolja, de azt nem érintettük, mert a mérete kicsi, és később visszatérünk rá.
Összességében világos volt, hogy mely táblákat kell feldolgozni. Azt döntöttük el, hogy hetente indikátumokat készítünk, kivéve az utolsót, a hónap számai alapján, tehát négy indikáció havonta: az 1-től a 7-ig, a 8-tól a 14-ig, a 15-től a 21-ig és a 22-től az (következő hónap) 1-ig. A nehézség abban rejlik, hogy a szükséges táblákat
meglepő módon, maguk a táblák adatszerkezete segített nekünk. Például a táblázat history a következő szerkezettel rendelkezik:
`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',ezek mellett
KEY `history_1` (`itemid`,`clock`) Ahogy látjuk, minden metrika végül két nagyon fontos és számunkra kényelmes mezőbe kerül a táblába itemid és clock. Így teljesen létrehozhatunk egy ideiglenes táblát, például a nevét history_tmp, beállíthatjuk a partícionálását, majd átkonvertálhatjuk az összes adatot a táblából history, utána átnevezzük a táblát history be. history_old, majd a táblát history_tmp be. history, ezután pedig feltöltjük azokat az adatokat, amelyeket nem sikerült átkonvertálni a history_old be. history és töröljük history_old. Ezt teljesen biztonságosan végrehajthatjuk, nem veszítünk semmit, hiszen a fent említett mezők itemid és clock biztosítják a konkrét metrika adott időponthoz való kötését, nem pedig valamilyen sorszámhoz.
Áttérés menete
Figyelem! Nagyon ajánlott, hogy bármilyen tevékenység előtt készítsünk egy teljes biztonsági mentést az adatbázisból. Mindannyian élő emberek vagyunk, és hibázhatunk a parancsok bevitelénél, ami adatvesztéshez vezethet. Igen, a biztonsági másolat nem biztosít maximális aktualitást, de jobb, ha van egy, mint ha nincs.
Tehát semmit sem kapcsolunk ki vagy állítunk le. A legfontosabb, hogy a MySQL szerveren elegendő szabad lemezterület álljon rendelkezésre, tehát minden felsorolt táblához history, history_text, history_str, history_uint, legalább elegendő hely álljon rendelkezésre egy '_tmp' végződésű tábla létrehozásához, figyelembe véve, hogy az olyan méretű lesz, mint az eredeti tábla.
Nem fogunk minden egyes fent említett táblát többször leírni, hanem csak egy példa alapján fogjuk megvizsgálni - a táblát history.
Tehát, létrehozunk egy üres táblát history_tmp a tábla struktúrája alapján history.
CREATE TABLE `history_tmp` LIKE `history`;Meghatározzuk a szükséges partíciókat. Például ezt egy hónapra fogjuk megtenni. Minden partíció a particionálási szabály alapján jön létre, amely a mező értékén alapul clock, amelyet az időbélyeggel összehasonlítunk:
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"))
); Ez a parancs particionálást ad hozzá az általunk létrehozott táblához. history_tmpTisztázzuk, hogy azok az adatok, amelyek mezőértéke clock kisebb mint „2019-02-01 00:00:00”, az p20190201 partícióba fognak kerülni. p20190201, azután azok az adatok, amelyek mezőértéke clock nagyobb mint „2019-02-01 00:00:00”, de kisebb mint „2019-02-07 00:00:00”, a p20190207 partícióba fognak kerülni. p20190207 és így tovább.
Fontos megjegyzés: Mi fog történni, ha a particionált táblában olyan adatok jelennek meg, amelynek clock mezőértéke nagyobb vagy egyenlő „2019-03-01 00:00:00”-nál? Mivel ezekhez az adatokhoz nincs megfelelő partíció, nem kerülnek a táblába, és elvesznek. Ezért nem szabad elfelejtenie időben létrehozni további partíciókat, hogy elkerülje az ilyen adatvesztést (erről lentebb).
Tehát, a ideiglenes tábla előkészítve. Töltsük fel az adatokat. A folyamat elég sokáig tarthat, de szerencsére nem blokkolja semmilyen más lekérdezést, így csak türelemmel kell lennie:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;Az IGNORE kulcsszó a kezdeti feltöltésnél nem kötelező, hiszen az adatokat a táblában még nem tartalmazza, de szüksége lesz rá az adatok utólagos feltöltésénél. Ezen kívül hasznos lehet, ha az adatok feltöltése során meg kellett szakítania ezt a folyamatot és újra kellett kezdenie.
Tehát, egy idő elteltével (lehet, hogy akár több órányi), az első adatfeltöltés megtörtént. Ahogy érthető, most a tábla history_tmp nem tartalmazza az összes adatot a táblából, historycsak azokat, amelyek akkor voltak benne, amikor a lekérdezés végrehajtását elkezdték. Itt valójában választania kell: vagy csinálunk egy újabb átjárást (ha a feltöltési folyamat hosszú volt), vagy azonnal áttérünk a táblanevek átnevezésére, ahogyan az fentebb említve volt. Először nézzük a második átjárást. Először meg kell értenünk az utolsó beillesztett felvétel idejét history_tmp:
SELECT max(clock) FROM history_tmp;Tegyük fel, hogy ezt kaptad: 1551045645. Most használjuk a második adatfeltöltés során kapott értéket:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;Ennek a folyamatnak jelentősen gyorsabban kellene véget érnie. De ha az első folyamat órákig tartott, és a második is hosszú időt vesz igénybe, lehet, hogy érdemes lenne egy harmadik folyamatot végezni, amely teljesen hasonlóan zajlik, mint a második.
A végén ismét végrehajtjuk a legutoljára beszúrt bejegyzés időpontjának lekérdezését history_tmp, végrehajtva:
SELECT max(clock) FROM history_tmp;Tételezzük fel, hogy megkaptad 1551085645. Mentsd el ezt az értéket — szükségünk lesz rá a további feltöltéshez.
Most pedig, amikor az elsődleges adatfeltöltés a history_tmp befejeződött, hozzáfogunk a táblák átnevezéséhez:
BEGIN;
RENAME TABLE history TO history_old;
RENAME TABLE history_tmp TO history;
COMMIT; Ezt a blokkot egy tranzakcióként alakítottuk meg, hogy elkerüljük, hogy adatok kerüljenek egy nem létező táblába, hiszen az első RENAME után, mielőtt a második RENAME végrehajtódna, a tábla history nem fog létezni. De még ha a RENAME műveletek között bármilyen adat is érkezik a táblába, és a tábla még nem létezik (az átnevezés miatt), kapunk néhány kis beszúrási hibát, amelyeket figyelmen kívül hagyhatunk (hiszen nekünk monitorozásunk van, nem bankunk). history Most már van új táblánk
particionálással, de hiányzik belőle az az adat, amely az utolsó adatfeltöltés során került a táblába history . De ezek az adatok megvannak a táblában history_tmp, és most onnan feltöltjük őket. Ehhez szükségünk lesz a korábban elmentett 1551085645 értékre. Miért ezt az értéket mentettük el, és nem a maximális feltöltési időt használtuk a jelenlegi táblából? history_old INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; history? Потому что новые данные уже в неё поступают и мы получим неверное время. Итак, дозаливаем данные:
A művelet befejezése után az új, particionált táblánkban minden adatunk megvan, ami a régi táblában volt, plusz azok, amelyek már érkeztek a tábla átnevezése után. A tábla history többé nem szükséges. Azonnal törölhetjük, de törlés előtt készíthetünk róla biztonsági másolatot (ha pánikolunk). history_old A fent leírt folyamatot meg kell ismételni a táblákhoz
A Zabbix Server beállításain szükséges javítások history_str, history_text és history_uint.
Mit kell módosítani a Zabbix Server beállításaiban
Most már a számviteli adatbázis karbantartása a mi feladatunk. Ez azt jelenti, hogy a Zabbixnak többé nem kell törölnie a régi adatokat - ezt mi végezzük el. Ahhoz, hogy a Zabbix Server ne próbálja meg automatikusan tisztítani az adatokat, be kell lépnie a Zabbix webböngészőjébe, válassza az 'Adminisztráció' menüpontot, majd az 'Általános' almennyt, végül a jobb oldali legördülő menüből válassza a 'Történet törlése' lehetőséget. A megjelenő oldalon töröljön minden jelölést a 'Történet' csoportból, majd kattintson a 'Frissítés' gombra. Ez megakadályozza a nem kívánt táblák tisztítását. történet* a házi gondnok által.
Ugyanezen az oldalon figyeljen a 'Változások dinamikája' csoportra. Ez a táblázat trends, amelyhez visszaígértünk. Ha ez a táblázat túl nagyra nőtt, és particionálásra szorul, akkor a jelöléseket itt is vegye le, majd kezelje ezt a táblázatot pontosan úgy, mint a többi táblázat esetében. történet*.
A jövőbeli adatbázis karbantartás
Ahogy korábban említve volt, a particionált táblák normális működéséhez időben létre kell hozni a partíciókat. Ezt így lehet megtenni:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));Ezen kívül, mivel particionált táblákat hoztunk létre, és megtiltottuk a Zabbix Servernek azokat a tisztítást, így a régi adatok törlése már a mi gondunk. Szerencsére itt semmilyen probléma nincsen. Ezt egyszerűen a szükségtelenné vált partíció törlésével végezhetjük el.
Például:
ALTER TABLE history DROP PARTITION p20190201;A DATUM INTERVALLUMON alapuló DELETE FROM operátorokkal ellentétben a DROP PARTITION néhány másodperc alatt végrehajtódik, és nem terheli meg a rendszert szerver és ugyanúgy problémamentesen működik MySQL replikáció használata esetén is.
Zárás
Az itt leírt megoldás az idő próbáját kiállta. Az adatmennyiség nő, de lényeges teljesítménycsökkenést nem észleltek.
Forrás: habr.com
