PostgreSQL'de bir satırı UPDATE ettiğinizde eski satır silinmez. Yeni bir sürüm yazılır, eskisi "ölü" olarak işaretlenir ve hâlâ diskte durur. DELETE de aynı şekilde çalışır: satır işaretlenir, yer geri verilmez. Bu tasarım MVCC'nin (çok sürümlü eşzamanlılık kontrolü) bedelidir ve okuyucuların yazıcıları hiç beklememesini sağlar. Karşılığında bir temizlikçiye ihtiyaç duyarsınız; işte VACUUM budur.
VACUUM çalışmazsa iki şey olur. Birincisi tablolarınız şişer: bir milyon satırlık tablo diskte on milyon satırlık yer kaplar, her tam tarama on kat uzar ve önbelleğiniz ölü veriyle dolar. İkincisi çok daha ciddidir: PostgreSQL'in işlem numarası (transaction ID) sayacı sarmalanır ve veritabanı kendini yazmaya kapatır. Bu rehberde VACUUM'un ne yaptığını, autovacuum'un nasıl ayarlandığını, şişmeyi nasıl ölçüp gidereceğinizi ve sarmalanma uyarısını gördüğünüzde ne yapmanız gerektiğini anlatacağım.
Ölü Satırlar ve MVCC#
PostgreSQL her satırın yanında iki gizli sütun tutar: xmin (satırı oluşturan işlemin numarası) ve xmax (satırı silen ya da güncelleyen işlemin numarası). Bir sorgu çalıştığında, kendi görünürlük anlık görüntüsüne göre hangi satır sürümünü göreceğine karar verir. Bu yüzden uzun süren bir SELECT, aynı anda güncellenen satırların eski hâlini görmeye devam eder ve kimse kimseyi kilitlemez.
Bunu kendiniz görebilirsiniz:
CREATE TABLE deneme (id int PRIMARY KEY, deger text);
INSERT INTO deneme VALUES (1, 'ilk');
UPDATE deneme SET deger = 'ikinci' WHERE id = 1;
-- Ölü satırlar hala fiziksel olarak orada
SELECT n_live_tup AS canli, n_dead_tup AS olu
FROM pg_stat_user_tables WHERE relname = 'deneme';
n_dead_tup sütununda 1 görürsünüz. Bir tek satır için önemsiz ama günde yüz bin güncelleme alan bir oturum tablosunda bu sayı hızla milyonlara çıkar. VACUUM'un işi bu ölü satırların kapladığı alanı tabloya geri kazandırmaktır — işletim sistemine değil, tabloya. Yani dosya küçülmez, ama sonraki INSERT'ler o boşlukları kullanır.
VACUUM üç iş yapar:
- Ölü satırların yerini yeniden kullanılabilir olarak işaretler.
- Görünürlük haritasını (visibility map) günceller, böylece "yalnızca indeks taraması" mümkün olur.
- Eski satırları donuk (frozen) hâle getirerek işlem numarası sarmalanmasını engeller.
Üçüncü madde, ilk ikisinden çok daha kritiktir ve çoğu kişi VACUUM'u sadece yer temizliği sanarak bunu kaçırır.
Autovacuum Nasıl Karar Veriyor#
Autovacuum, arka planda çalışan bir başlatıcı (launcher) süreç ve onun tetiklediği işçilerden oluşur. Her autovacuum_naptime süresinde bir tabloları tarar ve şu eşiği hesaplar:
vacuum eşiği = autovacuum_vacuum_threshold + (autovacuum_vacuum_scale_factor * canlı satır sayısı)
Varsayılanlarla bu şu demektir: 50 + (0.2 * satır sayısı). On milyon satırlık bir tabloda eşik iki milyon ölü satırdır. Yani tablonuz yüzde 20 şişmeden autovacuum kılını kıpırdatmaz. Küçük tablolar için makul olan bu oran, büyük tablolarda felakettir.
Mevcut ayarlarınıza ve tabloların durumuna bakalım:
SELECT
relname AS tablo,
n_live_tup AS canli,
n_dead_tup AS olu,
round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 1) AS olu_yuzde,
last_autovacuum,
last_autoanalyze
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC
LIMIT 15;
last_autovacuum sütunu boş ya da çok eskiyse, o tablo için autovacuum ya hiç çalışmıyor ya da yetişemiyor demektir. Genel ayarları postgresql.conf içinde makul bir seviyeye çekin:
autovacuum = on
autovacuum_max_workers = 4
autovacuum_naptime = 30s
autovacuum_vacuum_scale_factor = 0.05 # %20 yerine %5
autovacuum_analyze_scale_factor = 0.02
autovacuum_vacuum_cost_limit = 2000 # varsayılan 200, çok düşük
autovacuum_vacuum_cost_delay = 2ms
autovacuum_vacuum_cost_limit en çok fark yaratan parametredir. Autovacuum kasıtlı olarak yavaş çalışır ki normal iş yükünü boğmasın; ama modern SSD'lerde varsayılan 200'lük bütçe gülünç derecede düşüktür ve autovacuum büyük tablolara asla yetişemez. 2000'e çıkarmak temizleme hızını on kat artırır.
Yoğun güncellenen tablolar için genel ayarı bozmadan tablo bazında agresifleşin:
-- Oturum tablosu: günde yüz binlerce UPDATE alıyor
ALTER TABLE oturumlar SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_vacuum_threshold = 1000,
autovacuum_vacuum_cost_limit = 4000
);
-- Ayarı görmek için
SELECT relname, reloptions FROM pg_class WHERE relname = 'oturumlar';
Genel ayarlar için postgresql.conf optimizasyon rehberi yazısındaki bellek ve kaynak hesaplamalarına da göz atın; maintenance_work_mem değeri VACUUM'un hızını doğrudan etkiler.
Tablo Şişmesini Ölçmek#
Şişme (bloat), bir tablonun içindeki canlı verinin kapladığı alan ile diskte kapladığı gerçek alan arasındaki farktır. Kesin ölçüm için pgstattuple eklentisi en güvenilir yoldur:
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT
pg_size_pretty(pg_total_relation_size('siparisler')) AS toplam_boyut,
round(dead_tuple_percent::numeric, 2) AS olu_yuzde,
pg_size_pretty(free_space::bigint) AS bos_alan
FROM pgstattuple('siparisler');
Bu eklenti tabloyu tam tarar, yani büyük tablolarda maliyetlidir; yoğun saatlerde çalıştırmayın. Hızlı bir genel bakış için tablo boyutlarını listelemek de yeterli bir başlangıçtır:
SELECT
relname AS tablo,
pg_size_pretty(pg_total_relation_size(relid)) AS toplam,
pg_size_pretty(pg_relation_size(relid)) AS sadece_tablo,
pg_size_pretty(pg_indexes_size(relid)) AS indeksler
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;
İndekslerin tablodan büyük olması normal olabilir ama tabloların iki katına çıkmışsa indeks şişmesi de var demektir. İndeks yapılarının nasıl davrandığını PostgreSQL index türleri yazısında bulabilirsiniz.
VACUUM Türleri ve Ne Zaman Hangisi#
| Komut | Kilit seviyesi | Diski geri verir mi | Ne zaman |
|---|---|---|---|
VACUUM tablo | Hafif, okuma/yazma devam eder | Hayır (tabloya geri verir) | Rutin temizlik |
VACUUM ANALYZE tablo | Hafif | Hayır | Temizlik + istatistik güncelleme |
VACUUM FULL tablo | ACCESS EXCLUSIVE, tablo kilitlenir | Evet | Son çare, bakım penceresinde |
VACUUM FREEZE tablo | Hafif | Hayır | Sarmalanma riski yakınsa |
ANALYZE tablo | Hafif | Hayır | Yalnızca istatistik gerektiğinde |
VACUUM FULL tabloyu baştan yazar ve gerçekten diski geri verir; ama süresince tabloyu tamamen kilitler ve geçici olarak tablonun bir kopyası kadar ek disk alanı ister. Üretim ortamında 200 GB'lık bir tabloda VACUUM FULL çalıştırmak, saatlerce süren tam bir kesinti demektir.
Kesintisiz alternatif pg_repack eklentisidir. Tabloyu arka planda yeniden yazar ve yalnızca son geçişte kısa bir kilit alır:
sudo apt install postgresql-16-repack
# Tek bir tabloyu kesintisiz sıkıştır
pg_repack -d uygulama -t public.siparisler --no-superuser-check
# Yalnızca indeksleri yeniden oluştur
pg_repack -d uygulama -t public.siparisler --only-indexes
İndeks şişmesi için PostgreSQL 12 ve sonrasında yerleşik bir çözüm de var:
-- Tabloyu kilitlemeden indeksi yeniden oluştur
REINDEX INDEX CONCURRENTLY idx_siparisler_musteri;
REINDEX TABLE CONCURRENTLY siparisler;
CONCURRENTLY yavaştır ve başarısız olursa geçersiz bir indeks bırakabilir; \di+ çıktısında INVALID gördüğünüz indeksleri silip yeniden oluşturun.
Transaction ID Wraparound#
PostgreSQL'in işlem numarası 32 bitliktir; yaklaşık 4 milyar farklı değer alabilir. Sayaç dolduğunda başa sarar ve eski satırlar "gelecekten gelmiş" gibi görünerek görünmez olur. Bu, veri kaybı anlamına geldiği için PostgreSQL buna izin vermez: sarmalanmaya yaklaşıldığında önce uyarı verir, sonra veritabanını yazmaya kapatır.
VACUUM, eski satırları donuk (frozen) işaretleyerek bu sayacın ilerlemesini güvenli kılar. Yani autovacuum çalışmıyorsa er ya da geç bu duvara toslarsınız. Durumunuzu şu sorguyla izleyin:
SELECT
datname,
age(datfrozenxid) AS xid_yasi,
2000000000 - age(datfrozenxid) AS kalan_islem
FROM pg_database
ORDER BY age(datfrozenxid) DESC;
age değeri 200 milyonun altındaysa rahatsınız. autovacuum_freeze_max_age (varsayılan 200 milyon) aşıldığında PostgreSQL, autovacuum kapalı olsa bile zorunlu bir donduma vacuum'u başlatır. Bir milyara yaklaşıyorsanız ciddi bir sorun var demektir; log dosyanızda şu satırı görürsünüz:
WARNING: database "uygulama" must be vacuumed within 10000000 transactions
HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database.
Bu uyarıyı gördüğünüzde yapılacak iş bellidir:
# Tüm veritabanını dondurmalı vacuum ile tara (uzun sürer, sabırlı olun)
sudo -u postgres vacuumdb --all --freeze --jobs=4 --verbose
Tablo bazında en riskli olanları bulmak için:
SELECT relname, age(relfrozenxid) AS yas
FROM pg_class
WHERE relkind IN ('r','m')
ORDER BY age(relfrozenxid) DESC
LIMIT 10;
Autovacuum'u Engelleyen Üç Şey#
Autovacuum ayarları doğru olsa bile üç durum onun işini yapmasını engeller ve bunlar en sinsi sorunlardır.
Uzun süren işlemler. Açık kalmış bir işlem, kendisinden sonra ölmüş satırların temizlenmesini engeller; çünkü o işlem hâlâ onları görebiliyor olabilir. Bir raporlama betiği BEGIN deyip saatlerce açık kaldıysa, o süre boyunca hiçbir tablo temizlenemez.
SELECT pid, usename, state, now() - xact_start AS islem_suresi, left(query, 80)
FROM pg_stat_activity
WHERE state != 'idle' AND xact_start IS NOT NULL
ORDER BY xact_start
LIMIT 10;
idle in transaction durumundaki bağlantılar en büyük suçludur. Bunlara karşı bir zaman aşımı koyun:
idle_in_transaction_session_timeout = 10min
Terk edilmiş replikasyon yuvaları. Kullanılmayan bir replication slot, WAL dosyalarının silinmesini ve satırların temizlenmesini engeller. Kopmuş bir yedek sunucudan kalan yuva, diski doldurup autovacuum'u felç edebilir:
SELECT slot_name, active, pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS geride_kalan_wal
FROM pg_replication_slots;
active = false olan ve büyük bir WAL farkı biriktiren yuvayı pg_drop_replication_slot('adi') ile silin. Replikasyon kurulumunun ayrıntıları için PostgreSQL streaming replikasyon yazısına bakın.
Hazırlanmış işlemler (prepared transactions). PREPARE TRANSACTION ile açılıp COMMIT PREPARED ile kapatılmamış işlemler süresiz açık kalır. pg_prepared_xacts görünümünü kontrol edin; iki fazlı işlem kullanmıyorsanız max_prepared_transactions = 0 bırakın.
Sıkça Sorulan Sorular#
VACUUM ne sıklıkla çalıştırılmalı#
Elle çalıştırmanız gerekmez, doğru ayarlanmış autovacuum bu işi sürekli yapar. Yapmanız gereken, autovacuum_vacuum_scale_factor değerini büyük tablolar için düşürmek ve autovacuum_vacuum_cost_limit değerini SSD'ye uygun biçimde yükseltmektir. Yalnızca büyük toplu silme veya güncelleme işlemlerinden hemen sonra elle bir VACUUM ANALYZE çalıştırmak faydalıdır.
VACUUM FULL çalıştırmak güvenli mi#
Veriye zarar vermez ama tabloyu tamamen kilitler: süresince ne okuma ne yazma yapılabilir. Ayrıca tablonun kopyası kadar geçici disk alanı gerektirir. Üretim ortamında yalnızca planlı bir bakım penceresinde ve disk alanınızdan emin olduğunuzda kullanın. Kesintisiz alternatif pg_repack eklentisidir; aynı sonucu neredeyse kilitsiz üretir.
Tablo şişmesini nasıl anlarım#
En kesin yöntem pgstattuple eklentisiyle dead_tuple_percent değerine bakmaktır; yüzde 20'nin üzeri müdahale gerektirir. Daha ucuz bir gösterge, pg_stat_user_tables içindeki n_dead_tup sayısını n_live_tup ile karşılaştırmaktır. Ayrıca tablo boyutunun satır sayısına oranla mantıksız büyümesi ve tam taramaların zamanla yavaşlaması da şişmenin klasik belirtileridir.
Autovacuum'u kapatmalı mıyım#
Hayır, asla. Autovacuum kapatmak kısa vadede yükü azaltıyor gibi görünür ama tablolar şişer ve eninde sonunda işlem numarası sarmalanmasıyla veritabanı yazmaya kapanır. Yükü azaltmak istiyorsanız autovacuum_vacuum_cost_delay değerini artırın veya işçi sayısını azaltın; kapatmak çözüm değil, sorunu ertelemektir.
Uzun süren sorgular VACUUM'u nasıl etkiler#
Açık bir işlem, kendisinden sonra ölen satırların temizlenmesini engeller çünkü o işlemin görünürlük anlık görüntüsü hâlâ bu satırlara ihtiyaç duyabilir. Özellikle idle in transaction durumunda saatlerce bekleyen bağlantılar tüm veritabanının temizliğini durdurur. idle_in_transaction_session_timeout ayarıyla bu bağlantıları otomatik sonlandırın.
VACUUM diski geri veriyor mu#
Normal VACUUM alanı işletim sistemine geri vermez; yalnızca tablonun içinde yeniden kullanılabilir hâle getirir. Yani dosya boyutu küçülmez ama sonraki eklemeler yeni yer istemez. Diski gerçekten geri almak için VACUUM FULL, pg_repack ya da tabloyu yeniden oluşturmak gerekir. Tek istisna, tablonun sonundaki tamamen boş sayfaların kırpılmasıdır.
Kapanış#
VACUUM, PostgreSQL'in isteğe bağlı bir bakım aracı değil, tasarımının zorunlu bir parçasıdır. İşini yapmasına izin verirseniz varlığını hiç hissetmezsiniz; engellerseniz önce yavaşlık, sonra disk dolması, en sonunda yazmaya kapanmış bir veritabanıyla karşılaşırsınız. Aklınızda tutmanız gereken dört alışkanlık: autovacuum_vacuum_scale_factor değerini büyük tablolarda düşürüp cost_limit değerini yükseltin, pg_stat_user_tables üzerinden ölü satır oranını düzenli izleyin, idle in transaction bağlantılarına zaman aşımı koyun ve age(datfrozenxid) değerini izlemeyi rutininize alın.
Bu bakım işleri disk I/O'suna doğrudan bağlıdır; yavaş bir diskte autovacuum hiçbir zaman yetişemez ve şişme kartopu gibi büyür. NVMe destekli VDS ve bulut sunucu paketlerimizde PostgreSQL bakım işleri belirgin biçimde rahat çalışır; izleme, ayar ve düzenli bakım işini bize bırakmak isterseniz sunucu yönetimi hizmetimiz devrede. Bakım öncesi güvenli bir kopya için yedekleme çözümlerimize göz atmayı unutmayın.