Pro monitorování serverů a služeb jsme dlouho, a stále úspěšně, používali kombinované řešení založené na Nagios a Munin. Tato kombinace však má řadu nedostatků, a proto my, jako mnozí, aktivně využíváme . V tomto článku se podělíme o to, jak lze s minimálním úsilím vyřešit problém výkonnosti při zvyšujícím se počtu sledovaných metrik a růstu objemů databáze MySQL.
Problémy používání databáze MySQL ve spolupráci se Zabbixem
Dokud byla databáze malá a počet v ní uložených metrů malý, bylo vše skvělé. Standardní proces housekeeper, který spouští sám Zabbix Server, úspěšně odstraňoval zastaralé záznamy z databáze, čímž nedovolil jejímu růstu. Jakmile však počet sledovaných metrik vzrostl a objem databáze dosáhl určité velikosti, situace se zhoršila. Housekeeper přestal stíhat odstraňovat data ve stanoveném čase, v databázi zůstávala stará data. Během činnosti housekeepera byla na Zabbix Server vyvíjena zvýšená zátěž, která mohla trvat dlouhou dobu. Bylo jasné, že je třeba nějak vyřešit vzniklou situaci.
To je známý problém, s nímž se prakticky každý, kdo pracoval s velkými objemy monitorování na Zabbix, setkal. Řešení bylo několik: například přechod z MySQL na PostgreSQL nebo dokonce Elasticsearch, ale nejjednodušším a osvědčeným řešením byl přechod k partitionování tabulek, které ukládají data metrik v databázi MySQL. Rozhodli jsme se jít právě touto cestou.
Přechod od obvyklých tabulek MySQL k partitionovaným
Zabbix je dobře zdokumentován a tabulky, ve kterých ukládá metriky, jsou známy. Jsou to tabulky: history, kde se ukládají float hodnoty, history_str, kde se ukládají krátké řetězcové hodnoty, history_text, kde se ukládají dlouhé textové hodnoty a history_uint, kde se ukládají celočíselné hodnoty. Je také tabulka trends, která ukládá dynamiku změn, ale tu jsme se rozhodli nedotýkat, protože její velikost je malá a později se k ní vrátíme.
Celkově bylo jasné, které tabulky je potřeba zpracovat. Rozhodli jsme se vytvořit partitiony na každý týden, s výjimkou posledního, na základě čísel měsíce, tj. čtyři partitiony za měsíc: od 1. do 7., od 8. do 14., od 15. do 21. a od 22. do 1. (následujícího měsíce). Obtížnost spočívala v tom, že bylo potřeba převést požadované tabulky na partitionované „za běhu“, aniž by došlo k přerušení práce Zabbix Serveru a shromažďování metrik.
Jak nečekané, pomoc přišla sama od struktury dat tabulek. Například tabulka history má následující strukturu:
`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řičemž
KEY `history_1` (`itemid`,`clock`) Jak vidíme, každá metrika je nakonec zaznamenána v tabulce se dvěma velmi důležitými a výhodnými pro nás poli itemid a clock. Tak můžeme vytvořit dočasnou tabulku, například s názvem history_tmp, nastavit pro ni partitioning a poté do ní přenést všechna data z tabulky history, poté přejmenovat tabulku history v history_old, a tabulku history_tmp v history, po kterém doplníme ta data, která nám chyběla z history_old v history a odstranit history_old. Můžeme to udělat zcela bezpečně, nic neztratíme, protože výše uvedená pole itemid a clock zajišťují přidružení konkrétní metriky k určitému času, nikoli k nějakému pořadovému číslu.
Samotný přechod
Pozor! Je velmi žádoucí, před zahájením jakýchkoli akcí, provést úplnou zálohu databáze. Jsme všichni živí lidé a můžeme udělat chybu při zadávání příkazů, což může vést ke ztrátě dat. Ano, záloha nezajistí maximální aktuálnost, ale je lepší mít nějakou než žádnou.
Takže nic nevypínáme a nezastavujeme. Hlavní je, aby na samotném MySQL serveru bylo dostatečné množství volného místa na disku, tj. aby pro každou z výše uvedených tabulek history, history_text, history_str, history_uint, bylo alespoň dost místa na vytvoření tabulky s příponou „_tmp“, přičemž bude mít objem stejný jako původní tabulka.
Nebudeme popisovat vše několikrát pro každou z uvedených tabulek a zaměříme se pouze na jeden příklad — tabulku history.
Takže vytváříme prázdnou tabulku history_tmp na základě struktury tabulky history.
CREATE TABLE `history_tmp` LIKE `history`;Vytváříme potřebné partice. Pro příklad to uděláme na měsíc. Každá partice se vytváří na základě pravidla rozdělení na hodnotu pole clock, které porovnáváme s časovým razítkem:
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"))
); Tento příkaz přidává rozdělení pro námi vytvořenou tabulku history_tmp. Ujasněme, že data, jejichž hodnota pole clock je menší než „2019-02-01 00:00:00“, se dostanou do partice p20190201, poté data, jejichž hodnota pole clock je větší než „2019-02-01 00:00:00“, ale menší než „2019-02-07 00:00:00“, se dostanou do partice p20190207 a tak dále.
Důležitá poznámka: Co se stane, pokud se v naší rozdělené tabulce objeví data, jejichž hodnota pole clock bude větší nebo rovna „2019-03-01 00:00:00“? Protože pro tato data neexistuje vhodná partice, nebudou zařazena do tabulky a budou ztracena. Proto je nutné nezapomínat včas vytvářet další partitce, aby se předešlo těmto ztrátám dat (o tom, co se stane níže).
Takže, dočasná tabulka je připravena. Nahráváme data. Proces může trvat docela dlouho, ale naštěstí nezablokuje žádné jiné dotazy, takže je potřeba jen pořídit si trpělivost:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;Klíčové slovo IGNORE při prvotním nahrávání není povinné, protože v tabulce stejně žádná data nejsou, ale to budete potřebovat při dodatečném nahrávání dat. Kromě toho se může ukázat užitečné, pokud jste museli během nahrávání přerušit tento proces a začít znovu.
Takže po nějakém čase (možná dokonce několika hodinách) proběhlo první nahrávání dat. Jak chápete, nyní tabulka history_tmp obsahuje ne všechna data z tabulky history, ale pouze ta, která byla v ní v okamžiku zahájení provádění dotazu. Tady máte na výběr: buď provedeme ještě jeden průchod (pokud proces nahrávání trval dlouho), nebo hned přejdeme k přejmenování tabulek, o kterém se hovořilo výše. Nejprve se podívejme na druhý průchod. Nejdříve musíme zjistit čas posledního vloženého záznamu v history_tmp:
SELECT max(clock) FROM history_tmp;Předpokládejme, že jste obdrželi: 1551045645Nyní použijeme získanou hodnotu při druhém průchodu vkládání dat:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;Tento průchod by měl skončit mnohem rychleji. Pokud však první průchod trval hodiny a druhý také trvá dlouho, možná bude správné provést i třetí průchod, který se provádí přesně stejně jako druhý.
Nakonec znovu provedeme operaci získání času posledního vložení záznamu do history_tmp, a to provedením:
SELECT max(clock) FROM history_tmp;Představme si, že jste získali 1551085645. Uložte tuto hodnotu — budeme ji potřebovat pro dodatečné vkládání.
A nyní vlastně, když primární vkládání dat do history_tmp skončilo, přistupujeme k přejmenování tabulek:
BEGIN;
RENAME TABLE history TO history_old;
RENAME TABLE history_tmp TO history;
COMMIT; Tento blok jsme zformátovali jako jednu transakci, abychom se vyhnuli situaci, kdy se data vkládají do neexistující tabulky, protože po prvním RENAME do doby provedení druhého RENAME, tabulka history existovat nebude. Ale i když mezi operacemi RENAME do tabulky history přijdou nějaká data a samotná tabulka ještě nebude (kvůli přejmenování), dostaneme malé množství chyb vkládání, kterými se můžeme zabývat (máme monitoring, ne banku).
Nyní máme novou tabulku history s partitonací, ale chybí v ní data, která byla získána během posledního průchodu vkládání dat do tabulky history_tmp. Ale tato data máme v tabulce history_old a nyní je odtud doplníme. Za tímto účelem budeme potřebovat dříve uloženou hodnotu 1551085645. Proč jsme tuto hodnotu uložili, a nevyužili maximální čas vkládání již z aktuální tabulky history? Потому что новые данные уже в неё поступают и мы получим неверное время. Итак, дозаливаем данные:
INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; Po dokončení této operace máme v nové, partitonované tabulce history všechna data, která byla ve staré, plus ta, která již přišla po přejmenování tabulky. Tabulka history_old už není potřeba. Můžeme ji okamžitě smazat, nebo ji před smazáním zálohovat (pokud máte paranoia).
Celý výše popsáný proces je třeba opakovat pro tabulky history_str, history_text a history_uint.
Co je třeba opravit v nastaveních Zabbix Server
Nyní údržba databáze v části historie dat padá na naše ramena. To znamená, že Zabbix už nemusí odstraňovat stará data — budeme se o to starat sami. Aby se Zabbix Server nesnažil data čistit sám, musíte se přihlásit do webového rozhraní Zabbix, vybrat z menu „Administrace“, poté podmenu „Obecné“, a pak v pravém rozevíracím seznamu vybrat „Údržba historie“. Na zobrazené stránce je třeba odškrtnout všechny zaškrtávací políčka pro skupinu „Historie“ a kliknout na tlačítko „Aktualizovat“. To zabrání nežádoucímu čištění tabulek, které nepotřebujeme. historie* přes housekeeper.
Všimněte si na této stránce skupiny „Dynamika změn“. To je přesně tabulka trends, na kterou jsme slíbili vrátit se. Pokud se vám také stala příliš velkou a potřebuje být partition, odškrtněte políčka i v této skupině a poté zpracujte tuto tabulku stejně jako bylo provedeno u tabulek historie*.
Další údržba databáze
Jak bylo napsáno dříve, pro normální fungování na partition tabulkách je nutné včas vytvářet partition. Můžete to udělat takto:
ALTER TABLE `historie` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));Kromě toho, protože jsme vytvořili partition tabulky a zakázali Zabbix Serveru je čistit, je nyní odstraňování starých dat naší starostí. Naštěstí s tím nejsou vůbec žádné problémy. Provádí se jen odstraněním té partition, jejíž data už nepotřebujeme.
Například:
ALTER TABLE historie DROP PARTITION p20190201;Na rozdíl od příkazů DELETE FROM s určením časového rozsahu se DROP PARTITION provádí za pár sekund, vůbec nezatěžuje server a stejně bezproblémově funguje v případě použití replikace MySQL.
Závěr
Popisované řešení bylo ověřeno časem. Objem dat roste, ale nezaznamenalo se žádné výrazné zpomalení výkonu.
Zdroj: habr.com
