Zabbix Veritabanı Bakımı ve Partition Yönetimi
Zabbix kurulumunun ilk aylarında her şey güzeldir. Dashboard’lar canlı, grafikler akıyor, alert’ler geliyor. Ama 6-12 ay sonra fark ediyorsunuz: veritabanı şişmiş, sorgular yavaşlamış, hatta Zabbix server bazen “database too slow” uyarıları vermeye başlamış. İşte bu noktada partition yönetimi ve düzenli bakım rutinleri hayat kurtarıcı oluyor. Bu yazıda üretim ortamında öğrendiğim dersleri, yaptığım hataları ve çözümlerini aktaracağım.
Neden Zabbix Veritabanı Şişer?
Zabbix, metrik verilerini birkaç ana tabloda tutar. Bu tablolar zamanla inanılmaz büyüklüklere ulaşabilir:
- history: Sayısal olmayan geçmiş veriler
- history_uint: İşaretsiz tamsayı geçmiş verileri (en büyük tablo genellikle bu olur)
- history_str: Karakter dizisi geçmişi
- history_text: Uzun metin geçmişi
- history_log: Log geçmişi
- trends: Saatlik trend özet verileri
- trends_uint: Unsigned integer trend verileri
- events: Tetiklenen olaylar
500 host, ortalama 50 item per host ve 30 günlük retention ayarıyla sadece history_uint tablosu kolayca 50-100 GB’a ulaşabilir. Bunu hiç partition yapmadan tek tablo olarak çalıştırmayı düşündüğünüzde, her housekeeper çalışmasının ne kadar maliyetli olduğu anlaşılır.
Zabbix Housekeeper’ın Sınırları
Zabbix’in yerleşik housekeeper mekanizması, eski verileri DELETE sorgusuyla siler. Küçük ortamlarda bu yeterlidir. Ancak büyük tablolarda DELETE işlemi hem yavaştır hem de tablo fragmentasyonuna yol açar. Diskte yer boşalmaz, sadece “kullanılabilir” alan işaretlenir.
Bunu görmek için şu sorguyu çalıştırın:
SELECT
table_name,
ROUND(data_length / 1024 / 1024 / 1024, 2) AS data_gb,
ROUND(data_free / 1024 / 1024 / 1024, 2) AS free_gb,
ROUND((data_free / (data_length + data_free)) * 100, 1) AS fragmentation_pct
FROM information_schema.tables
WHERE table_schema = 'zabbix'
AND table_name IN ('history', 'history_uint', 'trends', 'trends_uint')
ORDER BY data_length DESC;
Eğer free_gb sütununda büyük değerler görüyorsanız, housekeeper çalışmış ama disk alanı geri alınamamış demektir. Bu durumda OPTIMIZE TABLE çalıştırabilirsiniz ama production’da bunu yapmak tabloyu kilitleyeceği için tehlikelidir. Partition yapısına geçmek çok daha sağlıklıdır.
MySQL/MariaDB için Partition Kurulumu
Zabbix community’sinde ünlü olan partition scripti birçok ortamda test edilmiş durumda. Ama ben sıfırdan kendi ihtiyaçlarıma göre uyarladığım versiyonu kullanıyorum. Önce mevcut tablonun durumunu kontrol edelim:
-- Mevcut history_uint boyutu ve satır sayısı
SELECT
table_name,
table_rows,
ROUND(data_length / 1024 / 1024 / 1024, 2) AS size_gb
FROM information_schema.tables
WHERE table_schema = 'zabbix'
AND table_name = 'history_uint';
-- Mevcut partition durumu
SELECT
partition_name,
partition_description,
table_rows
FROM information_schema.partitions
WHERE table_schema = 'zabbix'
AND table_name = 'history_uint'
ORDER BY partition_ordinal_position;
Eğer partition yoksa partition_name sütunu NULL gelecektir. Şimdi partition’a geçiş yapalım. Dikkat: Bu işlem büyük tablolarda uzun sürebilir, maintenance window açın.
-- history_uint tablosunu günlük partition'a çevirme
-- Önce clock sütununun index durumunu kontrol edin
SHOW INDEX FROM zabbix.history_uint;
-- Partition oluşturma (son 3 gün için örnek, gerçekte daha fazla ekleyin)
ALTER TABLE history_uint PARTITION BY RANGE (clock) (
PARTITION p2024_01_01 VALUES LESS THAN (UNIX_TIMESTAMP('2024-01-02 00:00:00')),
PARTITION p2024_01_02 VALUES LESS THAN (UNIX_TIMESTAMP('2024-01-03 00:00:00')),
PARTITION p2024_01_03 VALUES LESS THAN (UNIX_TIMESTAMP('2024-01-04 00:00:00')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
Otomatik Partition Yönetim Scripti
Manuel partition ekleme yapmak sürdürülebilir değil. Aşağıdaki stored procedure hem yeni partition’lar ekler hem de eskilerini temizler:
DELIMITER $$
DROP PROCEDURE IF EXISTS manage_partitions$$
CREATE PROCEDURE manage_partitions(
IN schema_name VARCHAR(64),
IN table_name VARCHAR(64),
IN keep_days INT,
IN future_days INT
)
BEGIN
DECLARE partition_name VARCHAR(64);
DECLARE partition_ts BIGINT;
DECLARE current_ts BIGINT;
DECLARE cutoff_ts BIGINT;
DECLARE max_future_ts BIGINT;
DECLARE done INT DEFAULT 0;
SET current_ts = UNIX_TIMESTAMP(CURDATE());
SET cutoff_ts = current_ts - (keep_days * 86400);
SET max_future_ts = current_ts + (future_days * 86400);
-- Gelecek partition'ları ekle
SET @day_counter = 0;
WHILE @day_counter <= future_days DO
SET @target_date = DATE_ADD(CURDATE(), INTERVAL @day_counter DAY);
SET @p_name = CONCAT('p', DATE_FORMAT(@target_date, '%Y_%m_%d'));
SET @p_ts = UNIX_TIMESTAMP(DATE_ADD(@target_date, INTERVAL 1 DAY));
-- Partition zaten var mı kontrol et
SET @exists = 0;
SELECT COUNT(*) INTO @exists
FROM information_schema.partitions
WHERE TABLE_SCHEMA = schema_name
AND TABLE_NAME = table_name
AND PARTITION_NAME = @p_name;
IF @exists = 0 THEN
SET @sql = CONCAT(
'ALTER TABLE ', schema_name, '.', table_name,
' REORGANIZE PARTITION p_future INTO (',
'PARTITION ', @p_name, ' VALUES LESS THAN (', @p_ts, '),',
'PARTITION p_future VALUES LESS THAN MAXVALUE)'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
SET @day_counter = @day_counter + 1;
END WHILE;
-- Eski partition'ları sil
DROP TABLE IF EXISTS tmp_old_partitions;
CREATE TEMPORARY TABLE tmp_old_partitions AS
SELECT partition_name, CAST(partition_description AS UNSIGNED) AS p_ts
FROM information_schema.partitions
WHERE TABLE_SCHEMA = schema_name
AND TABLE_NAME = table_name
AND partition_name != 'p_future'
AND CAST(partition_description AS UNSIGNED) <= cutoff_ts;
-- Her eski partition'ı DROP et
BEGIN
DECLARE cur CURSOR FOR SELECT partition_name FROM tmp_old_partitions;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
read_loop: LOOP
FETCH cur INTO partition_name;
IF done THEN LEAVE read_loop; END IF;
SET @drop_sql = CONCAT('ALTER TABLE ', schema_name, '.', table_name,
' DROP PARTITION ', partition_name);
PREPARE drop_stmt FROM @drop_sql;
EXECUTE drop_stmt;
DEALLOCATE PREPARE drop_stmt;
END LOOP;
CLOSE cur;
END;
DROP TEMPORARY TABLE IF EXISTS tmp_old_partitions;
END$$
DELIMITER ;
Bu procedure’ü çalıştırmak için:
-- history tablolarını 30 gün tut, 7 gün ileriye partition ekle
CALL manage_partitions('zabbix', 'history_uint', 30, 7);
CALL manage_partitions('zabbix', 'history', 30, 7);
CALL manage_partitions('zabbix', 'history_str', 30, 7);
CALL manage_partitions('zabbix', 'history_text', 30, 7);
CALL manage_partitions('zabbix', 'history_log', 30, 7);
-- Trend tablolarını 365 gün tut, 30 gün ileriye partition ekle
CALL manage_partitions('zabbix', 'trends', 365, 30);
CALL manage_partitions('zabbix', 'trends_uint', 365, 30);
Zabbix Housekeeper’ı Devre Dışı Bırakma
Partition kullandığınızda Zabbix’in kendi housekeeper’ını devre dışı bırakmanız gerekir, aksi halde çakışma yaşanır. zabbix_server.conf dosyasında:
# /etc/zabbix/zabbix_server.conf
# Housekeeper'ı tamamen kapatmak yerine sadece history/trend temizliğini kapatın
HousekeepingFrequency=1
# Administration > General > Housekeeping ekranından:
# "Enable internal housekeeping" for History ve Trends seçeneklerini KAPAT
# Events, Alerts, Audit için açık bırakabilirsiniz
Bunu UI üzerinden yapmak için Zabbix arayüzüne gidin: Administration > General > Housekeeping. “History and trends” bölümündeki “Enable internal housekeeping” kutusunu kaldırın. Bu ayar partition tabanlı temizlik yapacağınız tablolar için kritiktir.
Cron ile Otomatik Bakım
Procedure’ü cron’a bağlayalım. Her gece çalışacak bir bash scripti:
#!/bin/bash
# /usr/local/sbin/zabbix_partition_maintenance.sh
DB_HOST="localhost"
DB_USER="zabbix"
DB_PASS="your_password_here"
DB_NAME="zabbix"
LOG_FILE="/var/log/zabbix/partition_maintenance.log"
HISTORY_DAYS=30
TRENDS_DAYS=365
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Partition bakimi basliyor..."
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" <<EOF 2>> "$LOG_FILE"
CALL manage_partitions('zabbix', 'history_uint', ${HISTORY_DAYS}, 7);
CALL manage_partitions('zabbix', 'history', ${HISTORY_DAYS}, 7);
CALL manage_partitions('zabbix', 'history_str', ${HISTORY_DAYS}, 7);
CALL manage_partitions('zabbix', 'history_text', ${HISTORY_DAYS}, 7);
CALL manage_partitions('zabbix', 'history_log', ${HISTORY_DAYS}, 7);
CALL manage_partitions('zabbix', 'trends', ${TRENDS_DAYS}, 30);
CALL manage_partitions('zabbix', 'trends_uint', ${TRENDS_DAYS}, 30);
EOF
if [ $? -eq 0 ]; then
log "Partition bakimi basariyla tamamlandi."
else
log "HATA: Partition bakiminda sorun olustu!"
# Alarm gonderebilirsiniz buradan
exit 1
fi
# Disk kullanim raporunu logla
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "
SELECT table_name,
ROUND(data_length/1024/1024/1024, 2) AS data_gb,
ROUND(index_length/1024/1024/1024, 2) AS index_gb
FROM information_schema.tables
WHERE table_schema = 'zabbix'
AND table_name IN ('history_uint','history','trends','trends_uint')
ORDER BY data_length DESC;" 2>> "$LOG_FILE" | tee -a "$LOG_FILE"
log "Bakim tamamlandi."
Crontab’a ekleyin:
chmod +x /usr/local/sbin/zabbix_partition_maintenance.sh
# Crontab'a ekle: her gece 02:00'de calistir
echo "0 2 * * * root /usr/local/sbin/zabbix_partition_maintenance.sh" > /etc/cron.d/zabbix-partition
# Log rotation da ekleyelim
cat > /etc/logrotate.d/zabbix-partition << 'EOF'
/var/log/zabbix/partition_maintenance.log {
weekly
rotate 8
compress
missingok
notifempty
}
EOF
TimescaleDB Alternatifi (PostgreSQL Ortamları için)
Eğer Zabbix’i PostgreSQL üzerinde çalıştırıyorsanız ve Zabbix 5.2 veya üzeri kullanıyorsanız, TimescaleDB ciddi bir alternatif. Zabbix artık native TimescaleDB desteği sunuyor ve kurulumu görece basit:
# TimescaleDB extension'ı yükle (Ubuntu/Debian)
apt install timescaledb-postgresql-14
# postgresql.conf'a ekle
echo "shared_preload_libraries = 'timescaledb'" >> /etc/postgresql/14/main/postgresql.conf
# Servisi yeniden başlat
systemctl restart postgresql
# Zabbix veritabanında extension'ı aktifleştir
psql -U zabbix -d zabbix -c "CREATE EXTENSION IF NOT EXISTS timescaledb;"
# Zabbix'in sağladığı migration scriptini çalıştır
# Bu script history tablolarını hypertable'a dönüştürür
psql -U zabbix -d zabbix -f /usr/share/doc/zabbix-server-pgsql/timescaledb.sql
TimescaleDB’nin avantajı, partition yönetimini sizin yerinize yapması ve compression özelliğiyle eski verileri otomatik sıkıştırması. 90 günden eski history verilerini compress etmek için:
-- Compression politikası ekle
SELECT add_compression_policy('history_uint',
INTERVAL '90 days');
-- Retention politikası (silme)
SELECT add_retention_policy('history_uint',
INTERVAL '1 year');
Performans İzleme ve Sağlık Kontrolleri
Partition yönetimini kurduktan sonra düzenli sağlık kontrolleri yapmanız gerekiyor. Şu sorgu mevcut partition durumunu özetler:
-- Partition sağlık raporu
SELECT
table_name,
COUNT(*) AS partition_count,
MIN(CAST(partition_description AS UNSIGNED)) AS oldest_partition_ts,
FROM_UNIXTIME(MIN(CAST(partition_description AS UNSIGNED))) AS oldest_date,
MAX(CASE WHEN partition_name != 'p_future'
THEN CAST(partition_description AS UNSIGNED) END) AS newest_ts,
FROM_UNIXTIME(MAX(CASE WHEN partition_name != 'p_future'
THEN CAST(partition_description AS UNSIGNED) END)) AS newest_date,
SUM(table_rows) AS total_rows
FROM information_schema.partitions
WHERE table_schema = 'zabbix'
AND table_name IN ('history_uint', 'history', 'trends', 'trends_uint')
AND partition_name IS NOT NULL
GROUP BY table_name
ORDER BY table_name;
Zabbix’in kendi veritabanı yavaşlıklarını izlemek için “Zabbix server health” template’indeki şu metriklere dikkat edin:
- zabbix[wcache,history,pfree]: History write cache doluluk oranı. %20’nin altına düştüğünde alarm verin.
- zabbix[db,history_cache,pfree]: Veritabanına yazma gecikmeleri
- zabbix[process,housekeeper,avg,busy]: Housekeeper iş yükü, partition sonrası bu sıfıra yaklaşmalı
Büyük Tablolarda Veri Sıkıştırma (InnoDB)
MariaDB 10.3+ ile gelen InnoDB compression, partition’larla birlikte kullanılabilir. Tablo seviyesinde aktifleştirmek için:
-- Mevcut tabloyu compressed format ile yeniden oluştur
-- DİKKAT: Bu işlem tabloyu kilitler, maintenance window gerekli
ALTER TABLE history_uint
ROW_FORMAT=COMPRESSED
KEY_BLOCK_SIZE=8;
-- Compression ratio'yu kontrol et
SELECT
table_name,
row_format,
ROUND(data_length/1024/1024/1024, 2) AS data_gb
FROM information_schema.tables
WHERE table_schema = 'zabbix'
AND table_name = 'history_uint';
Gerçek hayatta gördüğüm oranlara göre history_uint tablosunda %40-60 arasında sıkıştırma sağlamak mümkün. 100 GB’lık bir tablonun 45 GB’a inmesi disk ve I/O maliyeti açısından ciddi fark yaratıyor.
Gerçek Hayattan Senaryo: Kriz Anında Kurtarma
Geçen yıl bir müşteri ortamında şu durumla karşılaştım: Zabbix server tamamen durmuş, log’da sürekli “Query timeout” hataları. history_uint tablosu 280 GB olmuş, partition yok, housekeeper 4 saattir bir DELETE işlemi dönüyor.
Hızlı çözüm adımları şunlar oldu:
# 1. Housekeeper'ı durdur
# zabbix_server.conf'ta StartHousekeepers=0 yap, servisi restart et
# 2. Hangi tarihten önce veri silinebilir hesapla
# 30 günlük retention, bugün 2024-01-15 ise 2023-12-16 öncesi silinebilir
python3 -c "import time; print(int(time.mktime(time.strptime('2023-12-16', '%Y-%m-%d'))))"
# Çıktı: 1702684800
# 3. MySQL'de small batch'ler halinde sil (tabloyu kilitlemeden)
# Bu bir procedure ile yapılabilir ama acil durumda elle de çalıştırılabilir
-- Acil durum için batch silme (her çalışmada 100K satır siler)
SET @cutoff = 1702684800;
SET @batch_size = 100000;
REPEAT
DELETE FROM history_uint
WHERE clock < @cutoff
LIMIT @batch_size;
SELECT SLEEP(0.1);
UNTIL ROW_COUNT() = 0 END REPEAT;
Bu script saatlerce çalıştı ama her iteration 0.1 saniye beklediği için diğer sorgulara nefes aldırdı. Zabbix tekrar ayağa kalktı. Ardından düzgün partition yapısını kurduğumuzda tablo boyutu 280 GB’dan 35 GB’a indi, çünkü artık boş alanlar da geri alındı.
InnoDB Buffer Pool ve Sorgu Optimizasyonu
Partition tek başına yeterli değil. MySQL/MariaDB’nin buffer pool’u doğru ayarlanmazsa partition faydası sınırlı kalır.
# /etc/mysql/conf.d/zabbix-tuning.cnf
[mysqld]
# Toplam RAM'in %70-75'i kadar ayarla
innodb_buffer_pool_size = 8G
innodb_buffer_pool_instances = 8
# History tabloları için kritik
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
# Partition ile birlikte çalışan sorgu optimizasyonu
optimizer_switch = 'index_merge_intersection=off'
# Binary log'u sadece replikasyon varsa açık bırakın
# Replikasyon yoksa kapamak I/O yükünü azaltır
# log_bin = OFF (dikkatli karar verin)
# Slow query log - hangi sorgular yavaş görmek için
slow_query_log = 1
slow_query_log_file = /var/log/mysql/zabbix-slow.log
long_query_time = 2
Sonuç
Zabbix veritabanı bakımı, kurulum günü değil aylar sonra göz ardı edilemeyecek bir konu haline geliyor. Deneyimlerime göre şu üç şeyi en baştan yapmak sonradan yaşanacak kriz senaryolarını büyük ölçüde önlüyor:
- Partition yapısını ilk kurulumda kurun. Sonradan geçmek mümkün ama büyük tablolarda acı verici bir süreç.
- Housekeeper yerine partition drop kullanın. DELETE maliyeti ile karşılaştırıldığında DROP PARTITION neredeyse anlık çalışır.
- Write cache ve buffer pool metriklerini izleyin. Zabbix’i izlerken Zabbix’in kendisini izlemeyi unutmayın; bu bir öz izleme döngüsü ama değer.
TimescaleDB yolunu seçenler için bu iş çok daha az elle müdahale gerektiriyor, özellikle Zabbix 6.x ile gelen native entegrasyon olgunlaştıkça PostgreSQL + TimescaleDB kombinasyonu büyük ortamlarda ciddi bir opsiyon haline geldi. Ama MariaDB’de kalmaya devam edenlerin de yukarıdaki partition prosedürü ile rahat bir çalışma ortamı kurmaları mümkün. Tek şart: bu bakımı bir kenara atmadan düzenli yapmak.
