Çoğu ekip veritabanı izlemeyi CPU ve RAM grafiğine bakmak sanır. Oysa veritabanı sunucusu çöktüğünde CPU genellikle %30'dadır; asıl sıkışma bağlantı havuzunda, disk gecikmesinde ya da buffer pool'un yetmemesinde yaşanır. Veritabanı izleme, işletim sistemi metriklerinin bir katman altına inip motorun kendi sayaçlarını okumak demektir ve bu sayaçların hangisinin gerçekten anlamlı olduğunu bilmek, panoyu grafik çöplüğüne çevirmemenin tek yolu.
Bu rehberde MySQL ve MariaDB üzerinden gidip PostgreSQL karşılıklarına da değinerek şu soruları cevaplayacağım: hangi metrikler gerçekten sorun habercisidir, her biri hangi komutla okunur, makul alarm eşikleri nedir ve bir metrik alarm verdiğinde ilk olarak nereye bakılır. Sonunda da izleme kurarken en sık yapılan hataları toplayacağım — özellikle "yüzde 99 hit oranı" gibi insanı rahatlatan ama aslında hiçbir şey söylemeyen metriklerin tuzağını.
CPU Yüzdesi Neden Tek Başına Bir Şey Söylemez#
Bir veritabanı sunucusunun işi ağırlıklı olarak beklemektir: diskten sayfa gelmesini, kilidin açılmasını, ağdan paketin ulaşmasını bekler. Bu yüzden yük altında bile CPU düşük görünebilirken kullanıcı tarafında sayfalar 8 saniyede açılıyor olabilir. Tersi de geçerlidir: %95 CPU gördüğünüz bir sunucu, aslında sağlıklı biçimde tam kapasite çalışıyor da olabilir.
Doğru yaklaşım, üç katmanı ayrı ayrı izlemektir. İş yükü katmanı (saniyedeki sorgu, bağlantı sayısı) size talebin ne olduğunu söyler. Doygunluk katmanı (bekleyen bağlantı, kilit bekleme, disk kuyruğu) sınırın nerede olduğunu gösterir. Sonuç katmanı (yanıt süresi, hata oranı) kullanıcının ne hissettiğini söyler. Alarm kurarken sonuç katmanını, teşhis yaparken doygunluk katmanını kullanın. Aşağıdaki tablo bu ayrımı somutlaştırır:
| Katman | Örnek metrik | Ne işe yarar | Alarm kurulur mu |
|---|---|---|---|
| İş yükü | Queries/sn, Connections/sn | Trafiği anlamak | Hayır, sadece bağlam |
| Doygunluk | Threads_running, kilit bekleme, disk await | Darboğazı bulmak | Sınırlı |
| Sonuç | P95 sorgu süresi, hata oranı, replika gecikmesi | Kullanıcı etkisini görmek | Evet |
Bağlantı Metrikleri#
Veritabanı kaynaklı kesintilerin belki yarısı bağlantı tükenmesinden çıkar. Bakılacak üç sayaç var: Threads_connected (açık bağlantı sayısı), Threads_running (o an gerçekten iş yapan bağlantı) ve Max_used_connections (sunucu açıldığından beri görülen tepe değer). Bunlardan asıl önemlisi Threads_running'dir; bağlantı sayısı 400 olabilir ama bunların 395'i uyuyorsa sorun yoktur.
-- Anlık bağlantı tablosu
SHOW GLOBAL STATUS WHERE Variable_name IN (
'Threads_connected','Threads_running','Aborted_connects',
'Max_used_connections','Connection_errors_max_connections');
-- Yapılandırılmış üst sınır
SHOW VARIABLES LIKE 'max_connections';
-- O an ne yapılıyor
SELECT id, user, host, db, command, time, state, LEFT(info, 80) AS sorgu
FROM information_schema.processlist
WHERE command <> 'Sleep' ORDER BY time DESC LIMIT 20;
Threads_running sürekli olarak CPU çekirdek sayısının 2-3 katının üstündeyse sunucu kuyruğa girmiş demektir; yanıt süreleri o noktadan sonra doğrusal değil üstel artar. Aborted_connects artıyorsa ya yanlış parola denemesi vardır ya da uygulama havuzu bağlantıyı yarıda kesiyordur. Connection_errors_max_connections sıfırdan büyükse istemciler kapıdan geri çevriliyor demektir; bu tam olarak MySQL Too Many Connections hatası tablosudur ve çözümü genelde max_connections değerini artırmak değil, uygulamada bağlantı havuzu kullanmaktır.
PostgreSQL tarafında aynı bilgi pg_stat_activity üzerinden okunur:
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
SELECT count(*) AS bekleyen FROM pg_stat_activity WHERE wait_event_type = 'Lock';
Bellek: InnoDB Buffer Pool Hit Oranı#
InnoDB, veriyi ve indeksi diskten okuyup buffer pool adı verilen bellek alanında tutar. Bir sorgu ihtiyaç duyduğu sayfayı bellekte bulursa mikrosaniyeler, bulamayıp diskten okursa milisaniyeler harcar; aradaki fark bin kattır. Bu yüzden buffer pool hit oranı en çok bakılan metriklerden biridir:
SELECT
ROUND(100 - (
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_reads')
/ (SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_read_requests') * 100
), 4) AS hit_orani_yuzde;
Burada bir tuzak var ve çok kişi buna düşer: hit oranı kümülatiftir, yani sunucu açıldığından beri toplamı gösterir. Aylardır açık bir sunucuda oran her zaman %99.9 çıkar; son 10 dakikada disk okuması patlamış olsa bile. Anlamlı olan türevdir: Innodb_buffer_pool_reads sayacının dakikadaki artışı. İzleme sisteminizde bu sayacı kümülatif değil, oran (rate) olarak çizin.
İkinci önemli gösterge Innodb_buffer_pool_pages_free değeridir. Uzun süredir sıfıra yapışıksa ve aynı anda disk okuması artıyorsa, innodb_buffer_pool_size yetmiyordur. Genel kural, aktif veri kümesinin (sık okunan tablolar + indeksleri) buffer pool'a sığmasıdır; boyutlandırma mantığını veritabanı sunucusu boyutlandırma yazısında ayrıntılı ele aldım.
Sorgu Metrikleri: Yavaşlık Nerede Doğuyor#
Bir veritabanı sunucusunun performansı, en yavaş sorgusunun tekrar sayısına eşittir. Bu yüzden ortalama değil yüzdelik dilim izleyin: ortalama 8 ms görünen bir sistemde P99 3 saniye olabilir ve şikayet eden kullanıcılar tam olarak o dilimdedir. Yavaş sorgu günlüğünü açık tutmak neredeyse ücretsizdir:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 0
log_queries_not_using_indexes ayarını üretimde kapalı tutun; küçük tablolarda tam tarama normaldir ve bu ayar günlüğü gürültüyle doldurur. Toplanan günlüğü ham okumak yerine özetleyin; pt-query-digest toplam süreye göre sıralar, yani "tek başına 4 saniye süren sorgu" yerine "12 ms ama günde 900 bin kez çalışan sorgu"yu da yakalar:
# En çok toplam süre harcayan sorguları çıkar
pt-query-digest /var/log/mysql/slow.log | head -60
Günlük dosyasına hiç dokunmadan aynı bilgiyi almanın yolu performance_schema özet tablolarıdır:
SELECT digest_text, count_star AS calisma,
ROUND(sum_timer_wait/1000000000000, 2) AS toplam_saniye,
ROUND(avg_timer_wait/1000000000, 2) AS ort_ms,
sum_rows_examined AS incelenen_satir
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC LIMIT 15;
incelenen_satir sütununun döndürülen satıra oranı, indeks eksikliğinin en net göstergesidir: 3 satır döndürmek için 400 bin satır inceleniyorsa orada bir indeks eksiktir. Yöntemin tamamı için MySQL yavaş sorgu bulma yazısına bakın.
Disk, I/O ve Kilitler#
Veritabanının en sert sınırı diskin gecikmesidir. NVMe üzerinde 0.2 ms olan bir okuma, ağ üzerinden bağlı yavaş bir depolamada 12 ms'ye çıkar ve aynı sorgu 60 kat yavaşlar. İşletim sistemi tarafında izlenecek metrik IOPS değil await (ortalama bekleme süresi) ve kuyruk derinliğidir:
# 5 saniyede bir, cihaz bazında gecikme ve kuyruk
iostat -x 5 3
# Hangi süreç ne kadar yazıyor
pidstat -d 5 3
%util sütunu %100'e yapışıp await iki haneli milisaniyelere çıkıyorsa, disk doygundur ve hiçbir sorgu optimizasyonu bunu kurtarmaz. Veritabanı tarafında bunun karşılığı Innodb_row_lock_waits ve Innodb_row_lock_time_avg sayaçlarıdır; artıyorlarsa işlemler birbirini bekliyordur:
SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
-- Kim kimi bekletiyor (MySQL 8)
SELECT * FROM performance_schema.data_locks LIMIT 20;
Bir diğer sessiz katil geçici tablolardır. Created_tmp_disk_tables sayacı hızla artıyorsa, sıralama ve gruplama işlemleri belleğe sığmayıp diske taşıyor demektir; çözüm genelde tmp_table_size/max_heap_table_size ayarı değil, ORDER BY kolonuna uygun indeks eklemektir.
Replikasyon Gecikmesi ve Yedek Yaşı#
Replika kullanıyorsanız izlemeniz gereken tek en kritik metrik gecikmedir. Okuma trafiğini replikaya yönlendiren bir uygulamada 40 saniyelik gecikme, kullanıcının az önce kaydettiği siparişi listede görememesi demektir:
-- MySQL 8 / MariaDB 10.5+
SHOW REPLICA STATUS\G
-- Bakılacak alan: Seconds_Behind_Source (MariaDB'de Seconds_Behind_Master)
Bu alanın NULL dönmesi "gecikme yok" değil, replikasyonun durduğu anlamına gelir; alarmınızı sadece "değer > 30" diye kurarsanız durmuş bir replikayı asla fark etmezsiniz. Alarmı NULL veya > eşik biçiminde kurun. Kurulum ayrıntıları için MySQL replikasyon kurulumu yazısına bakabilirsiniz.
İkinci ve en çok atlanan metrik yedek yaşıdır: son başarılı yedeğin üzerinden kaç saat geçti? Bunu izlemeyen ekipler, yedek görevinin üç hafta önce sessizce kırıldığını felaket günü öğrenir. Basit bir kontrol yeterlidir:
# Son yedek dosyasının yaşı (saat)
SON=$(ls -1t /yedek/*.sql.gz | head -1)
echo $(( ( $(date +%s) - $(stat -c %Y "$SON") ) / 3600 )) saat
Alarm Eşikleri ve Sade Bir Pano#
Panoya her metriği koymak izleme değil dekorasyondur. Aşağıdaki tablo, çoğu orta ölçekli kurulumda işe yarayan başlangıç eşikleridir; kendi taban çizginizi bir hafta ölçtükten sonra ayarlayın:
| Metrik | Uyarı | Kritik | İlk bakılacak yer |
|---|---|---|---|
| Threads_running | > 2× çekirdek | > 4× çekirdek | processlist, kilitler |
| Bağlantı doluluk oranı | %70 | %85 | Uygulama bağlantı havuzu |
| Buffer pool disk okuma (rate) | Taban çizginin 3 katı | 10 katı | buffer_pool_size, sorgu planı |
| P95 sorgu süresi | 300 ms | 1 sn | performance_schema özetleri |
| Replika gecikmesi | 30 sn veya NULL | 120 sn veya NULL | SHOW REPLICA STATUS |
| Disk await | 10 ms | 25 ms | iostat, depolama katmanı |
| Veri diski doluluk | %75 | %85 | binlog, tmp, eski tablolar |
| Son yedek yaşı | 26 saat | 48 saat | Yedek görevi günlüğü |
Disk doluluğu eşiğini düşük tutmanın sebebi şudur: veritabanı diski dolduğunda yazma reddedilir, servis durur ve dolu diskte kurtarma yapmak çok daha zordur. Binlog'un diski doldurma senaryosu için MySQL binlog diski doldurdu yazısına göz atın.
Sık Yapılan Hatalar#
Kümülatif sayaçları oran olarak çizmemek. Innodb_buffer_pool_reads, Questions, Aborted_connects gibi sayaçlar hep artar. Ham değerini çizerseniz sürekli yükselen anlamsız bir çizgi elde edersiniz. Her birini "saniyedeki artış" olarak hesaplayın.
Ortalamaya bakmak. Ortalama sorgu süresi neredeyse her zaman iyi görünür; kullanıcı şikayeti P95 ve P99'da saklıdır. Panoda ortalamayı tutabilirsiniz ama alarmı yüzdelik dilime kurun.
Alarm eşiğini rastgele seçmek. "CPU %80" gibi ödünç alınmış eşikler yanlış alarm üretir, ekip alarmları susturmayı öğrenir ve gerçek olay geldiğinde kimse bakmaz. Bir hafta ölçüp taban çizginizi belirledikten sonra eşiği ona göre koyun.
Sadece veritabanını izlemek. Sorunun kaynağı çoğu zaman uygulama tarafındadır: havuz yapılandırması, N+1 sorgular, kapatılmayan bağlantılar. Uygulamanın veritabanına ne sorduğunu görmüyorsanız yarım izliyorsunuz demektir.
İzleme sorgularını ağır yazmak. Her 10 saniyede SHOW FULL PROCESSLIST ya da geniş information_schema taraması çalıştırmak yüklü bir sunucuda ölçtüğünüz şeyi bozar. Özet tabloları tercih edin ve izleme kullanıcısına yalnızca PROCESS, REPLICATION CLIENT, SELECT yetkilerini verin.
Sıkça Sorulan Sorular#
Buffer pool hit oranı yüzde kaç olmalı#
Pratikte %99 üzeri beklenir ama bu sayı tek başına aldatıcıdır çünkü sunucunun açık olduğu tüm süreyi kapsar. Anlamlı olan, saniyedeki fiziksel disk okuma sayısının zaman içindeki değişimidir. Oran %99.8 iken bile disk okuması on kat artmışsa sisteminiz sıkışıyordur; alarmı orana değil okuma hızının taban çizgisine kurun.
MySQL'de yavaş sorgu günlüğü performansı düşürür mü#
long_query_time makul bir değerdeyken (örneğin 1 saniye) etkisi ihmal edilebilir düzeydedir, çünkü yalnızca eşiği aşan sorgular diske yazılır. Asıl maliyet long_query_time = 0 yapıp her sorguyu kaydetmek ya da log_queries_not_using_indexes seçeneğini üretimde açık bırakmaktır; ikisi de günlüğü şişirir ve disk yazma yükünü artırır.
Replika gecikmesini nasıl kontrol ederim#
MySQL 8'de SHOW REPLICA STATUS çıktısındaki Seconds_Behind_Source, MariaDB'de Seconds_Behind_Master alanına bakılır. Bu alanın NULL olması gecikmenin sıfır olduğu anlamına gelmez, replikasyonun durduğunu gösterir. Bu yüzden alarm koşulunuz mutlaka "NULL veya eşikten büyük" biçiminde olmalı, yoksa durmuş bir replika sessizce fark edilmez.
Hangi izleme aracını kullanmalıyım#
Araç seçimi metrik seçiminden daha az önemlidir. Prometheus artı bir exporter, Zabbix ya da barındırma panelinizin kendi grafiklerinden hangisi elinizin altındaysa onunla başlayın. Önemli olan, bu yazıdaki sekiz on metriği toplayıp eşik koymanız ve alarmın gerçekten birine ulaşmasıdır. Kimsenin bakmadığı zengin bir pano, üç metrikli çalışan bir alarmdan daha değersizdir.
Veritabanı diskinin dolmak üzere olduğunu nasıl anlarım#
Sadece boş alan yüzdesine değil, doluluk eğimine bakın: son yedi günün artış hızından kalan gün sayısını hesaplayın. Ayrıca binlog, geçici dosyalar ve eski tablo alanlarını ayrı ayrı ölçün, çünkü hızlı büyüyen genellikle veri değil bunlardan biridir. Alarmı %85'te değil %75'te kurun; dolu diskte müdahale etmek çok daha zordur.
PostgreSQL'de karşılık gelen metrikler hangileri#
Bağlantılar için pg_stat_activity, önbellek isabeti için pg_stat_database içindeki blks_hit ve blks_read, sorgu özetleri için pg_stat_statements eklentisi, replikasyon gecikmesi için pg_stat_replication görünümündeki gönderme ve uygulama LSN farkı kullanılır. Mantık aynıdır: kümülatif sayaçları orana çevirin ve sonuç metriklerine alarm kurun.
Kapanış#
İzlemede işe yarayan yaklaşım sade: sonuç metriklerine (yanıt süresi, replika gecikmesi, hata oranı, yedek yaşı) alarm kurun, doygunluk metriklerini (çalışan iş parçacığı, disk gecikmesi, kilit bekleme) teşhis için tutun, kümülatif sayaçları oran olarak çizin ve eşikleri kendi taban çizginizden türetin. Bu dört alışkanlık, panosu on kat büyük ama kör olan kurulumlardan sizi ayırır.
İzleme ve müdahaleyi kendiniz kurmak yerine devretmek isterseniz sunucu yönetimi hizmetimiz metrik toplama, alarm ve olay müdahalesini üstlenir. Daha fazla belleğe ya da hızlı NVMe diske ihtiyacınız olduğunu ölçtüyseniz VDS ve bulut sunucu paketlerimize bakabilir, uygulama katmanındaki yükü ölçmek için uptime SLA hesaplayıcı aracımızı kullanabilirsiniz.