PG_STAT_IO ile Veri Tabanı Kümülatif I/O İstatistiklerinin İncelenmesi

PostgreSQL 16 versiyonu ile birlikte gelen pg_stat_io, veri tabanlarındaki I/O ilişkili işlemlere ait istatistikleri kümülatif olarak ve tüm cluster için gösteren, doğru kullanıldığında ve yorumlandığında oldukça faydalı olabilecek bir view. View içerisinde I/O işlemleri backend process türlerine göre gruplanmış olarak gösteriliyor, örneğin veri tabanına bağlantı kuran kullanıcıların işlemlerine ilişkin istatistikler “backend_type”=”client backend” şeklinde bir arada gösteriliyor. Diğer bir kolon “context” ise I/O işleminin içeriğini, bir diğer anlamda çeşidini belirtiyor ve “normal”, “vacuum”, “bulkread”, “bulkwrite” değerlerini alabiliyor. View kolonları ile ilgili detaylı bilgiyi buradan inceleyebilirsiniz. Yazımızda pg_stat_io view’ının sunduğu içeriği daha iyi anlayabilmek adına, basit işlemler sonucu ortaya çıkan istatistikleri inceleyeceğiz.

Testlerimize yeni bir cluster oluşturarak ve kümülatif olarak doldurulacak view’ın başlangıç ​​durumunu sorgulayarak başlıyoruz.

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;
 
  backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes | hits  | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+-------+-----------+--------+--------
 autovacuum worker | relation | normal  |     8 |      0 |          0 |       0 |     8192 | 19354 |         0 |        |      0
 client backend    | relation | normal  |   144 |      0 |          0 |       0 |     8192 |  2994 |         0 |        |      0
(2 rows)

İki kolonlu bir test tablosu yaratarak bu tablo içerisine 15 bin random kayıt insert edelim ve pg_stat_io view’ını sorgulayalım.

postgres=# create table io_test (c1 integer,c2 text);

postgres=# INSERT INTO io_test( c1, c2)
SELECT floor(random() * 999 + 1)::int,
left(md5(random()::text),3)
FROM generate_series(1,15000);

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;

   backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes | hits  | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+-------+-----------+--------+--------
 autovacuum worker | relation | normal  |    32 |      0 |          0 |       2 |     8192 | 25258 |         0 |        |      0
 client backend    | relation | normal  |   230 |      0 |          0 |      73 |     8192 | 19305 |         0 |        |      0
(2 rows)

Insert ile veri tabanına yazma işlemi yapmış olmamıza rağmen “writes” değeri halen 0 görünüyor. Gözümüze çarpan değişiklik ise “client backend” tipi için “hits” kolonundaki değerin değişimi. Bu noktada “hits” kolonunu açıklamakta fayda var. Bu kolon dökümantasyonda shared buffers’da istenen bloğun bulunma / erişilme sayısı olarak tanımlanmış. PostgreSQL’de bir tabloya insert edilen veri direk olarak diskteki data file’a değil, shared buffer’a yani RAM üzerine yazılır. Bizim Insert işlemimiz de, shared buffer üzerindeki boş alanlara yapılan yazma ile “client backend” tipi için “hits” değerini arttırdı.

Şimdi veri tabanında manuel olarak checkpoint işlemini tetikleyerek view’ı yeniden sorguluyoruz.

postgres=# checkpoint;

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;

   backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes | hits  | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+-------+-----------+--------+--------
 autovacuum worker | relation | normal  |    32 |      0 |          0 |       2 |     8192 | 25589 |         0 |        |      0
 client backend    | relation | normal  |   230 |      0 |          0 |      73 |     8192 | 19358 |         0 |        |      0
 checkpointer      | relation | normal  |       |    111 |        111 |         |     8192 |       |           |        |     32
(3 rows)

Sorgu sonucumuzda, Checkpointer process’i tarafından 111 adet write (diske yazma) işlemi yapıldığına ilişkin bir kayıt geldi. Her işlem 8192 byte (op_bytes kolonunun değeri) olduğuna göre yaklaşık 880 KB verinin yazıldığını görüyoruz. Bu aşamada tablo boyutunu sorguladığımızda tablomuzun da yaklaşık 600KB olduğunu görüyoruz. Internal işlemleri de hesaba kattığımızda değerler tutarlı ve shared buffers’da tutulan 15 bin kayıtlık verinin checkpoint işlemi ile birlikte diske aktarıldığını söyleyebiliriz. Çıkaracağımız sonuç ise “client backend” tipinde “write” sayısının artmadığını görmemizin, veri tabanında kullanıcıların yazma işlemi yapmıyor oldukları anlamına gelmediği.

Şimdi ise tablomuzdaki verileri silerek, silme işleminin sonucunu inceleyelim. Yalnız bu silme işlemini tek bir delete cümlesi ile değil her defasında random bir değer silmeyi 1000 defa tekrar edecek şekilde çalıştırıyoruz.

postgres=# do
$$
declare 
  i record;
begin
  for i in 1..1000 loop
	delete from io_test where c1=(SELECT floor(random() * 999 + 1)::int);
  end loop;
end;
$$
;

postgres=# checkpoint;

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;

   backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes |  hits   | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+---------+-----------+--------+--------
 autovacuum worker | relation | normal  |    32 |      0 |          0 |       2 |     8192 |   26638 |         0 |        |      0
 client backend    | relation | normal  |   277 |      0 |          0 |      73 |     8192 | 1273446 |         0 |        |      0
 checkpointer      | relation | normal  |       |    111 |        111 |         |     8192 |         |           |        |     32
(3 rows)

Silme işleminin yine “client backend” tipindeki “hits” değerini, bu defa çok daha yüksek bir oranda arttırdığını görüyoruz. Bu yüksek artışın nedeni her bir delete cümlesinin index bulunmayan tablonun tamamını okuması, bu verinin de shared buffer‘da bulunması.
Şimdi ise tabloya daha büyük miktarda veri yükleyelim.

postgres=# INSERT INTO io_test( c1, c2)
SELECT floor(random() * 999 + 1)::int,
left(md5(random()::text),3)
FROM generate_series(1,3000000);

postgres=# checkpoint;

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;

   backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes |  hits   | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+---------+-----------+--------+--------
 autovacuum worker | relation | normal  |    32 |      0 |          0 |       4 |     8192 |   35316 |         0 |        |      0
 client backend    | relation | normal  |   277 |      0 |          0 |    7113 |     8192 | 2885411 |         0 |        |      0
 checkpointer      | relation | normal  |       |   7166 |       7166 |         |     8192 |         |           |        |     46
(3 rows)

3 milyon satır random veriyi insert edip sonrasında manuel checkpoint aldık. Hemen ardından pg_stat_io view’ını sorguladığımızda beklediğimiz gibi “client backend” tipindeki “hits” değerinin ve checkpoint’ten gelen “write” değerinin artmasının yanında bir değerin daha büyük miktarda arttığını görüyoruz. Bu da “client backend” tipindeki “extends” değeri. Bu değer tablo extend, yani tablonun disk üzerindeki alanını büyütme işleminin kaç defa yapıldığı gösterir ve testimizde 3 milyon kayıt ile 100MB’a ulaşan tabloda 7000’den fazla kez bu işlemin yapıldığını görüyoruz.

Bu defa tablodaki verilerin yarısını update edelim.

postgres=# update io_test set c1=111 where c1<500;
UPDATE 1499783

postgres=# checkpoint;

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;

   backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes |   hits   | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+----------+-----------+--------+--------
 autovacuum worker | relation | normal  |    32 |      0 |          0 |       4 |     8192 |    46692 |         0 |        |      0
 autovacuum worker | relation | vacuum  | 22780 |   9670 |          0 |       0 |     8192 |   124412 |        32 |  22748 |
 client backend    | relation | normal  |   279 |      0 |          0 |   14145 |     8192 | 10065347 |         0 |        |      0
 checkpointer      | relation | normal  |       |  17708 |      17708 |         |     8192 |          |           |        |     50

Update işlemi bittikten bir süre sonra autovacuum işlemi devreye girmiş ve “backend_type”=”autovacuum worker” ve “context”=”vacuum” olmak üzere yeni bir istatistiğin geldiğini görüyoruz. Bu işlemin shared buffers dan yaptığı okumanın yanında diskten de okuma ve yazma yaptığını, bu sayede test tablomuzda “dead row” ların temizlendiğini ve boşalan alanın yeniden kullanılabilir hale geldiğini söyleyebiliriz.

Şimdiye kadar yaptığımız işlemler shared buffers içerisinde doluluk yaratmadı ve bu nedenle yaptığımız işlemlerin diske yazılması her zaman checkpoint işlemi ile gerçekleşti. Ancak PostgreSQL’de dirty buffer’ın diske yazılması görevini checkpoint dışında bir process daha yapar, bu process “background writer“dır ve shared buffers üzerinde her zaman boş alan olmasını sağlamak amacıyla gerektiğinde devreye girerek diske yazma işlemini gerçekleştirir. Biz de şimdi shared buffers kullanımını arttıracak şekilde, tablomuzdaki verinin tamamını yeniden tablomuza insert ediyoruz.

postgres=# insert into io_test select * from io_test ;

postgres=# select backend_type,object,context,reads,writes,writebacks,extends,op_bytes,hits,evictions,reuses,fsyncs 
from pg_stat_io WHERE reads <> 0 OR writes <> 0;

   backend_type    |  object  | context | reads | writes | writebacks | extends | op_bytes |   hits   | evictions | reuses | fsyncs
-------------------+----------+---------+-------+--------+------------+---------+----------+----------+-----------+--------+--------
 autovacuum worker | relation | normal  |    32 |      0 |          0 |       4 |     8192 |    48536 |         0 |        |      0
 autovacuum worker | relation | vacuum  | 22780 |   9670 |          0 |       0 |     8192 |   124412 |        32 |  22748 |
 client backend    | relation | normal  |   279 |      0 |          0 |   14145 |     8192 | 10123567 |         0 |        |      0
 checkpointer      | relation | normal  |       |  32109 |      32109 |         |     8192 |          |           |        |     50
 background writer | relation | normal  |       |   6564 |       6528 |         |     8192 |          |           |        |      0

İşte bu operasyon shared buffers kullanımında yer baskısı oluşturdu ve pg_stat_io view’ının çıktısında “backend_type”=”background writer” olmak üzere yeni bir istatistik gördük. Bu backend process’in checkpoint ile birlikte yine diske yazma işlemi yaptığını da istatistiklerden anlayabiliyoruz.

Tüm bu testler ile, pg_stat_io view’ının incelenmesinde dikkat edilmesi gereken bazı noktaların üzerinde durmak istedik. View’ın çıktısında gördüğünüz diğer backend process’lerin ve yazımızda incelemediğimiz kolonların anlamları için dökümantasyonu inceleyebilir, sorularınız için bizimle iletişime geçebilirsiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir