Untuk memantau server dan layanan, kami telah lama, dan masih berhasil, menggunakan solusi gabungan berdasarkan Nagios dan Munin. Namun, banyak ini memiliki sejumlah kelemahan, jadi kami, seperti banyak orang, secara aktif mengeksploitasi
Zabbix . Pada artikel ini, kita akan berbicara tentang bagaimana Anda dapat memecahkan masalah kinerja dengan upaya minimal ketika meningkatkan jumlah metrik yang dilepas dan meningkatkan volume basis data MySQL
Masalah menggunakan database MySQL dengan Zabbix
Meskipun basis datanya kecil dan jumlah metrik yang tersimpan di dalamnya kecil, semuanya indah. Proses pembantu rumah tangga biasa yang memulai Zabbix Server sendiri berhasil menghapus catatan usang dari basis data, mencegahnya berkembang. Namun, segera setelah jumlah metrik yang ditangkap meningkat dan ukuran basis data mencapai ukuran tertentu, semuanya menjadi lebih buruk. Houserkeeper berhenti mengelola untuk menghapus data untuk interval waktu yang ditentukan, data lama mulai tetap berada di database. Selama operasi pengurus rumah tangga ada peningkatan beban pada Server Zabbix, yang bisa bertahan lama. Menjadi jelas bahwa itu perlu untuk entah bagaimana menyelesaikan situasi saat ini.
Ini adalah masalah yang diketahui, hampir semua orang yang bekerja dengan pemantauan Zabbix dalam jumlah besar menghadapi hal yang sama. Ada beberapa solusi juga: misalnya, mengganti MySQL dengan PostgreSQL atau bahkan Elasticsearch, tetapi solusi paling sederhana dan paling terbukti adalah beralih ke tabel partisi yang menyimpan data metrik dalam database MySQL. Kami memutuskan untuk pergi dengan cara ini.
Bermigrasi dari tabel MySQL biasa ke yang dipartisi
Zabbix didokumentasikan dengan baik dan tabel tempat menyimpan metrik diketahui. Ini adalah tabel:
history
, tempat nilai float disimpan,
history_str
, tempat nilai string pendek disimpan,
history_text
, tempat nilai teks yang panjang disimpan, dan
history_uint
, tempat nilai integer disimpan. Ada juga tabel
trends
yang menyimpan dinamika perubahan, tetapi kami memutuskan untuk tidak menyentuhnya, karena ukurannya kecil dan sedikit kemudian kami akan kembali ke sana.
Secara umum, tabel apa yang perlu diproses jelas. Kami memutuskan untuk membuat partisi untuk setiap minggu, dengan pengecualian yang terakhir, berdasarkan pada jumlah bulan, mis. empat partisi per bulan: dari 1 ke 7, dari 8 ke 14, dari 15 ke 21 dan dari 22 ke 1 (bulan berikutnya). Kesulitannya adalah bahwa kami harus mengubah tabel yang kami butuhkan menjadi dipartisi "on the fly," tanpa mengganggu Zabbix Server dan mengumpulkan metrik.
Anehnya, struktur tabel-tabel ini sangat membantu kami dalam hal ini. Misalnya, tabel
history
memiliki struktur berikut:
`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',
sementara
KEY `history_1` (`itemid`,`clock`)
Seperti yang Anda lihat, setiap metrik akhirnya dimasukkan ke dalam tabel dengan dua bidang yang sangat penting dan nyaman bagi kami
itemid dan
jam . Dengan demikian, kita bisa membuat tabel sementara, misalnya, dengan nama
history_tmp
, mengatur
history_tmp
untuk itu dan kemudian mentransfer semua data dari tabel
history
sana, dan kemudian mengganti nama tabel
history
ke
history_old
, dan tabel
history_tmp
ke
history
, kemudian menambahkan data yang kami kurang isi dari
history_old
ke
history
dan hapus
history_old
. Anda dapat melakukan ini sepenuhnya dengan aman, kami tidak akan kehilangan apa-apa, karena bidang
itemid dan
jam yang ditunjukkan di atas memberikan metrik tautan ke waktu tertentu, dan bukan ke semacam nomor seri.
Prosedur transisi itu sendiri
Perhatian! Sebelum memulai tindakan apa pun, sangat diinginkan untuk membuat cadangan lengkap dari basis data. Kita semua adalah orang yang hidup dan dapat membuat kesalahan dalam serangkaian perintah, yang dapat menyebabkan hilangnya data. Ya salinan cadangan tidak akan memberikan relevansi maksimum, tetapi lebih baik memilikinya daripada tidak sama sekali.
Jadi, jangan mematikan atau menghentikan apa pun. Yang utama adalah bahwa pada server MySQL itu sendiri harus ada ruang disk kosong yang cukup, mis. sehingga untuk masing-masing tabel di atas
history
,
history_text
,
history_str
,
history_uint
, setidaknya, ada cukup ruang untuk membuat tabel dengan akhiran "_tmp", mengingat bahwa itu akan menjadi jumlah yang sama dengan tabel asli.
Kami tidak akan menjelaskan semuanya beberapa kali untuk masing-masing tabel di atas dan mempertimbangkan semuanya dengan contoh hanya satu di antaranya - tabel
history
.
Jadi, buat tabel
history_tmp
kosong berdasarkan pada struktur tabel
history
.
CREATE TABLE `history_tmp` LIKE `history`;
Kami membuat partisi yang kami butuhkan. Sebagai contoh, mari kita lakukan selama sebulan. Setiap partisi dibuat berdasarkan aturan partisi, berdasarkan nilai bidang
jam , yang kami bandingkan dengan cap waktu:
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")) );
Operator ini menambahkan pemartisian ke tabel
history_tmp
kami buat. Mari kita perjelas bahwa data yang nilai bidang
jamnya kurang dari "2019-02-01 00:00:00" akan jatuh ke dalam partisi
p20190201 , maka data yang nilai bidang bidang
jamnya lebih besar dari "2019-02-01 00:00:00" tetapi kurang "2019-02-07 00:00:00" akan jatuh ke
p20190207 pihak dan sebagainya.
Catatan penting: Dan apa yang terjadi jika kita memiliki data di tabel dipartisi di mana nilai bidang jam lebih besar atau sama dengan "2019-03-01 00:00:00"? Karena tidak ada partisi yang cocok untuk data ini, mereka tidak akan jatuh ke dalam tabel dan akan hilang. Oleh karena itu, Anda tidak boleh lupa untuk membuat partisi tambahan secara tepat waktu, untuk menghindari kehilangan data seperti itu (tentang yang di bawah ini).
Jadi, meja sementara disiapkan. Isi data. Prosesnya bisa memakan waktu yang cukup lama, tetapi untungnya itu tidak memblokir permintaan lain, jadi Anda hanya harus bersabar:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history;
Kata kunci IGNORE tidak diperlukan selama pengisian awal, karena masih belum ada data dalam tabel, namun, Anda akan membutuhkannya saat menambahkan data. Selain itu, mungkin berguna jika Anda harus menghentikan proses ini dan mulai lagi saat mengisi data.
Jadi, setelah beberapa waktu (mungkin bahkan beberapa jam), unggahan data pertama telah berlalu. Seperti yang Anda pahami, sekarang tabel
history_tmp
tidak berisi semua data dari tabel
history
, tetapi hanya data yang ada di dalamnya pada saat kueri dimulai. Di sini, pada kenyataannya, Anda punya pilihan: apakah kami membuat satu pass lagi (jika proses pengisian berlangsung lama), atau kami segera melanjutkan untuk mengubah nama tabel yang disebutkan di atas. Pertama, mari kita ambil lintasan kedua. Pertama, kita perlu memahami waktu dari catatan yang dimasukkan terakhir di
history_tmp
:
SELECT max(clock) FROM history_tmp;
Misalkan Anda menerima:
1551045645 . Sekarang kita menggunakan nilai yang diperoleh di pass kedua dari pengisian data:
INSERT IGNORE INTO `history_tmp` SELECT * FROM history WHERE clock>=1551045645;
Perikop ini seharusnya berakhir lebih cepat. Tetapi jika oper pertama dilakukan berjam-jam, dan yang kedua juga dilakukan untuk waktu yang lama, mungkin benar untuk membuat oper ketiga, yang dilakukan benar-benar mirip dengan yang kedua.
Pada akhirnya, kami kembali melakukan operasi untuk mendapatkan waktu penyisipan terakhir catatan di
history_tmp
dengan melakukan:
SELECT max(clock) FROM history_tmp;
Katakanlah Anda mendapat
1551085645 . Simpan nilai ini - kita akan membutuhkannya untuk diisi ulang.
Dan sekarang, sebenarnya, ketika data primer yang mengisi
history_tmp
selesai, kami melanjutkan untuk mengubah nama tabel:
BEGIN; RENAME TABLE history TO history_old; RENAME TABLE history_tmp TO history; COMMIT;
Kami merancang blok ini sebagai satu transaksi untuk menghindari momen memasukkan data ke dalam tabel yang tidak ada, karena setelah RENAME pertama hingga RENAME kedua dijalankan, tabel
history
tidak akan ada. Tetapi bahkan jika beberapa data tiba antara operasi RENAME di tabel
history
dan tabel itu sendiri belum ada (karena penggantian nama), kami akan mendapatkan sejumlah kecil kesalahan penyisipan yang dapat diabaikan (kami memiliki pemantauan, bukan bank).
Sekarang kita memiliki tabel
history
baru dengan partisi, tetapi tidak memiliki cukup data yang diterima selama pass terakhir memasukkan data ke dalam tabel
history_tmp
. Tetapi kami memiliki data ini di tabel
history_old
dan sekarang kami membagikannya dari sana. Untuk ini, kita akan membutuhkan nilai yang disimpan sebelumnya 1551085645. Mengapa kita menyimpan nilai ini dan tidak menggunakan waktu pengisian maksimum sudah dari tabel
history
saat ini? Karena data baru sudah masuk ke dalamnya dan kami akan mendapatkan waktu yang salah. Jadi, kami mengukur data:
INSERT IGNORE INTO `history` SELECT * FROM history_old WHERE clock>=1551045645;
Setelah akhir operasi ini, kita memiliki di tabel
history
baru yang dipartisi semua data yang lama, ditambah data yang datang setelah tabel diganti nama. Tabel
history_old
tidak lagi diperlukan. Anda dapat segera menghapusnya, atau membuat salinan cadangannya (jika Anda menderita paranoia) sebelum menghapusnya.
Seluruh proses yang dijelaskan di atas perlu diulang untuk
history_uint
history_str
,
history_text
dan
history_uint
.
Apa yang perlu diperbaiki dalam pengaturan Server Zabbix
Sekarang pemeliharaan database tentang sejarah data berada di pundak kami. Ini berarti bahwa Zabbix tidak boleh lagi menghapus data lama - kami akan melakukannya sendiri. Agar Server Zabbix tidak mencoba untuk membersihkan data itu sendiri, Anda harus pergi ke antarmuka web Zabbix, pilih "Administrasi" di menu, lalu submenu "Umum", lalu pilih "Hapus riwayat" pada daftar drop-down di sebelah kanan. Pada halaman yang muncul, hapus centang semua kotak centang untuk grup "Riwayat" dan klik tombol "Perbarui". Ini akan mencegah meja-meja
history*
tidak dibersihkan oleh kami melalui pembantu rumah tangga.
Perhatikan halaman yang sama dengan grup “Dinamika perubahan”. Ini hanya tabel
trends
, yang kami janjikan untuk kembali. Jika itu juga menjadi terlalu besar untuk Anda dan perlu dipartisi, hapus centang grup ini juga dan kemudian proses tabel ini persis seperti yang dilakukan untuk tabel
history*
.
Pemeliharaan basis data lebih lanjut
Seperti yang telah ditulis sebelumnya, untuk operasi normal pada tabel dipartisi, perlu membuat partisi dalam waktu. Anda dapat melakukan ini seperti ini:
ALTER TABLE `history` ADD PARTITION (PARTITION p20190307 VALUES LESS THAN (UNIX_TIMESTAMP("2019-03-07 00:00:00")));
Selain itu, karena kami membuat tabel dipartisi dan melarang Zabbix Server untuk membersihkannya, menghapus data lama sekarang menjadi perhatian kami. Untungnya, tidak ada masalah sama sekali. Ini dilakukan hanya dengan menghapus partisi yang datanya tidak lagi kita perlukan.
Sebagai contoh:
ALTER TABLE history DROP PARTITION p20190201;
Tidak seperti pernyataan DELETE FROM dengan rentang tanggal, DROP PARTITION dilakukan dalam beberapa detik, tidak memuat server sama sekali dan berfungsi dengan lancar saat menggunakan replikasi di MySQL.
Kesimpulan
Solusi yang dijelaskan sudah teruji oleh waktu. Volume data tumbuh, tetapi tidak ada penurunan kinerja yang nyata.