İçindekiler

Proses Mimarisi

Postgresql, multi-process mimarisine sahip istemci-sunucu tipli ilişkisel veritabanı yönetim sistemidir ve tek bir sunucuda çalışır. İstemci, PostgreSQL sunucusuna bir istek gönderir ve PostgreSQL sunucusu istemci isteğine bir yanıt verir. İstemci ve sunucu genelde farklı sunucularda çalışırken aralarındaki bağlantı TCP/IP ağ bağlantısı üzerinden olur. PostgreSQL sunucusu, istemciden gelen birden çok sessionları eşzamanlı  yönetir, bunu her session için bir proses başlatarak sağlar.

Sunucu prosesi olan postgres prosesi veritabanı dosyalarını yönetir, istemci uygulamalarından gelen bağlantıları kabul eder ve istemci adına eylemler gerçekleştirir. Ana sunucu prosesi olan postgres prosesinin, her yeni bağlantı için yeni bir proses açtığını zaten yukarıda söylemiştik. Bu nedenle istemci ve sunucu prosesleri, ana sunucu prosesinin müdahalesi olmadan birbirleriyle iletişim kurar.  PostgreSQL sunucusu aşağıdaki gibi kabaca dört alt sisteme bölünebilir:

  • Process manager
  • Query processor
  • Utilities
  • Storage manager

Process manager

PostgreSQL’i başlattığımızda başlatılan ilk proses /usr/pgsql-12/bin/postgres daemon prosesidir. Bu prosesin recovery, shared data structures/memory  alanlarını başlatmak, zorunlu ve isteğe bağlı prosesleri başlatmak gibi pek çok sorumluluğu vardır. Bu prosesler aynı zamanda utility prosesler olarak da adlandırılır ve bgwriter, checkpointer, autovacuum launcher, log writer, stats collector prosesleri gibi prosesleri içerir. Ayrıca, daemon prosesi bağlantı isteklerini dinler, istemcilerden bağlantı istekleri alır ve istemci için sunucuda backend prosesi oluşturur.

Aşağıdaki şema, daemon prosesin bir bağlantı isteğini nasıl aldığını ve bir backend prosesi nasıl başlattığını (fork) anlamamızı sağlar. Backend prosesi, başarılı bir kimlik doğrulamasında, bu istemciden gelen istekleri işlemeye başlayacaktır:

Postgresql Mimarisi Architecture

Benzer şekilde, işlem tüm bağlantı istekleri için tekrarlanır.(max_connections limitine ulaşırsak o zaman proses başlamayacaktır). Sonuç olarak, etkin bir sunucu, bir süre sonra, aşağıdaki şemada gösterildiği gibi, sunucu başladığında orada olan proseslere ve istemci bağlantılarına hizmet etmek için epeyce proseslere sahip olacaktır:

Postgresql Process

Kullanıcı veritabanına bağlandıktan sonra, tipik olarak veri okumak (SELECT) , veri yazmak (UPDATE, DELETE, INSERT), tablo yapısında değişiklik yapma, index ekleme vb işler yapmak isteyecektir. Ancak bunu yapmak isteyen binlerce kullanıcının olabileceğini düşündüğümüzde işler biraz daha karmaşık hale geliyor. Bildiğimiz gibi veritabanı sunucusu yaptığımız donanımlarda en zayıf halka genellikle disk okuma yazma hızı oluyor, böyle düşününce her sorgu için dizinlerden/dosyalardan okumak perişan bir şekilde ölçeklenemez bir sistemle sonuçlanacaktır. Okumalar, yazmalar birçok dosyanın aranması, bu dosyaların açılması, belirli veri kayıtları için fseek () kullanılması, kilitleme, düzenleme ve kilidi açmayla sonuçlanacaktır. İşte bu anlatılan sıkıntıyı çok daha ölçeklenebilir ve daha hızlı hale getirmek için, hared buffers (memory area) kavramı vardır.  Yani backend prosesleri  artık dosyalardan okuma ve dosyalara yazma değil, performansta önemli bir gelişme ile buffer veya RAM ile uğraşıyor.

Query processor

Sonuçları elde etmek için bir sorgunun geçmesi gereken aşamalara kısa bir genel bakalım.

  1. Öncelikle PostgreSQL sunucusuna bir bağlantı kurulur
  2. Parser aşaması, uygulama tarafından iletilen sorguyu doğru sözdizimi için kontrol eder ve ardından bir query tree(ağaç yapısında parçalara böler) oluşturur.
  3. Rewrite aşaması, parser aşamasında oluşturulan query tree alır ve içlerindeki database objelerine olan referansları bulur ve bunlar üzerinden ilgili sorguları çıkarır yani parse edilen query’den parçalanmış ağaç yapısı çıkmıştı bunları syntax kontrolünden geçirdikten sonra veritabanındaki referanslarını bulup engine için anlamlı query tree’lere çevirir. (Örneğin sorgumuzda bir view kullandıysak rewrite aşamasında sorgu, view’in referans aldığı tabloları kullanacak şekilde yeniden yazılır)
  4. İşte en önemli kısım, çıkan sorguların minimum kaynak tüketecek şekilde ve minimum maliyetli planlanmasını sağlar. Optimizer (yeniden yazılmış) query tree’yi alır ve executor girdisi olacak bir query plan oluşturur. Bu aşamada bu planları çıkarırken kullanabileceği indexlere, tablolardaki verilere, veritabanı istatistiklerine vb. göre karar verip her bir gerçekleştireceği işlemin maliyetini hesaplayıp genel ortalamayı belirler ve ardından minimum maliyetteki planı seçer. Bu seçilen plana Execution Plan denir.
  5. Executor son aşamada execution plan’da ilerler ve planın temsil ettiği şekilde satırları alır.

Postgresql SQL Processing

Utility Processes

Utility prosesler depolama talebinde bulunmak, istatistikleri güncellemek , export, import, logging  gibi veritabanını korumak için bir araçlar sağlar.

Varsayılan bir yapılandırmayla, checkpointer, writer, WAL writer, autovacuum launcher ve Statistic collector prosesleri bulunur. Streaming replication açarsanız bubnun içinde başka prosesin çalıştığını göreceksiniz. Yapılandırmaya bağlı olarak, server log yazmak için bir proses görebiliriz.

Postgres Process

Background writer

Shared buffer pool’da bulunan dirty blokların diske yazılması işlemini gerçekleştirmekle görevli processtir. Veri tabanları sık erişilen veriyi her seferinde diskten okuma yazma yapmak yerine veriyi memory’de bir alanda tutarak değişiklikleri bu alan üzerinde yapar. Daha sonra belirli olaylar geliştiğinde (checkpoint işlemi, shared buffer pool’un dolması vb) memory’deki veri kalıcı olarak diske yazılır. Background writer, bir algoritmaya dayalı olarak diske özel dirty bufferları  yazmaktan sorumluyken, checkpoint tüm dirty bufferları  yazar. İlgili parametreler aşağıdaki gibidir.

#bgwriter_delay = 200ms # 10-10000ms between rounds

#bgwriter_lru_maxpages = 100 # 0-1000 max buffers written/round

#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
  • Gördüğümüz gibi, gecikme için varsayılan değer 200 milisaniyedir. Bu parametre, işlemin ardışık yürütmeler arasında ne kadar beklemesi gerektiğini belirtir.
  • Bgwriter_lru_maxpages parametresi, her bir yinelemede işlem tarafından yazılacak maksimum buffer sayısını belirtir.
  • Üçüncü parametre, ihtiyaç duyulacak buffer sayısına ulaşmak için de kullanılır. Bu değer 2 olarak ayarlanırsa ve ortalama son ihtiyacın (buffer) 10 olduğu tahmin edilirse, 20 buffer bulunana kadar veya bgwriter_lru_maxpages yazılana kadar dirty buffer temizlenir.

Bgwriter prosesi, postmaster tarafından start işlemi biter bitmez veya arşiv kurtarma işlemi gerçekleştiriyorsak kurtarma başlar başlamaz başlatılır. Postmaster’ın ona son vermesini söyleyene kadar hayatta kalır.

Checkpoint

Checkpoint zorunlu bir prosesdir. Checkpoint’in görevi  Background writer  prosesini tetikleyerek, buffer cache’deki dirty buffer’ların datafile’lara yazılmasını emretmek ve temizlenen page leri temiz olarak işaretlemektir. Ayrıca WAL dosyasını’da bu bilgiler ışığında günceller.  Crash durumunda, recovery prosedürü, REDO işlemini başlatması gereken noktayı belirlemek için logdaki en son checkpoint kaydına bakar. Bu noktadan önce data file larda yapılan herhangi bir değişikliğin zaten diskte olması garanti edilir. Bu nedenle, checkpoint’den sonra, redo kaydını içeren önceki log  segmentleri artık gerekli değildir silinebilir.

Peki checkpoint ne zaman gerçekleşir? Buna cevabımız otomatik veya elle olacaktır. Otomatik olduğunda bir dereceye kadar buna biz karar veririz. Checkpoint’in ne zaman gerçekleşmesi gerektiğine karar veren birkaç parametre vardır: checkpoint_segments, checkpoint_timeout ve checkpoint_ complete_target.

checkpoint_segments :  Bunun varsayılan değeri 3’tür. 3 WAL segmenti doldurulduğunda, checkpoint oluşur. Her WAL segmenti 16 MB’dir.

checkpoint_timeout : saniye (varsayılan), dakika veya bir saat olarak ayarlanabilen bir zaman aşımı değeridir.

NOT : Checkpoint segmentleri için düşük bir ayar, mevcut segmentlerin hızla doldurulmasına ve checkpoint lerin sık sık görülmesine neden olur. Benzer şekilde, checkpoint_timeout için düşük bir ayar da sık checkpointlere neden olacaktır. Buda, sık disk kullanımına neden olur. Öte yandan, bu değerleri çok yüksek tutarsak, az checkpointe neden olur buda yazma ağırlıklı bir sistemde, checkpoint sırasında diğer sorguların performansını etkileyen önemli I/O artışlarına neden olabilir, ayrıca seyrek çalışan checkpoint, sistem açılışında recovery gereken durumlarda bu süreyid e artıracaktır.

checkpoint_completion_target : Genellikle, PostgreSQL’in veriyi daha yavaş yazmaya çalışmasını sağlayan bir parametredir – checkpoint_completion_target * checkpoint_timeout zamanında bitirmek için. Genellikle 5 dakikaya ayarlı checkpoint_timeout’unuz vardır (değiştirmediyseniz) ve varsayılan checkpoint_completion_target 0,5’tir. Bu, PostgreSQL’in I/O yükünü düşürmek için checkpointin 2,5 dakika sürmesini sağlamaya çalışacağı anlamına gelir. Örnek verecek olursak dosyalara yazılacak 100GB verimiz var. Ve diskim saniyede 1 GB yazma kapasitesine sahip . Normal checkpoint  yaparken (8.3’ten önceki gibi), verileri yazmak için yazma kapasitemizi 100 saniye boyunca %100’e kadar kullanılmasına neden olur. Ancak – checkpoint_completion_target 0,5 olarak ayarlandığında – PostgreSQL verileri 2,5 dakikada yazmaya çalışır, böylece yazma kapasitemizin hepsini kullanmaz.((100*1024)/(2,5*60)=682 MB/s kullanır)

Manüel olarak checkpointi tetiklemek isterseniz aşağıdaki komutu kullanabiliriz.

CHECKPOINT;

Postgresql Checkpoint

Wal Writer

Daha önce de belirtildiği gibi, verilerde değişiklik yaptığımızda, değişiklikler data file lara hemen yazılmaz. Postgres bu değişiklikleri buffer daki  bloklara yazar ve ayrıca bunları WAL buffer’ına yazar (verilerde değişiklik yapılır yapılmaz). Bağlantıdan commit geldiğinde ise değişiklikler WAL segmentlerine yazılır. Kısaca WAL bufferda bulunan veriyi WAL dosyalarına kalıcı olarak yazmakla sorumlu prosesdir.

[root@primary data]# grep wal postgresql.conf
#wal_level = replica                    # minimal, replica, or logical
#wal_sync_method = fsync                # the default is the first option
#wal_compression = off                  # enable compression of full-page writes
#wal_log_hints = off                    # also do full page writes of non-critical updates
#wal_init_zero = on                     # zero-fill new WAL files
#wal_recycle = on                       # recycle WAL files
#wal_buffers = -1                       # min 32kB, -1 sets based on shared_buffers
#wal_writer_delay = 200ms               # 1-10000 milliseconds
#wal_writer_flush_after = 1MB           # measured in pages, 0 disables
max_wal_size = 1GB
min_wal_size = 80MB
#max_wal_senders = 10           # max number of walsender processes
#wal_keep_segments = 0          # in logfile segments; 0 disables
#wal_sender_timeout = 60s       # in milliseconds; 0 disables
#wal_receiver_status_interval = 10s     # send replies at least this often
#wal_receiver_timeout = 60s             # time that receiver waits for
#wal_retrieve_retry_interval = 5s       # time to wait before retrying to 

wal_level  : Wal_level önemli bir parametredir, çünkü bu ayar WAL ile yapabileceklerimiz üzerinde doğrudan bir etkiye sahiptir, sadece çökmeden kurtarmak için kullanabileceğimiz gibi, bir noktadan sonra kurtarma için arşivlemek için de kullanabiliriz veya bir stand-by sunucusu oluşturmak içinde kullanabiliriz. Yani wal_level parametresi ile WAL’a ne kadar bilgi yazılacağını belirler. Varsayılan değer, yedek bir sunucuda salt okunur sorguların çalıştırılması dahil, WAL arşivlemeyi ve çoğaltmayı desteklemek için yeterli veriyi yazan replica’dır. minimal, bir çökme veya anında kapanma durumundan kurtarmak için gereken bilgiler dışındaki tüm log kaydını kaldırır. logical, logical decoding için gerekli bilgileri ekler.

wal_sync_method   : WAL güncellemelerini diske göndermeye zorlamak için kullanılan yöntem. Fsync kapalıysa, WAL dosya güncellemeleri hiç zorlanmayacağından bu ayar geçersizdir. Alabileceği parametreler aşağıdaki gibidir. fsync ayarı, her transaction’ın her commit’den sonra sabit diske yazılmasını zorlar. Bunu kapatmak, özellikle toplu yükleme işlemleri için performansı artıracaktır. Fsync’in devre dışı bırakılması, elektrik kesintisi ve çökmeler durumunda veri tutarlılığı gibi ciddi sorunlara yol açabilir.

  • open_datasync
  • fdatasync
  • fsync
  • fsync_writethrough
  • open_sync

wal_buffers : WAL buffer için ayrılan shared memory miktarıdır. Bunu -1 olarak ayarlamak, shared_buffers ayarına göre otomatik bir seçimle sonuçlanacaktır. Otomatik ayarda, değer 64 KB ile 16 MB arasında değişebilir. Manuel olarak ayarlandığında, minimum olası değer 32 KB’dir.

wal_writer_delay : WAL writer’ın WAL’ı diske ne sıklıkla yazması gerektiğidir. Varsayılan değer 200 milisaniyedir.

Autovacuum Launcher

Varsayılan olarak olarak AÇIK olan isteğe bağlı bir prosesdir. Vacuum prosesi, önceden silinmiş (veya güncellenmiş) kayıtlar tarafından kullanılan alanı tablo içinde yeniden kullanılabilir olarak işaretler. Bunu manuel olarak yapmak için bir vacuum komutu da vardır. Vacuum esnasında tabloya lock lanmaz. Ancak, VACUUM FULL, alanı yeniden kullanılabilir olarak işaretlemenin yanı sıra, silinen veya güncellenen kayıtları siler ve tablo verilerini yeniden sıralar. Bu sırada tablo exclusive lock lanır. Autovacuum, vacuum sürecini otomatikleştirir. Veritabanının günün büyük bir bölümünde yoğun yük altında olduğu durumlarda, Postgres yoğun olmayan saatlerde vacuum planlayabilir. Vacuum konusu başlı başına bir yazı olacağı için burada detaya girmiyorum. Veritabanında çok az silme / güncelleme olsa veya hiç olmasa da, vacuum planlayıcı tarafından kullanılan veri istatistiklerini güncellediğinden rutin vacuum yapılması yararlıdır.

Statistic collector

Bu proses, adından da anlaşılacağı gibi, veritabanı hakkında istatistikler toplar. Varsayılan değeri açık olan isteğe bağlı bir prosesdir. Proses, disk bloğu, satırlara, tablolara ve indexlere erişimi izler. Ayrıca tablolar için kayıt sayılarının kaydını tutar ve vacuum ve analiz eylemlerini izler. Tek tek işlemlerin boşta kalmadan hemen önce toplayıcıya yeni istatistiksel sayımlar ilettiğini unutmamak önemlidir. Çalışan prosesler boşa çıkmadan önce collector’a yeni istatiksel verileri gönderir. Collector’a gelen veriler bir dizi tabloya kaydedilir ve buna sağlanan bir dizi view  aracılığıyla erişebiliriz. Viewler  pg_stat ile başlar.

Postgresql system view

Postgresql all tables

İstatistik toplama için de bir dizi parametre vardır. Varsayılan olarak devre dışı bırakılan sayaçlar şunlardır: log_statement_stats, log_parser_stats, log_planner_stats, log_executor_stats,  track_io_timing. The track_activities, update_process_title ve track_counts parametreleri ise varsayılan olarak açıktır.

Logging Proses

Bu isteğe bağlı bir prosesdiir ve varsayılan ayar kapalıdır. Bu prosesi başlatmak için logging_collector parametresini on olarak ayarlamamız gerekiyor: Bu prosose açmak için postgresql.conf dosyasında birkaç paramtreyi düzenlememiz gerekir.

 vi $PGDATA/postgresql.conf
log_destination = 'stderr'
logging_collector = on
log_directory = 'pg_log'
log_min_duration_statement = 0

--paramtrelerin gemesi için restartlarız
pg_ctl restart

WAL sender ve WAL receiver prosesi

Replikasyon sürecini desteklemek için PostgreSQL’in son sürümlerinde sunulan iki prosesdir. Sender prosei, WAL’leri standby sunucusuna gönderir ve ikincil taraftaki receiver prosesi onu alır.

Storage manager

Storage manager memory cache, disk buffers ve storage allocation işlerini yönetir.

Memory Mimarisi

PostgreSQL’deki memory mimarisi iki geniş kategoriye ayrılabilir:

  • Local memory area : Her backend prosesinin kendi kullanımı için ayrılır.
  • Shared memory area : PostgreSQL sunucusunun tüm prosesleri tarafından kullanılır.

Postgresql backend process

Local Memory Area

Her backend process kullanıcıdan gelen sorgulama işlemleri sırasında kullanmak için memory’den bir alan allocate eder. Bu alan local memory area’dır. Kendi içinde temp_buffer, work_mem ve maintenance_work_mem gibi 3 farklı alana ayrılır.

  • Work_mem : Sorgulama işlemleri sırasında join, order by, distinct operasyonları için kullanılır. Varsayılan değer dört megabayttır 4MB’dır.
  • Maintenance_work_mem : Reindex, vacuum gibi maintenance operasyonları için kullanılır. Varsayılan olarak 64 megabayttır 64MB’dır. Bu işlemlerden yalnızca biri bir veritabanı bağlantısı tarafından aynı anda yürütülebildiği için ve normalde bir kurulumda çoğu eşzamanlı olarak çalışmadığından, bu değeri work_mem’den çok daha büyük bir değere ayarlamak güvenlidir. Otomatik vacuum çalıştığında, bu alanın autovacuum_max_workers katına kadar tahsis edilebileceğini unutmayın
  • Temp_buffer : Temporary table’ların tutulduğu alandır. Varsayılan, sekiz megabayttır 8MB’dır.

Shared Memory Area

Postgresql server başlatıldığında memory’den allocate edilen alandır. Bu alanda kendi içinde birkaç alt bölüme ayrılır.

  • shared buffer pool : Bellekteki verileri okumak veya yazmak her zaman diğer ortamlardan daha hızlıdır. Bir veritabanı sunucusu, ister READ ister WRITE erişimi olsun, verilere hızlı erişim için belleğe de ihtiyaç duyar. PostgreSQL’de buna ” shared buffers” denir ve shared_buffers parametresi tarafından kontrol edilir. Shared buffers’ın gerektirdiği RAM miktarı, PostgreSQL instance için kullanım ömrü boyunca her zaman işletim sisteminden locklanır. Varsayılan değer tipik olarak 128 MB’dır.
  • WAL buffer : Veri kaybının yaşanmaması için kullanılan transaction kayıt dosyalarına WAL dosyası denir(XLOG kaydı olarak da bilinir). WAL kayıtları dosyalara yazılmadan önce memory’de bu alanda tutulur. WAL buffer tahsisi, wal_buffers parametresi tarafından kontrol edilir. Varsayılan ayarlara bırakılırsa, shared bufferın 1/16’sı büyüklüğünde tahsis edilir.
  • commit log : Commit log page lerini RAM’de tutumak için ayrılmış alandır. Commit log pageleri transaction meta verilerinin loglarını içerir ve WAL verilerinden farklıdır. Commit log, tüm transactionların commit durumuna sahiptir ve bir transactionın tamamlanıp tamamlanmadığını (committed) gösterir. Bu bellek alanını kontrol etmek için belirli bir parametre yoktur. Bu, veritabanı engine tarafından küçük miktarlarda otomatik olarak yönetilir.

NOT 1: Veritabanı sorgu sürelerini iyileştirmekten sorumlu olan tek başına bu bellek parçası değildir, ancak işletim sistemi önbelleği de(OS Cache) çok sayıda veriyi hizmete hazır tutarak oldukça yardımcı olur. Bu iki önbellek birlikte, gerçek fiziksel okuma ve yazma sayısında önemli bir azalma ile sonuçlanır. Bu iki önbelleğe alma düzeyine ek olarak, bir disk controller önbelleği, bir disk sürücüsü önbelleği vb. Olabilir. Sonuç olarak, bu önbelleklerin artısı fiziksel I/O’yu azaltarak performansı artırmasıdır. Bu yazdıklarımı bir örnek üzerinden anlatacak olursak mesela bir SELECT sorgusu çalıştırdık diyelim;

İlk olarak, proses, istediği verilerin veritabanı buffer cache’de bulunup bulunmadığını kontrol edecektir. İkinci olarak, veritabanı buffer cache’de mevcut değilse, işletim sistemine belirli bir dosya veya bloğu getirme isteği gider. İşletim sistemi önbelleğinin zaten bloğa sahip olması ve bunu veritabanı önbelleğine geçirmesi ihtimali vardır. Her iki durumda da fiziksel bir I/O engellenir. Son olarak, yalnızca veriler bu önbelleklerde  bulunmadığında,  gerçekten fiziksel bir I/O ile sonuçlanır. Bu üç olasılık aşağıdaki diyagramda gösterilmektedir:

Postgresql Shared Buffer

NOT 2 : Sistem RAM’inin çoğunun veritabanına verildiği ve işletim sistemi önbelleğinin senkronize ve doğrudan yazma gibi yaklaşımlar kullanılarak atlandığı bir veritabanına alışkınsanız, PostgreSQL’i aynı şekilde kurmak istemezsiniz. Bazı alanlarda ters etki yaratacaktır. Örneğin, PostgreSQL’in commit log bilgilerini pg_clog dizininde depolar. Bu veriler düzenli olarak okunur ve yazılır, bu nedenle işletim sisteminin bu erişimi optimize etmekle ilgileneceği varsayılır.

Öyleyse neden tüm RAM’i işletim sistemine vermiyorsunuz sorusunu sorabilirsiniz. PostgreSQL shared buffer cache ın işletim sistemi önbelleğinden daha iyi performans göstermesinin ana nedeni, arabellek kullanım sayısını tutmasıdır. Bu, arabelleklerin 0’dan 5’e kadar bir “popülerlik” puanı almasını sağlar ve puan ne kadar yüksekse, bu arabelleklerin önbelleği terk etme olasılığı o kadar düşük olur. Veritabanı, ihtiyaç duyduğu veriler için daha fazla alan açmak üzere çıkarılacak bir şey aradığında, bu kullanım sayısını azaltır. Kullanım sayısındaki her artış, bu engelden kurtulmayı zorlaştırır. Bu uygulamaya clock-sweep algoritması denir.

Tipik işletim sistemi önbellekleri, herhangi bir arabelleğe, bu veriler kaldırılmadan önce yalnızca bir veya iki şans verir. Genellikle, işletim sistemi bir çeşit LRU algoritması kullanır. Veritabanınızda sık kullanılan veriler varsa, verilerin işletim sistemindekine kıyasla veritabanının shared RAM’inde kalması daha iyi olasıdır.

Mustafa Bektaş Tepe
İyi Çalışmalar

Loading