VACUUM, dead tuple ların bulunduğu depolama alanını geri kazanır. Öncelikle Postgres üzerinde bir tabloda Update veya Delete çalıştırdığımızda neler olduğuyla başlayalım. DELETE işlemleri gerçekleştirildiğinde, verilerin bulunduğu tupleları fiziksel olarak silmek yerine mevcut tuple’ı DEAD olarak işaretler. Benzer şekilde, GÜNCELLEME işlemi gerçekleştirildiğinde, karşılık gelen mevcut tuple’ı DEAD olarak işaretler ve yeni bir tuple ekler.(Aslında Update = Hide/Unvisible + Insert demektir.)

Bu nedenle, her bir DELETE ve UPDATE komutu, asla kullanılmayacak olan bir DEAD tuple ile sonuçlanacaktır. Bu dead tuple lar, veriler aynı veya daha az sayıda olsa bile gereksiz fazladan alan kullanımına yol açacaktır.

İşte vacuum DEAD tuple ile ilgilenmeyi sağlayan bakım sürecidir. Vacuum işlemi sequential scan sırasında okunan tuple sayısını azaltacağı için okuma işlemlerinde de hızlanmaya sebep olacaktır. Sequential scan sıralı okuma işlemidir ve dead tuple’ları atlamaz. Dolayısı ile dead tuple’ların temizlenmesi daha az satır okunması anlamına gelir.

Vacuum, tüm tabloların taranmasını içerdiğinden, maliyetli bir işlemdir. 8.4 (2009) sürümünde, Visibility Map (VM), dead tupleların işaretlenme verimliliğini artırmak için tanıtıldı. 9.6 (2016) sürümünde, VM geliştirilerek freeze prosesi iyileştirildi.

Visibility Map

VM’in temel konsepti basittir. Her tablonun, tablo dosyasındaki her page’in görünürlüğünü tutan ayrı bir görünürlük haritası vardır. Pagelerin görünürlüğü, her page’de dead tuple olup olmadığını belirler. Vacuum, dead tuple içermeyen pageleri atlayabilir. Aşağıdaki şekildeki gibi bir tablomuz olduğunu düşünelim; Tablonun üç page’den oluştuğunu ve 0. ve 2. sayfaların dead tuplelar içerdiğini ve 1. sayfanın olmadığını varsayalım. Bu tablonun görünürüülük haritası(vm), hangi pagelerin dead tuplelar içerdiği hakkında bilgi içerir. Bu durumda, vacuum, VM’nin bilgilerine başvurarak 1. sayfayı atlar.

Postgresql Vacuum

Her VM bir veya daha fazla 8 KB page’den oluşur ve bu dosya ‘vm’ sonekiyle saklanır. Örnek olarak, FSM (18751_fsm) ve VM (18751_vm) dosyaları ile relfilenode 18751 olan bir tablo dosyası aşağıda gösterilmiştir.

 $ cd $PGDATA
$ ls -la base/16384/18751*
-rw------- 1 postgres postgres  8192 Apr 21 10:21 base/16384/18751
-rw------- 1 postgres postgres 24576 Apr 21 10:18 base/16384/18751_fsm
-rw------- 1 postgres postgres  8192 Apr 21 10:18 base/16384/18751_vm

Free Space Map

Free space map, her veri dosyasının boş alanı hakkındaki bilgileri depolar. Yukarıdada gördüğümüz gibi anadosyaya paralel ‘fsm’ soneki ile biten dosyada saklanır bilgiler. FSM’nin amacı yeterli alan içeren bir page’i hızlı bir şekilde bulmaktır veya böyle bir page’in bulunmadığını veri dosyasının bir page kadar büyütülmesi gerektiğini belirlemek içindir.

Freeze Tuple

Postgresql’deki her yazma işlemi veya bağımsız ifadeye benzersiz bir transaction id (veya XID) atanır. Bu XID, diğer eşzamanlı kullanıcıların transactionla ilişkili satır değişikliklerini görebileceğini belirleyerek öncelik görünürlüğünü(precedence visibility) belirler. Salt okunur işlemlere XID atanmaz. Bu XID’ler, depolamadaki her satıra xmin ve xmax biçiminde damgalanır ve satırı “görebilen” minimum ve maksimum eşzamanlı transactionları belirtir. Bunları row header lerine yerleştirerek, Postgresql’in eşzamanlılık(concurrency) yeteneklerini destekleyen transaction çakışması çözümünü merkezileştiriyor.

Ancak, XID’lerle ilgili bir sorun var: bunlar dört baytlık bir tamsayıdır. Bu, 2,1 milyar işlemden sonra başa sarmaları gerektiği anlamına gelir. Ve XID sayacı başa sardığında, eski XID’lerin tüm eski row headerlarından çıkarılması gerekir. Bu, XID’nin xmin ve xmax’tan kaldırılması ve özel RelFrozenXID değeriyle değiştirilmesi anlamına gelir; bu, herkesin görebileceği çok eski bir transactionı belirtir.

VACUUM

VACUUM’un birincil görevi, DEAD kayıtlarının kullandığı depolama alanını geri kazanmaktır. Geri kazanılan depolama alanı işletim sistemine geri verilmez, bunun yerine aynı page’de birleştirilirler, bu nedenle aynı tabloya gelecekteki verilerin eklenmesiyle yeniden kullanılabilirler. VACUUM işlemi belirli bir tablo üzerinde devam ederken, tabloda exclusive lock alınmadığı için aynı tabloda aynı anda diğer READ/WRITE işlemi yapılabilir. Tablo adının belirtilmemesi durumunda, VACUUM veritabanının tüm tablolarında gerçekleştirilecektir. VACUUM işlemi, ShareUpdateExclusive lock içinde bir dizi işlemin altında gerçekleştirilir;

  • Tüm dead tupleları almak için veritabanının tüm tablolarının (veya belirtilen tablonun) tüm pagelerini taranır.
  • İhtiyaç duyulursa eski tuple lar Freeze lenir.
  • Dead tuplerı gösteren index tuple lar silinir.
  • Free space Map (FSM) ve Visibility Map (VM) güncellenir.
  • Mümkünse son page kısalır (serbest bırakılan DEAD tuple varsa).
  • İlgili tüm sistem tablolarını günceller.

VACUUM için yukarıdaki çalışma adımlarından da görebileceğimiz gibi, ilişkili olduğu dosyanın tüm pagelerini işlemesi gerektiğinden çok maliyetli bir işlem olduğu açıktır. Bu nedenle, vacuumlanması gerekmeyen olası pageleri atlamak çok gereklidir. Visibility map dead tuple lar hakkında bilgi tutuğu için ilgili page’e vacuum gerekli olmadığı ve dolayısıyla bu page’e  güvenli bir şekilde atlanabileceği varsayılabilir.

Full VACUUM

VACUUM tüm DEAD tuplelarını kaldırsa ve ileride kullanmak üzere page’i birleştirse de, işletim sistemine aslında alan bırakılmadığından tablonun toplam depolama alanını azaltmaya yardımcı olmaz. Örn. Toplam depolama alanının 1,5 GB’a ulaştığını ve bu 1 GB’ın dead tuple tarafından işgal edildiğini varsayalım, VACUUM’dan sonra yaklaşık 1 GB daha fazla tuple insert için kullanılabilir, ancak yine de toplam depolama alanı 1,5 GB olarak kalacaktır.

Full VACUUM, alanı gerçekten boşaltarak ve işletim sistemine geri döndürerek bu sorunu çözer. Ama bunun bir bedeli var. VACUUM’dan farklı olarak, Full VACUUM paralel çalışmaya izin vermez, çünkü Full VACUUM exclusive  lock getirir. Adımlar aşağıdadır;

  • Exclusive lock konur
  • Paralel boş bir depolama dosyası oluşturur
  • Tüm live tupler ları mevcut depolamadan yeni ayrılmış depolamaya kopyalar
  • Ardından orijinal depolama alanını siler
  • Son olarak da koyduğu lock’ı kaldrır

Adımlardan da anlaşılacağı gibi, yalnızca live tuple ları için gerekli depolamaya sahip olacaktır. Tablo üzerinde varsa indexler de kopyalanan yeni tablo üzerinde yeniden oluşturulur. Dolayısı ile indexler yeniden oluşturulduğu için VACUUM işleminden sonra tekrar analiz yapılmaz.

Özetle depolama alanının büyük kısmının dead tuple lardan kaynaklandığı bilinmedikçe, full VACUUM’dan kaçınılmalıdır. Postgresql eklentisi pg_freespacemap, boş alan hakkında ipucu elde etmek için kullanılabilir.

ANALYZE

Sorgularınız çalışırken postgres planner o tablo ile ilgili istatistikleri kullanarak en verimli yolu bulur ve veriye ulaşır. Eğer normalde çok hızlı cevap veren sorgularınızda yavaşlama olmaya başladıysa tablonuzdaki verinin değişmesine bağlı olarak (ilgili tablodan çok fazla kayıt silinmesi veya tabloya çok fazla yeni kayıt gelmesi durumunda) tablonun istatistikleri biraz bozulabilir ve planner veriyi getirebileceği en verimli yolu bulmada sıkıntı yaşayabilir. Bu gibi durumlarda “analyze” tablonun istatistiklerini güncelleyerek sorgu performansınızın artmasını sağlar. Analyze çalışan tabloda sadece read lock oluşur ve normalde bu işlem sırasında kesinti olmaz, herhangi bir olumsuzluk yaşamazsınız.

 Vacuum Analyze = Vacuum + Analyze 

Yani eğer analyze yerine “Vacuum Analyze” çalıştırdığınızda hem vacuum yapmış (dead row’ ları temizlemiş) + hem de analyze yapmış (tablonun istatistiklerini güncelleyerek sorguların hızlanmasını sağlamış) olursunuz.

Auto VACUUM

VACUUM’u manuel olarak yapmak yerine Postgresql, VACUUM’u periyodik ve otomatik olarak tetikleyen bir demon’u destekler. VACUUM her uyandığında (varsayılan olarak 1 dakika’da bir çalışır, autovacuum_naptime parametresi ie düzenlenir), birden fazla workerı başlatır (varsayılan olarak 3 worker başlatılır, autovacuum_worker parametresi ile düzenlenir). Auto vacuumu başlatmak için, autovacuum parametresini ON olarak ayarlamanız gerekir.

 SELECT name, setting FROM pg_settings WHERE name=’autovacuum’; 

Auto-vacuum worker ları, ilgili belirlenmiş tablolar için aynı anda VACUUM işlemlerini gerçekleştirir. VACUUM, tablolar üzerinde herhangi exclusive  lock almadığından, diğer veritabanı çalışmalarını etkilemez. Bu arada bir obje üzerinde auto vacuum olduğunda otamatikmen analyze işlemi de gerçekleşir.

Auto-VACUUM yapılandırması, veritabanının kullanım modeline göre yapılmalıdır. Çok sık olmamalıdır  veya çok fazla gecikmemelidir . Postgres’te, kullanım ve etkinlik bilgilerini izleyen Stats Collector adlı başka bir background proses vardır. Bu prosesle toplanan bilgiler, auto vacuum başlatıcısı tarafından auto vacuum için aday tabloların listesini belirlemek için kullanılır. Postgresql, vacuum veya analiz gerektiren tabloları otomatik olarak tanımlar, ancak yalnızca otomatik vacuum etkinleştirildiğinde bunlar işlem uygular. track_counts paramtresi, Stats Collector tarafından kullanılır, ON olmadan, autovacuum aday tablolarına erişemez. Auto vacuum, vacuum yapacağı tabloyu aşağıdaki formüle göre seçer;

Autovacuum VACUUM thresold for a table = autovacuum_vacuum_scale_factor * number of tuples + autovacuum_vacuum_threshold

  • autovacuum_vacuum_scale_factor veya autovacuum_analyze_scale_factor: Formüle eklenecek tablo kayıtlarının fraksiyonu. Örneğin, 0,2 değeri tablo kayıtlarının% 20’sine eşittir.
  • autovacuum_vacuum_threshold veya autovacuum_analyze_threshold: Bir auto vacuumu tetiklemek için gereken minimum eski kayıt veya dml sayısı.

Formulü bir örnek üzerinden açıklarsak 1000 kayıtlı bir tablo olduğu düşünelim ve paramtrelerin de aşağıdaki gibi ayarlandığını varsayalım.

autovacuum_vacuum_scale_factor = 0.2
autovacuum_vacuum_threshold = 50
autovacuum_analyze_scale_factor = 0.1
autovacuum_analyze_threshold = 50 

Belirtilen tablo auto vacuuma aday olurken;

 Toplam Eski kayıt sayısı = (0,2 * 1000) + 50 = 250 

Belirtilen tablo auto vacuum analiz adayı olurken;

Toplam Ekleme / Silme / Güncelleme sayısı = (0.1 * 1000) + 50 = 150 

Postgresql, veritabanı global parametrelerinin yanında bireysel tablo düzeyinde auto vacuum ayarlarını yapılandırmanıza olanak tanır.

ALTER TABLE scott.employee SET (autovacuum_vacuum_scale_factor = 0, autovacuum_vacuum_threshold = 100); 

veya

 ALTER TABLE table_name SET (autovacuum_enabled = true);
SELECT reloptions FROM pg_class WHERE relname= table_name;

Tablolar için auto vacuumu ayrı ayrı ayarlamak için, tablodaki ekleme / silme / güncelleme sayısını bilmeniz gerekir, bunun basit yolu olarak da postgres katalog viewini görüntüleyebilirsiniz: bu bilgiyi almak için pg_stat_user_tables kullanılır.

 SELECT n_tup_ins as "inserts",n_tup_upd as "updates",n_tup_del as "deletes", n_live_tup as "live_tuples", n_dead_tup as "dead_tuples"
FROM pg_stat_user_tables
WHERE schemaname = 'scott' and relname = 'employee';
 inserts | updates | deletes | live_tuples | dead_tuples 
---------+---------+---------+-------------+-------------
      30 |      40 |       9 |          21 |          39

Autovacuum’un fazla zaman harcadığı tabloları loga kaydetmek isteyebilirsiniz. Bu durumda, log_autovacuum_min_duration parametresini ayarlamamız gerekir (varsayılan değer milisaniyedir), böylece bu değerden daha uzun süre çalışan herhangi bir otomatik vacuum Postgresql log dosyasına kaydedilir.

Autovacuum bir temizlik olarak düşünülebilir. Autovacuum, bir tablonun 8KB (varsayılan blok_boyutu) pagelerini diskten okur ve dead tuple içeren pageleri değiştirir. Bu hem okuma hem de yazmayı içerir. Bu nedenle, bir tabloda yoğun bir transaction ve aynı zamanda  çok sayıda dead tuple için çalışan auto vacuum olduğunda, bu yoğun bir IO işlemine neden olabilir. Bu sorunu önlemek için, vacuum nedeniyle IO üzerindeki etkisini en aza indirecek birkaç parametremiz var.

  • autovacuum_vacuum_cost_limit : auto vacuumun ulaşabileceği toplam maliyet sınırı (tüm auto vacuum işleri ile birlikte).
  • autovacuum_vacuum_cost_delay : auto vacuum, autovacuum_vacuum_cost_limit maliyetine ulaşan bir temizlik yapıldığında, bu milisaniye kadar uyuyacaktır.
  • vacuum_cost_page_hit : Shared bufferda bulunan ve disk okuması gerektirmeyen bir page’i okumanın maliyeti.
  • vacuum_cost_page_miss : Shared buffer’da olmayan bir page’i getirmenin maliyeti. vacuum_cost_page_dirty : İçinde dead tuple bulunduğunda her page’e yazmanın maliyeti.

Bu parametrelerin default değeri;

autovacuum_vacuum_cost_limit = -1 (So, it defaults to vacuum_cost_limit) = 200
autovacuum_vacuum_cost_delay = 20ms
vacuum_cost_page_hit = 1
vacuum_cost_page_miss = 10
vacuum_cost_page_dirty = 20 

ÖRNEK

Şimdi bütün bu anlattığımız teorik bilgiler ile ilgili börnek yapacak olursak;

Test için tablo oluştururuz.

create table demo1(id int, id2 int); 

Tablo içine 10.000 kayıt ekleriz.

 postgres=# insert into demo1 values(generate_series(1,10000), generate_series(1,10000));
INSERT 0 10000
 
postgres=# create extension pg_freespacemap;
CREATE EXTENSION
postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1');
 npages | average_freespace_ratio
--------+-------------------------
  45 |                0.00
(1 row)

Dead tuple oluşturmak için 5000 kayıt sileriz.

 postgres=# delete from demo1 where id%2=0;
DELETE 5000

Dead tuple ları aşağıdaki sorgu ile görebiliriz.

postgres=# SELECT schemaname, relname, n_live_tup, n_dead_tup, last_autoanalyze, last_autovacuum FROM pg_stat_all_tables where relname='demo1';
 schemaname | relname | n_live_tup | n_dead_tup | last_autoanalyze | last_autovacuum 
------------+---------+------------+------------+------------------+-----------------
 public     | demo1   |       5000 |       5000 |                  | 
(1 row) 

Vacum’u çalıştıralım.

postgres=# vacuum demo1;
VACUUM
 
postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1');
 npages | average_freespace_ratio
--------+-------------------------
  45 |               45.07
(1 row) 

Bu boş alan artık Postgresql tarafından yeniden kullanılabilir, ancak bu alanı işletim sistemine kazandırmak istiyorsak, şunu çalıştırmamız lazım;

postgres=# vacuum full demo1;
VACUUM
 
postgres=# SELECT count(*) as npages, round(100 * avg(avail)/8192 ,2) as average_freespace_ratio FROM pg_freespace('demo1');
 npages | average_freespace_ratio
--------+-------------------------
  23 |                0.00
(1 row) 

vacuum veya vacuum full komutunda tablo adı belirtmessek bütün veritabanı için işlemi gerçekleştirir. Tabloda en son ne zaman vacuum yapıldığını aşağıdaki sorgu ile öğrenebiliriz.

SELECT relname, last_vacuum, last_autovacuum FROM pg_stat_user_tables where relname='demo1'; 

Mustafa Bektaş Tepe
İyi Çalışmalar

 52 total views,  2 views today