Geçen yıl, bir müşteri projesinde PostgreSQL veritabanı sunucusunun disk kullanımının aniden fırladığını gördüm. Sistem, kritik bir üretim ERP’si için çalışıyordu ve diskin %90’ını aşan doluluk oranları, ciddi bir alarm sinyaliydi. İlk kontrol ettiğim yerlerden biri, tabii ki pg_wal dizini oldu. Tahmin ettiğim gibi, bu dizin gigabaytlarca, hatta terabaytlarca WAL (Write-Ahead Log) dosyasıyla dolup taşmıştı.
Bu durum, aslında PostgreSQL kullanan birçok sistem yöneticisinin veya geliştiricisinin başına gelen klasik bir senaryo. WAL bloat, sistemin sessizce disk alanını tüketmesine neden olan, ancak doğru adımlar atılmazsa büyük sorunlara yol açabilen sinsi bir problem. Bu yazıda, bu sorunu nasıl tespit ettiğimi ve disk alanını geri kazanmak için adım adım uyguladığım 4 temel stratejiyi anlatacağım.
WAL Bloat Nedir ve Neden Ortaya Çıkar?
PostgreSQL’in veri bütünlüğünü ve dayanıklılığını sağlayan temel mekanizmalardan biri WAL, yani Write-Ahead Log sistemidir. Herhangi bir veri değişikliği (INSERT, UPDATE, DELETE) önce WAL dosyalarına yazılır, ardından ana veri dosyalarına uygulanır. Bu, çökme durumunda veritabanının son tutarlı durumuna geri dönebilmesini garantiler. Normalde, WAL dosyaları belirli bir süre sonra veya checkpoint’lerden sonra geri dönüştürülür.
Ancak, bazı durumlar bu geri dönüşüm sürecini aksatabilir ve WAL dosyalarının diskte birikmesine neden olabilir. İşte buna WAL bloat diyoruz. Ben genelde aşağıdaki ana nedenlerle karşılaşıyorum:
- Uzun Süreli İşlemler (Long-Running Transactions): Açık kalan bir transaction, WAL segmentlerinin geri dönüştürülmesini engelleyebilir çünkü bu transaction’ın başladığı noktaya geri dönme ihtiyacı olabilir. Gece çalışan ve saatlerce açık kalan bir raporlama işlemi bu duruma tipik bir örnektir.
- Replikasyon Slotları: Fiziksel replikasyon kullanan sistemlerde, replikasyon slotları, replica sunucusunun yakalaması gereken WAL segmentlerini tutar. Eğer bir replica sunucusu uzun süre çevrimdışı kalırsa veya slotu düzgün yönetilmezse, bu slotlar çok sayıda WAL dosyasını tutmaya devam eder. Çevrimdışı kalan veya unutulan bir test replica’sının slotu, ana sunucuda çok büyük WAL birikimine yol açabilir.
archive_modeAyarları:archive_modeaktif olduğunda, WAL dosyaları belirlenen bir konuma arşivlenir. Bu, felaket kurtarma senaryoları için kritiktir. Ancak, arşivleme işlemi başarısız olursa veya arşivleme hedefi dolarsa, WAL dosyalarıpg_waldizininde birikmeye devam eder.wal_keep_size(veyawal_keep_segments) Değeri: Bu parametre, PostgreSQL’inpg_waldizininde ne kadar WAL dosyasını tutacağını belirler. Büyük bir değer belirlenirse veya varsayılanın üzerinde bir ihtiyaç olursa, gereksiz yere fazla WAL dosyası tutulabilir.
Bu durumları erkenden tespit etmek ve müdahale etmek, disk doluluk oranlarının %100’e ulaşmasını ve veritabanı hizmetinin durmasını engellemek için hayati öneme sahip.
Sadece Disk Değil: WAL Bloat’ın Performans Maliyeti
WAL bloat’ı “disk dolması” sorunu olarak görmek, buzdağının yalnızca görünen kısmına bakmaktır. Saha deneyimimde, şişmiş bir pg_wal dizini disk alarmı vermeden çok önce performansı sessizce yemeye başlar. Bu yüzden temizliğe geçmeden önce, neyin pahasını ödediğimizi netleştirmek istiyorum.
- Disk I/O Darboğazı: Devasa boyuta ulaşmış WAL dosyaları, diskin hem WAL yazmaları hem de veri dosyası erişimleri için aynı anda meşgul kalmasına yol açar. Yoğun bir sistemde
max_wal_size’ı optimize edip gereksiz WAL üreten sorguları düzeltmek, I/O yükünü belirgin biçimde düşürebilir. - Artan Kurtarma Süresi (Recovery Time): Bir çökme veya yeniden başlatma sonrası PostgreSQL, tutarlı duruma dönmek için biriken WAL’ı baştan oynatır. Biriken WAL ne kadar fazlaysa kurtarma da o kadar uzar; normalde kısa süren bir işlem WAL bloat yüzünden kat kat uzayabilir. Yüksek erişilebilirlik isteyen sistemlerde bu, doğrudan kabul edilemez bir kesinti demektir.
- Bellek ve CPU Yükü: WAL buffer’ları bellekte yer tutar, segmentlerin diske yazılması ise CPU zamanı ister. Sürekli okunup yazılan bir
pg_wal, ek bellek/CPU baskısı yaratır. - Sorgu Latency’si: Yukarıdaki üç etkinin bileşkesi, indeks taramalarını ve okuma/yazmaları yavaşlatarak en sade sorguları bile geciktirir. Basit
SELECTsorgularının yavaşladığı durumlarda, kök neden WAL bloat kaynaklı disk I/O’su olabilir; yönetimi düzeltmek süreleri normale döndürür.
WAL Bloat Tespiti: Disk Doldu Alarmı ve Ötesi
WAL bloat genellikle disk doluluk oranı alarmlarıyla kendini gösterir, ancak proaktif olmak her zaman daha iyidir. Ben, bu tür sorunları önceden fark etmek için birkaç farklı yöntem ve metrik kullanıyorum.
İlk ve en basit kontrol, pg_wal dizininin boyutunu kontrol etmektir. SSH üzerinden sunucuya bağlanıp şu komutu çalıştırıyorum:
sudo du -sh /var/lib/postgresql/16/main/pg_wal
Not: Dizin yolu PostgreSQL sürümünüze ve kurulumunuza göre farklılık gösterebilir. Benim sistemimde /var/lib/postgresql/16/main şeklindeydi.
Bu komut, pg_wal dizininin toplam boyutunu gösterir. Eğer bu boyut, beklediğimden çok daha büyükse (örneğin, normalde 1-2 GB olması gereken bir sistemde 50 GB görüyorsam), hemen daha derinlemesine incelemeye başlıyorum.
Veritabanı içinden, aktif WAL LSN (Log Sequence Number) konumunu ve arşivleme durumunu kontrol edebiliriz:
SELECT pg_current_wal_lsn(), pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') AS wal_bytes_since_start;
Bu sorgu, anlık WAL konumunu ve veritabanı başlatıldığından bu yana yazılan WAL miktarını verir. Ancak, asıl önemli olan, pg_wal dizinindeki WAL dosyalarının neden geri dönüştürülemediğini anlamak. Bunun için replikasyon slotlarını ve uzun süreli işlemleri kontrol etmek gerekiyor.
Disk boyutunun yanı sıra, kaç adet WAL segmenti biriktiğini doğrudan veritabanı içinden de görebiliriz. SSH gerektirmediği için izleme sorgularına gömmesi pratik olan yöntem pg_ls_waldir()’dir (PostgreSQL 10+):
SELECT count(*) AS wal_file_count,
pg_size_pretty(sum(size)) AS wal_directory_size
FROM pg_ls_waldir();
Her segment varsayılan olarak 16 MB olduğundan, segment sayısı tek başına size hızlı bir büyüklük hissi verir; 1-2 GB beklediğim bir sistemde binlerce segment görüyorsam alarm zilleri çalmaya başlar. Not: Bazı eski yazılarda dolaşan pg_walfilecount() diye bir fonksiyon yoktur; doğru fonksiyon pg_ls_waldir()’dir.
Arşivlemenin sessizce başarısız olup olmadığını anlamanın en temiz yolu ise pg_stat_archiver görünümüdür. Log taramadan önce buraya bakmak, sorunun arşivleme boru hattında olup olmadığını saniyeler içinde söyler:
SELECT archived_count, failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;
failed_count sürekli artıyor ve last_failed_time yakın bir tarihi gösteriyorsa, WAL dosyaları arşivlenemediği için pg_wal içinde tutuluyor demektir — kök neden büyük olasılıkla archive_command’dadır (bkz. Adım 2).
Ayrıca, PostgreSQL loglarını (genellikle journalctl -u postgresql ile erişilebilir) disk full veya WAL segment gibi anahtar kelimelerle taramak, sorunun kökenini anlamak için faydalı olabilir. Bazen, arşivleme komutunun başarısız olduğuna dair mesajlar veya belirli bir replikasyon slotunun ilerleyemediğine dair uyarılar görebilirsiniz. Örneğin LOG: could not archive WAL file "...": No space left on device gibi bir mesaj, arşivleme hedefinin dolduğunu açıkça gösterir.
Adım 1: Replikasyon Slotlarını Kontrol Etmek ve Temizlemek
Replikasyon slotları, PostgreSQL’in fiziksel replikasyonu için kritik bir mekanizmadır. Bir slot, belirli bir replika sunucusunun yakalaması gereken WAL LSN’sini tutar. Eğer bu slot aktifse, PostgreSQL bu LSN’den sonraki WAL dosyalarını silmez veya geri dönüştürmez. Bu, replica sunucusu çevrimdışı kalsa bile, geri geldiğinde kaldığı yerden devam edebilmesi için gereklidir. Ancak, kullanılmayan veya unutulmuş slotlar, pg_wal dizininin şişmesine neden olabilir.
Replikasyon slotlarını kontrol etmek için ana veritabanında aşağıdaki sorguyu çalıştırıyorum:
SELECT slot_name, plugin, slot_type, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots;
Bu sorgunun çıktısı, aktif olan (active = true) ve pasif olan (active = false) tüm replikasyon slotlarını gösterir. Özellikle active sütunu f (false) olan slotlara dikkat etmek gerekiyor. Bu slotlar, bir replika sunucusunun bağlı olmadığını veya artık var olmadığını gösterebilir. restart_lsn sütunu ise, bu slotun tuttuğu en eski WAL LSN’sini gösterir; bu da WAL dosyalarının ne kadar geriye doğru tutulduğu hakkında bir fikir verir.
Eğer pasif ve artık ihtiyaç duyulmayan bir slot tespit edersem, onu silmek için aşağıdaki komutu kullanıyorum:
SELECT pg_drop_replication_slot('slot_name_buraya');
Örneğin, bir test sunucusunun eski bir replikasyon slotu test_replica_slot adıyla kalmışsa ve bu sunucu artık mevcut değilse, SELECT pg_drop_replication_slot('test_replica_slot'); komutunu çalıştırarak o slotu temizlerim. Bu işlemden sonra, PostgreSQL kullanılmayan WAL segmentlerini otomatik olarak geri dönüştürmeye başlar ve disk alanı kademeli olarak boşalmaya başlar.
Bu adımı, her zaman ilk kontrol ettiğim ve genellikle en hızlı sonuç aldığım çözüm olarak görüyorum. Bir keresinde, eski bir geliştirme ortamının replikasyon slotu yüzünden ana sunucunun pg_wal dizini onlarca gigabayta ulaşmıştı. Slotu sildikten sonra disk kullanımı kısa sürede normale döndü.
Adım 2: archive_mode ve wal_keep_size Ayarlarını Optimize Etmek
WAL bloat yönetiminde ikinci önemli adım, PostgreSQL’in WAL arşivleme ve tutma politikalarını kontrol eden parametreleri doğru yapılandırmaktır. Bu parametreler, postgresql.conf dosyasında bulunur ve sistemin WAL dosyalarını ne kadar süreyle veya ne kadar boyutta tutacağını belirler.
archive_mode
archive_mode parametresi, WAL dosyalarının arşivlenip arşivlenmeyeceğini kontrol eder. Değerleri off, on veya always olabilir.
off: WAL arşivleme devre dışıdır.on: WAL dosyalarıarchive_commandile arşivlenir.always: Hemongibi çalışır hem de crash recovery sırasında dahi arşivleme yapar.
Eğer archive_mode on veya always olarak ayarlanmışsa ve archive_command düzgün çalışmıyorsa veya arşivleme hedefi (örneğin, bir NFS paylaşımı) dolmuşsa, WAL dosyaları pg_wal dizininde birikmeye başlar. Bu durumu kontrol etmek için postgresql.conf dosyasını inceler ve archive_command’ın doğru yapılandırıldığından ve çalışır durumda olduğundan emin olurum.
Bazen, archive_command içinde basit bir cp komutu yerine, bir bulut depolama servisine yükleme yapan daha karmaşık bir script olabilir. Bu script’in hatalarını kontrol etmek, sorunu çözmede anahtar olabilir. Bir projede, S3’e yükleme yapan bir script’in kimlik doğrulama hatası verdiğini fark ettim, bu da kısa sürede ciddi bir WAL birikimine yol açmıştı.
wal_keep_size (veya wal_keep_segments)
Bu parametre, ana sunucunun pg_wal dizininde replikasyon için tutacağı minimum WAL miktarını (MB cinsinden) belirler. PostgreSQL 9.x ve önceki sürümlerde wal_keep_segments adıyla WAL segment sayısı cinsinden belirtilirdi.
wal_keep_size = 0(varsayılan): Replika sunucuları için özel olarak WAL tutulmaz. Eğer replikasyon slotları kullanılıyorsa, slotlar WAL tutma işlemini yönetir.wal_keep_size = NMB: Belirtilen miktarda WAL dosyası tutulur.
Bu değeri çok yüksek ayarlamak, gereksiz yere disk alanı tüketimine neden olabilir. Ancak, çok düşük ayarlamak da replica sunucularının geride kalmasına ve timeline senkronizasyon sorunlarına yol açabilir. Genellikle, replikasyon slotları kullanıyorsam bu değeri 0da tutarım, çünkü slotlar çok daha dinamik ve verimli bir yönetim sağlar. Eğer replikasyon slotu kullanmıyorsam (ki önermem), o zaman replica sunucularının en uzun çevrimdışı kalma süresi ve WAL üretim hızına göre bir değer belirlerim.
Bu parametreleri değiştirmek için postgresql.conf dosyasını düzenledikten sonra, değişikliklerin etkili olması için PostgreSQL servisini yeniden başlatmak gerekebilir. Ancak ALTER SYSTEM komutu ile de dinamik olarak değiştirebiliriz:
-- wal_keep_size'ı 5 GB olarak ayarlamak (örneğin, replikasyon slotu kullanmıyorsanız)
ALTER SYSTEM SET wal_keep_size = '5GB';
-- Veya 0'a çekmek (replikasyon slotu kullanıyorsanız)
ALTER SYSTEM SET wal_keep_size = '0';
-- Değişikliklerin uygulanması için PostgreSQL'i yeniden başlatın
sudo systemctl restart postgresql@16-main.service
max_slot_wal_keep_size — Disk’i Patlamaktan Koruyan Sigorta
wal_keep_size üretilen WAL’ı sınırlamaz; asıl tehlike, bir replikasyon slotunun arkasında sınırsızca WAL biriktirebilmesidir. Standby sunucu kopar, slot açık kalır ve ana sunucunun diski sessizce dolar. PostgreSQL 13 ile gelen max_slot_wal_keep_size, tam da bu senaryoya karşı bir sigortadır: bir slotun tutabileceği WAL’a tavan koyar. Bu limit aşıldığında slot otomatik olarak lost durumuna düşer; replikasyon kopar ama ana veritabanı ayakta kalır.
Aşağıda, WAL tutma davranışını belirleyen üç parametreyi ve üretimde başlangıç olarak verdiğim değerleri özetledim:
| Parametre | İşlevi | Önerilen Üretim Değeri | Etki Derecesi |
|---|---|---|---|
wal_keep_size |
Standby için birincilde tutulacak minimum WAL boyutu | 4GB – 8GB |
Orta |
max_slot_wal_keep_size |
Bir replikasyon slotunun biriktirebileceği maksimum WAL boyutu | 32GB (diskin patlamasını önler) |
Çok Yüksek |
archive_timeout |
Belirli aralıklarla WAL arşivlemeyi zorunlu kılar | 600s (10 dakika) |
Düşük |
Bu ayarların optimizasyonu, özellikle uzun vadeli disk alanı yönetimi için kritiktir. Benim deneyimimde, çoğu zaman wal_keep_size parametresinin gereksiz yere yüksek ayarlanmış olması veya archive_command’ın sessizce başarısız olması, bu tür bloat sorunlarının ana nedenlerinden biri oluyor.
Adım 3: Uzun Süreli İşlemleri ve Geri Alınamayan Durumları Yönetmek
WAL bloat’ın bir diğer yaygın nedeni, veritabanında uzun süre açık kalan işlemlerdir (long-running transactions). Bir işlem BEGIN; ile başladığında ve COMMIT; veya ROLLBACK; ile sonlanmadığında, bu işlemin başladığı noktadan itibaren tüm WAL kayıtları, işlemin sonlanması ihtimaline karşı tutulur. Bu, MVCC (Multi-Version Concurrency Control) mimarisinin bir gereğidir ve işlemin tutarlılığını sağlar. Ancak, bu durum aynı zamanda WAL dosyalarının birikmesine yol açar.
Bu tür işlemleri tespit etmek için pg_stat_activity görünümünü kullanıyorum:
SELECT pid, datname, usename, client_addr, application_name, backend_start, state, query_start,
query, state_change, pg_wal_lsn_diff(pg_current_wal_lsn(), backend_xmin::text::pg_lsn) AS xmin_wal_diff_bytes
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY query_start ASC;
Bu sorgu, aktif olan tüm işlemleri listeler ve query_start sütunu, işlemin ne kadar süredir çalıştığını gösterir. xmin_wal_diff_bytes kısmı ise, bu işlemin en eski aktif transaction ID’sine göre ne kadar WAL’ın tutulduğunu gösterir. Eğer query_start çok eskiye dayanıyorsa ve xmin_wal_diff_bytes değeri yüksekse, bu işlem potansiyel bir WAL bloat kaynağıdır.
Bir müşteri projesinde, bir geliştiricinin test amaçlı başlattığı ancak unutulan bir transaction günlerce açık kalmış ve büyük bir WAL birikimine neden olmuştu. Bu tür durumlar için, sorumlu olan pid’i tespit ettikten sonra, işlemi iptal etmek veya sonlandırmak gerekebilir.
pg_cancel_backend(pid): İşlemi nazikçe iptal etmeye çalışır. Çoğu zaman yeterlidir.pg_terminate_backend(pid): İşlemi zorla sonlandırır. Bu, işlemi yapan uygulamanın hata almasına neden olabilir, bu yüzden son çare olarak kullanılmalıdır.
-- Örneğin, 12345 PID'li işlemi iptal et
SELECT pg_cancel_backend(12345);
-- Veya zorla sonlandır
SELECT pg_terminate_backend(12345);
Uzun süreli işlemler sadece WAL bloat’a neden olmakla kalmaz, aynı zamanda VACUUM ve AUTOVACUUM işlemlerinin de eski satır sürümlerini temizlemesini engelleyebilir. Bu da table bloat ve indeks bloat’ına yol açar. Bu yüzden, uygulama kodlarında transaction’ların mümkün olduğunca kısa ve verimli olmasını sağlamak, WAL yönetimi için de kritik bir adımdır. Bir üretim ERP’sinde, autovacuum ayarlarını daha agresif yaparak (örneğin autovacuum_vacuum_scale_factor = 0.05) ve uzun süreli transaction’ları erken tespit eden monitoring alarmları kurarak bu sorunu büyük ölçüde hafiflettim.
Adım 4: pg_basebackup ve Geçici Dosyaların Temizliği
PostgreSQL’in pg_basebackup aracı, fiziksel yedeklemeler almak ve replika sunucularını başlatmak için kullanılır. Bu işlem sırasında veya kesintiye uğradığında, pg_wal dizininde veya ana veri dizininde bazı geçici dosyalar veya tamamlanmamış WAL segmentleri kalabilir. Bu durumlar da WAL bloat’a katkıda bulunabilir.
pg_basebackup işlemi sırasında, genellikle ana sunucuda bir replikasyon slotu oluşturulur (eğer belirtilmişse) ve WAL dosyaları bu slot üzerinden aktarılır. Eğer pg_basebackup aniden kesintiye uğrarsa veya tamamlanamazsa, bu slot aktif kalabilir ve WAL dosyalarının birikmesine neden olabilir. Bu durumu Adım 1’de bahsettiğim gibi pg_replication_slots görünümünü kontrol ederek tespit edebiliriz.
Ayrıca, pg_wal dizininde bazen geçici dosyalar veya tamamlanmamış WAL segmentleri kalabilir. Bu dosyaların isimleri genellikle 00000001XXXXXXXXXXX.partial veya benzer bir formattadır. Bu tür dosyalar normalde otomatik olarak temizlenir, ancak nadiren de olsa bir hata veya sistem çökmesi sonrası kalıcı olabilirler.
Bu tür durumları tespit etmek için pg_wal dizinini doğrudan kontrol etmek gerekebilir:
sudo ls -lh /var/lib/postgresql/16/main/pg_wal/ | grep -E '\.partial$|temp'
Eğer bu tür geçici dosyalar bulursam ve bunların kesinlikle bir işlem tarafından kullanılmadığından eminsem, manuel olarak silmeyi düşünebilirim. Ancak, bu işlem son derece dikkatli yapılmalıdır, çünkü yanlış WAL dosyalarını silmek veritabanı tutarsızlığına veya veri kaybına yol açabilir. Genellikle, bu tür dosyalar veritabanı yeniden başlatıldığında veya bir checkpoint çalıştırıldığında temizlenir.
Bir keresinde, test ortamında pg_basebackup işlemi sırasında disk dolmuş ve işlem yarım kalmıştı. Sonrasında pg_wal dizininde birkaç partial WAL dosyası kalmıştı. Veritabanını yeniden başlattığımda, bu dosyalar otomatik olarak temizlenmişti. Bu da PostgreSQL’in kendi kendini toparlama yeteneğinin bir göstergesidir.
Bu adımın temel amacı, yedekleme ve replikasyon süreçlerinin doğru yönetildiğinden emin olmaktır. Düzenli ve başarılı pg_basebackup işlemleri, yedeklemelerin geçerli olmasını sağlarken, aynı zamanda gereksiz WAL birikimini de engeller. Yedekleme stratejimi gözden geçirmek ve pg_basebackup komutlarını target veya slot parametreleriyle doğru kullandığımdan emin olmak, bu tür sorunları minimize etmeme yardımcı oluyor.
Sonuç: WAL Bloat Yönetimi Sürekli Bir Mücadeledir
PostgreSQL WAL bloat yönetimi, “bir kere ayarla ve unut” yaklaşımının işe yaramadığı, sürekli dikkat ve izleme gerektiren bir alandır. Yukarıda bahsettiğim 4 adımı, farklı projelerde ve kendi yan ürünlerimin altyapısında defalarca uyguladım ve her seferinde disk alanımı başarıyla geri kazandım. Ancak her seferinde, sorunun kökenini anlamak için detaylı bir araştırma ve bazen de farklı bir yaklaşım gerekti.
Unutulmamalıdır ki, bu tür sorunlar genellikle tek bir nedeni değil, birden fazla faktörün bir araya gelmesiyle ortaya çıkar. Uzun süreli bir işlem, eski bir replikasyon slotu ve hatalı bir arşivleme komutu aynı anda aktif olabilir ve karmaşık bir WAL bloat senaryosu yaratabilir. Bu yüzden, pg_wal dizininin boyutunu düzenli olarak izlemek, pg_stat_activity ve pg_replication_slots görünümlerini periyodik olarak kontrol etmek, benim rutin sistem yönetimi görevlerimin önemli bir parçasıdır.
Bu sorunlarla karşılaşmak, aslında sistemlerimizi daha iyi anlamamızı ve daha dayanıklı altyapılar kurmamızı sağlıyor. Bir sonraki yazımda, PostgreSQL’de autovacuum ayarlarının performans üzerindeki etkilerini ve table bloat ile nasıl mücadele ettiğimi anlatacağım.