MySQL-də Zabbix üçün partisiya istifadəsi ilə çox sayda monitorinq obyekti

Server və xidmətlərin monitorinqi üçün biz uzun müddət və hələ də uğurla Nagios və Munin əsasında birləşdirilmiş həll istifadə edirik. Lakin bu birləşmənin bir sıra çatışmazlıqları var, ona görə biz, digər bir çoxu kimi, aktiv şəkildə Zabbix. Bu məqalədə artırılan metrikin sayı və MySQL verilənlər bazasının həcminin böyüməsi zamanı performans problemini minimum səy göstərməklə necə həll edəcəyimizi izah edəcəyik.

Zabbix ilə MySQL verilənlər bazasının birgə istifadəsi ilə bağlı problemlər

Verilənlər bazası kiçik olduqda və orada saxlanan metriklərin sayı az olduqda, hər şey möhtəşəm idi. Zabbix Server tərəfindən işə salınan standart housekeeper prosesi köhnə qeydləri müvəffəqiyyətlə silərək verilənlər bazasının böyüməsinə imkan vermirdi. Lakin, metriklərin sayı artdıqca və verilənlər bazasının həcmi müəyyən bir ölçüyə çatdıqca, vəziyyət pisləşdi. Housekeeper ayrılmış vaxt intervalında məlumatları silə bilmir və verilənlər bazasında köhnə məlumatlar qalırdı. Housekeeper-in işi zamanı Zabbix Serverə yüksək yük düşürdü ki, bu da uzun müddət davam edirdi. Aydın oldu ki, vəziyyəti həll etmək lazımdır.

Bu, tanış bir problemdir, demək olar ki, böyük monitorinq həcmləri ilə işləyən hər kəs eyni ilə qarşılaşıb. Həlli də bir neçə olub: məsələn, MySQL-in PostgreSQL ilə əvəz olunması və ya hətta Elasticsearch-ə keçid, amma ən sadə və təsdiq edilmiş həll partisiya olunmuş cədvəllərə keçid oldu, hansılar ki, verilənlər bazasında metrik məlumatları saxlayır. Biz də bu yolla getməyi qərara aldıq.

Adi MySQL cədvəllərindən partisiya olunmuşlara keçid

Zabbix yaxşı sənədləşdirilmişdir və onun metrikləri saxladığı cədvəllər məlumdur. Bu cədvəllər: history, burada float dəyərləri saxlanılır, history_str, burada qısa string dəyərləri saxlanılır, history_text, burada uzun tekst dəyərləri saxlanılır və history_uint, burada tam ədədi dəyərlər saxlanılır. Həmçinin trends, burada dəyişikliklərin dinamikası saxlanılır, amma biz buna toxunmadıq, çünki onun ölçüsü kiçikdir və biraz sonra ona geri dönəcəyik.

Ümumiyyətlə, hansı cədvəlləri işləmək lazım olduğu aydındır. Hər həftə üçün, sonuncu xaric, ayın günlərinə əsasən dörd partisiya edərək hər ay üçün planlaşdırdıq: 1-dən 7-yə, 8-dən 14-ə, 15-dən 21-ə və 22-dən 1-ə (növbəti ayın). Çətinlik, lazım olan cədvəlləri "havada" partisiya etmək, Zabbix Server-in işini və metriklərin toplanmasını dayandırmadan olurdu.

Təəccüblü deyil ki, bu məsələdə bizə cədvəlin verilənlər strukturunun köməyi oldu. Məsələn, cədvəl history aşağıdakı struktura malikdir:

`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',

bu zaman

KEY `history_1` (`itemid`,`clock`)

Gördüyünüz kimi, hər bir metrik nəticədə iki çox önəmli və bizim üçün əlverişli sahəsi olan cədvələ daxil edilir itemidclock. Bununla da, biz tamamilə müvəqqəti cədvəl yarada bilərik, məsələn, adla history_tmp, onun üçün partiləşdirməni tənzimləyib sonra cədvəldən bütün məlumatları oraya köçürə bilərik history, sonra cədvəli adını dəyişdirə bilərik history daxilindədir. history_old, cədvəl history_tmp daxilindədir. history, daha sonra isə, bizdə qeydə alınmamış olan məlumatları əlavə edə bilərik history_old daxilindədir. history və silə bilərik history_old. Bunu tamamilə təhlükəsiz bir şəkildə edə bilərik, çünki yuxarıda qeyd olunan sahələr itemid clock müəyyən bir metrikanı müəyyən bir vaxtda bağlayır, nə də bir sıra nömrəsinə.

Keçid prosesi

Qeyd! Hər hansı bir hərəkətə başlamazdan əvvəl, verilənlər bazasından tam bir ehtiyat nüsxəsi yaratmaq çox arzuolunandır. Biz insanlarıq və komandaları dəqiq yazmaqda səhv edə bilərik ki, bu da məlumat itkilərinə səbəb ola bilər. Bəli, ehtiyat nüsxəsi maksimum doğruluq təmin etməyəcək, amma belə bir nüsxənin olması, ümumiyyətlə, olmamasından daha yaxşıdır.

Beləliklə, heç nəyi söndürmürük və dayandırmırıq. Əsas odur ki, MySQL serverində kifayət qədər boş disk sahəsi olsun, yəni yuxarıda qeyd olunan hər bir cədvəl üçün history, history_text, history_str, history_uint, ən azı, «_tmp» əlavəsi olan cədvəlin yaradılması üçün yer olmalıdır, nəzərə alaraq ki, o, orijinal cədvələ bənzəyəcək.

Biz bütün bu yuxarıda göstərilən cədvəllər üçün eyni şeyi bir neçə dəfə təsvir etməyəcəyik və yalnız bir nümunədən — cədvəl history.

Beləliklə, boş bir cədvəl yaradırıq history_tmp cədvəlin strukturuna əsaslanaraq history.

CREATE TABLE `history_tmp` LIKE `history`;

Lazımi partiyaları yaradırıq. Nümunə üçün, bunu ay üzrə edək. Hər bir partiya, sahənin dəyəri əsasında partiyalaşdırma qaydasına əsaslanaraq yaradılır clock, biz onu vaxt nişanı ilə müqayisə edirik:

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"))
);

Bu operator, bizim yaratdığımız cədvələ partiləşdirməni əlavə edir. history_tmpDaha dəqiqi, dəyəri olan məlumatlar clock «2019-02-01 00:00:00»-dan kiçik olanlar p20190201 partiyasına düşəcək, sonra sahənin dəyəri clock «2019-02-01 00:00:00»-dan böyük, amma «2019-02-07 00:00:00»-dan kiçik olan məlumatlar p20190207 partiyasına düşəcək və s.

Vacib qeyd: Bəs əgər bizim partiləşdirilmiş cədvəldə «2019-03-01 00:00:00»-dan böyük və ya bərabər olan clock sahəsinin dəyəri varsa, nə baş verəcək? Bu məlumatlar üçün uyğun bir partiya olmadığı üçün onlar cədvələ düşməyəcək və itəcək. Buna görə də, əlavə partiyalar yaratmağı unutmamalısınız ki, belə məlumat itkilərini qarşısını alasınız (bununla bağlı aşağıda).

Beləliklə, müvəqqəti cədvəl hazırdır. Məlumatları yükləyəndə proses bir müddət apara bilər, amma şükürlər olsun ki, bu, hansısa digər sorğuları bloklamır, ona görə yalnızca səbr etməliyik:

INSERT IGNORE INTO `history_tmp` SELECT * FROM history;

Başlanğıc yükləmədə IGNORE açar sözü mütləq deyil, çünki cədvəldə hələ də məlumat yoxdur; amma məlumatların əlavə edilməsi zamanı buna ehtiyacınız olacaq. Bundan əlavə, əgər yükləmə prosesi zamanı bu prosesi dayandırmalısınızsa və yenidən başlayırsınızsa, bu da faydalı ola bilər.

Beləliklə, bir müddətdən sonra (bəlkə də bir neçə saat), ilk məlumat yüklənməsi başa çatdı. Anlayacağınız kimi, indi cədvəl history_tmp cədvəldəki bütün məlumatları deyil history, başladığınız zaman cədvəldə olanlarla yalnız. Burada əslində seçim var: ya bir daha keçid edirik (əgər yükləmə prosesi uzun sürübsə), ya da yuxarıda qeyd edildiyi kimi cədvəllərin adını dəyişdirməyə geçirik. Gəlin əvvəlcə ikinci keçid haqqında danışaq. Əvvəlcə, bizə history_tmp:

SELECT max(clock) FROM history_tmp;

dəyərləri alırıq. Təsəvvür edək ki, siz aldınız: 1551045645. İndi ikinci məlumat yükləmə keçidində əldə edilmiş dəyəri istifadə edək:

INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;

Bu keçid əhəmiyyətli dərəcədə daha sürətli başa çatmalıdır. Amma əgər ilk keçid saatlarla davam edirdisə, ikinci keçidin də uzun sürməsi halında, bəlkə də, üçüncü keçid etmək düzgün olacaq, bu da ikinciyə tamamilə bənzər şəkildə həyata keçiriləcək.

Nəticədə, yenidən daxil olan sonunun vaxtını əldə etmə əməliyyatını yerinə yetiririk history_tmp, icra edərək:

SELECT max(clock) FROM history_tmp;

Təsəvvür edək ki, siz aldınız 1551085645. Bu dəyəri saxlayın — bu, məlumat yükləmələrini tamamlamak üçün lazım olacaq.

İndi, ilk məlumat yüklənməsi bitdikdən sonra, cədvəllərin adını dəyişməyə başlayırıq: history_tmp BEGIN; RENAME TABLE history TO history_old; RENAME TABLE history_tmp TO history; COMMIT;

Biz bu bloku bir tranzaksiya olaraq tərtib etdik ki, məlumatların mövcud olmayan cədvələ daxil edilməsinin qarşısını alaq, çünki ilk RENAME-dan ikinci RENAME-ə keçən müddətdə cədvəl

mövcud olmayacaq. Amma RENAME əməliyyatları arasında cədvələ hər hansı məlumat daxil olarsa, özü də cədvəl hələ də olmayacaq (adın dəyişməsinə görə), biz daxil etmə səhvlərinin az miqdarını alacağıq ki, bunları əhəmiyyətsiz saymaq olar (bizim monitorinqimiz var, bank deyil). history İndi yeni cədvəlimiz var history partisyasiyaladır, amma son yükləmə zamanı əldə edilən məlumatlar çatmır

. Amma bu məlumatlar bizdə history və onları oradan dolduracağıq. Bunun üçün, əvvəlki saxlanmış dəyər olan 1551085645-ə ehtiyacımız var. Niyə bu dəyəri saxladıq, niyə də indi cədvəldən maksimum yükləmə zamanını istifadə etmədik. history_tmpINSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645; history_old və biz onları oradan əlavə edəcəyik. Bunun üçün əvvəllər saxlanılan 1551085645 dəyərinə ehtiyacımız var. Niyə bu dəyəri saxladıq, halbuki indiki cədvəldən maksimum doldurma vaxtını istifadə etmədik? history? Потому что новые данные уже в неё поступают и мы получим неверное время. Итак, дозаливаем данные:

INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645;

Bu əməliyyatdan sonra, yeni, partiyalaşdırılmış cədvəlimizdə history köhnə cədvəldə olan bütün məlumatları, üstəlik cədvəl adının dəyişdirilməsindən sonra gələn veriləri də əldə edəcəyik. Cədvəl history_old artıq lazım deyil. Onu dərhal silmək olar, ya da silməzdən əvvəl ehtiyat nüsxəsini çıxarmaq olar (əgər narahatlığınız varsa).

Yuxarıda təsvir edilən prosesi cədvəllər üçün təkrar etmək lazımdır history_str, history_text history_uint.

Zabbix Server-in tənzimləmələrində nəyi düzəltmək lazımdır

İndi verilənlər bazasının tarixi məlumatların idarəsi bizim üzərimizə düşür. Bu, Zabbix-in köhnə məlumatları silməməsi deməkdir — biz bunu özümüz edəcəyik. Zabbix Server-in öz-özünə məlumatları təmizləməsinin qarşısını almaq üçün, Zabbix web-interfeysində «İdarəetmə» menyusuna daxil olun, sonra «Ümumi» alt menyusunu seçin, sonra sağdakı açılan siyahıda «Tarixlərin Təmizlənməsi» seçin. Açılan səhifədə «Tarix» qrupunun bütün işarələrini ləğv edin və «Yeniləmə» düyməsini basın. Bu, cədvəllərin lazımsız təmizlənməsini əngəlləyəcək history* housekeeper vasitəsilə.

Həmin səhifədə «Dəyişiklik Dinamikası» qrupu ilə diqqət yetirin. Bu, bizə geri qayıtmağı vəd etdiyimiz cədvəl trendsdır, əgər o da artıq çox böyükdürsə və partiyalaşdırma tələb edirsə, bu qrupda işarələri ləğv edin, sonra bu cədvəli də eynilə yuxarıda olduğu kimi idarə edin history*.

Verilənlər bazasının daha da idarəsi

Əvvəlki yazılara əsasən, partiyalaşdırılmış cədvəllərdə düzgün işləmək üçün partiyaların vaxtında yaradılması vacibdir. Bunu belə etmək mümkündür:

ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));

Bundan əlavə, Zabbix Server-ə partiyalaşdırılmış cədvəlləri təmizləməyi qadağan etdiyimiz üçün köhnə məlumatların silinməsi artıq bizim məsuliyyətimizdir. Xoşbəxtlikdən, burada heç bir problem yoxdur. Bu, sadəcə, lazım olmayan partiyanı silməklə həyata keçirilir.

Məsələn:

ALTER TABLE history DROP PARTITION p20190201;

Məlumat aralığını göstərməklə DELETE FROM operatorlarından fərqli olaraq, DROP PARTITION bir neçə saniyə içində icra olunur, tamamilə yük yaratmır server və MySQL replikasiyasında istifadə edildikdə də problem yarada bilmir.

Nəticə

Təsvir olunan həll zamanla sınaqdan keçirilib. Məlumat həcmi artır, lakin hansısa əhəmiyyətli performans sürətlənməsi müşahidə edilmir.

Mənbə: habr.com

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster