PostgreSQL’de Crash Recovery Mekanizmasını Etkileyen Faktörler

OLTP veri tabanlarından beklentimiz sistemin ani/beklenmedik kapanmasından sonra tekrar veri kayıpsız ve tutarlı bir şekilde, yani commit edilmiş işlemlerin tamamlandığı, commit edilmemiş işlemlerin geri alındığı bir şekilde tekrar açılmasıdır. Bu işleme crash recovery ismini veriyoruz ve PostgreSQL’de crash recovery mekanizmasının merkezinde WAL (Write Ahead Log) var. PostgreSQL, bahsettiğimiz beklentiyi karşılamak için gerekli tüm yeteneğe WAL sistemi sayesinde sahip, ancak bir PostgreSQL veri tabanında bu işlemin sorunsuz tamamlanacağını, veri tabanının her kapanışta tutarlı ve veri kayıpsız açılacağını söyleyebilmek için bazı konfigürasyonları incelemek gerekli. Bu yazıda crash recovery mekanizmasını etkileyecek parametreler, varlık sebepleri ve konfigürasyon opsiyonlarını inceleyeceğiz.

Öncelikle WAL yazma sistemini hatırlayalım; veri tabanındaki bir transaction ile ilgili bilgiler WAL dosyasına yazılmadan öncelikle WAL buffers ismini verdiğimiz memory alanına yazılır. Bunu WAL yazma işlemini hızlandırmak için yapılan bir cache uygulaması olarak düşünebiliriz. Ancak kullanıcı transaction’ı commit ettiğinde, bu işleme ilişkin tüm bilginin WAL buffers’dan disk üzerindeki WAL dosyalarına yazılması şarttır ve bu diske yazma tamamlanmadan kullanıcıya commit sonucu döndürülmez. İşte crash recovery’de commit edilmiş verinin kaybedilmemesini sağlayan bu WAL yazma işlemidir, çünkü transaction’a ilişkin değişiklikler tabloların disk üzerindeki dosyalarına henüz aktarılmamış olsa bile, bilgilerin diskteki WAL dosyalarında bulunması sayesinde veri kaybı yaşanmaz. Ancak söz konusu WAL yazma işlemindeki tek cache faktörü WAL buffer değildir. Aşağıda görüldüğü gibi bu yazma işlemi sistem konfigürasyonuna bağlı olmak üzere farklı cache mekanizmalarından geçer.

WAL Buffer

OS Cache

RAID Controller Cache

Disk Drive Cache

WAL Segment

Bir WAL write işleminin OS cache katmanından geçerek diske yazmanın tamamlandığından emin olmak için PostgreSQL “fsync” ya da benzer sistem çağrılarından birini kullanır. (Sistem çağrısı -system call- bir programın işletim sistemi çekirdeğinden hizmet talep etmesi olarak tanımlanabilir.) Ancak bu mekanizma PostgreSQL’de bir parametre ile açılıp kapanabilmektedir. Bu parametrenin ismi de yine “fsync” dir. Varsayılan olarak “on” değeri ile açık olan bu mekanizma eğer “fsync=off” şeklinde kapatılırsa, PostgreSQL WAL write işlemlerini “fsync” veya benzeri bir validasyon sağlamadan işletim sistemine gönderecek ve diske yazıldığını garanti edemeyecektir. Peki bu neden tercih edilebilir? Veri tabanımızdaki veriler kolaylıkla yeniden oluşturulabiliyorsa ve mevcut veri tabanımızda çok yüksek yoğunluklu işlemleri daha performanslı yapmak istiyorsak “fsync=off” konfigürasyonunu değerlendirebiliriz. Ancak ani bir kapanmada veri tabanımızın tutarlı açılamayabileceğini, veri bozulması ve veri kaybı ihtimalinin olduğunu göz önünde bulundurmalıyız. “fsync=off” durumunda veri tabanı işlem sürelerinin yaklaşık ne kadar değişeceğini görmek için aşağıdaki küçük testi inceleyelim. “fsync=on” durumda yaklaşık 70 sn. süren INSERT işleminin, “fsync=off” set edildiğinde 38 sn. ye düştüğüni görüyoruz. UPDATE işlemi ise 5 sn. den 3.6 sn. ye düşüyor. (Farklı koşullarda ve farklı test yöntemlerinde etkinin değiştiğini görebilirsiniz.)

postgres=# CREATE TABLE Customers (
    ID  serial ,
    Name varchar(50) NOT NULL,
    Phone varchar(15) NOT NULL,
    PRIMARY KEY (ID)
);
CREATE TABLE

postgres=# \timing
Timing is on.


-- fsync=on

postgres=# INSERT INTO Customers( Name, Phone)
SELECT  left(md5(random()::text),10),
left(md5(random()::text),10)
FROM generate_series(1,10000000);
INSERT 0 10000000
Time: 69956.565 ms (01:09.957)

postgres=# UPDATE Customers set Phone = 1111111111 where ID<1000000;
UPDATE 999999
Time: 4942.934 ms (00:04.943)

postgres=# DELETE FROM Customers where ID>9000000;
DELETE 1000000
Time: 818.573 ms


-- Drop and recreate table
-- fsync=off

postgres=# INSERT INTO Customers( Name, Phone)
SELECT  left(md5(random()::text),10),
left(md5(random()::text),10)
FROM generate_series(1,10000000);
INSERT 0 10000000
Time: 37993.590 ms (00:37.994)

postgres=#  UPDATE Customers set Phone = 1111111111 where ID<1000000;
UPDATE 999999
Time: 3636.351 ms (00:03.636)

postgres=# DELETE FROM Customers where ID>9000000;
DELETE 1000000
Time: 1019.045 ms (00:01.019)

Yukarıda “fsync ya da benzeri sistem çağrıları” olarak ifade ettiğimiz yöntemi belirleyen parametre ise wal_sync_method parametresidir. Bu parametrenin alacağı değerlere şu linkten erişebilirsiniz. İşletim sistemi platformuna göre varsayılan değer değişir. Örneğin varsayılan değer Linux işletim sisteminde fdatasync, Windows’da ise open_datasync‘tir. Bu değeri değiştirmek için tüm bu sistem çağrılarının nasıl çalıştıklarını iyi anlamak ve geçerli/bilinçli bir sebebe sahip olmak gerekli. Aksi durumda varsayılan değerin değiştirilmesini önermiyoruz.

WAL yazma mekanimasında farkında olmamız gereken bir diğer katman ise RAID controller cache katmanıdır. Sistemimizde, üzerinde cache olan RAID kartı kullanıyorsak tüm I/O işlemleri bu cache üzerinden geçmektedir. Cache yöntemi olarak “Write-Back Cache” kullanılıyorsa, WAL write işlemi RAID controller cache’e yazılıp, disk drive üzerine henüz yazılmadan PostgreSQL’e yazma işleminin tamamlandığı bilgisi dönmektedir. (Bu noktada RAID cache yöntemleri hakkında daha fazla bilgi almak için bu yazıyı okumanızı tavsiye ederiz. ) Write-Back cache kullanılacaksa RAID kart üzerinde BBU (Battery Backup Unit) ismi verilen pillerin olması çok önemlidir. Bu piller sayesinde güç kesintisi durumunda RAID controller cache üzerindeki veriler hemen kaybolmaz ve güç geri gelene kadar cache BBU yardımıyla ayakta kalır. İşte bu katmandaki konfigürasyon da PostgreSQL veri tabanımızın WAL tutarlılığını, yani başarılı bir “crash recovery” işleminin garanti edilip edilmediğini belirleyen bir diğer etkendir. RAID controller katmanında olduğu gibi disk drive katmanında da cache kullanımı söz konusu olabilir. Bu cache konfigürasyonunu işletim sistemleri üzerinden sorgulayabilir ve kapabiliriz. Örneğin Linux’de SATA diskler için “hdparm”, SCSI diskler için “sdparm” komutları kullanılabilir. Daha detaylı bilgiye şu linkten erişebilirsiniz.

WAL yazma işlemini etkileyen bir başka parametre ise “full_page_writes” parametresidir. PostgreSQL’de checkpoint tetiklemesi ile üzerinde değişiklik yapılacak olan page’lerin (veri blokları) değişiklik yapılmadan önceki halinin tamamı WAL üzerine yazılır. Bu sayede page üzerine yazma esnasında yaşanacak bir crash durumuna karşı, page’in orijinal versiyonu WAL üzerinde yedeklenmiş olur. Bu yedekleme yapılmaz ise veri tabanı açılışında page üzerinde bir kısmı güncellenmiş bir kısmı güncellenmemiş bir veri seti, yani bir corruption olması muhtemel olacaktır. Bu parametreyi full_page_writes=off ile kapatmak, veri tabanı üzerindeki işlemlerde performans getirisi sağlasa da, crash recovery işleminin sağlıklı yapılamama riskini yani crash durumunda veri kaybı ihtimalini ortaya çıkaracaktır.

Bu noktada, testimizi tekrar ederek “full_page_writes=off” durumunda kabaca ne kadarlık bir performans kazancı olacağını görelim. İşlemleri tekrarladığımızda INSERT işleminin “fsync=on” ve “full_page_writes=off” iken yaklaşık 51sn. sürdüğünü görüyoruz.

-- Drop and recreate table
-- fsync=on
-- full_page_writes=off

postgres=# INSERT INTO Customers( Name, Phone)
SELECT  left(md5(random()::text),10),
left(md5(random()::text),10)
FROM generate_series(1,10000000);
INSERT 0 10000000
Time: 51134.514 ms (00:51.135)

postgres=#  UPDATE Customers set Phone = 1111111111 where ID<1000000;
UPDATE 999999
Time: 3929.867 ms (00:03.930)

postgres=# DELETE FROM Customers where ID>7000000;
DELETE 1000000
Time: 933.309 ms

Son olarak, WAL yazma işlemini direk olarak etkileyen bir başka unsur, “Asenkron Commit” özelliğine bakalım. Bu özellik aslında varsayılan olarak senkron çalışan, yani WAL dosyasına yazma işlemi tamamlanmadan kullanıcıya commit sonucunu dönmeyen mekanizmayı değiştirir. “synchronous_commit=off” kullanılarak devreye alınan “Asenkron Commit” konfigürasyonu ile WAL kayıtlarının her commit’te yazılması beklenmez. WAL yazma işlemi maksimum “wal_writer_delay” x 3ms olmak üzere ertelenebilir. “Asenkron Commit” aslında, crash durumunda en son işlemlerin kaybolması riskini kabul ederek, işlemlerin daha hızlı tamamlanmasına olanak tanıyan bir başka seçenektir. “synchronous_commit” parametresini on ve off dışında set edebileceğimiz farklı değerler de mevcuttur ve aşağıda görüldüğü üzere parametrenin değerleri, eğer mevcut ise standby veri tabanındaki veri tutarlılığını da kontrol etmektedir.

  • off = primary’de tutarlı commit yok
  • local = primary’de tutarlı commit var
  • remote_write = local + PostgreSQL crash sonrası standby’da tutarlı commit
  • on (varsayılan) = remote_write + OS crash sonrası standby’da tutarlı commit (senkron replication kullanılıyorsa)
  • remote_apply = on + standby sorgu tutarlılığı

Sonuç olarak, incelediğimiz tüm bu konfigürasyon seçenekleri ve parametreler, PostgreSQL veri tabanımızın crash recovery davranışını etkiliyor. Bu yazıda, crash sonrası veri tabanlarımızın tutarlı açılmasını garanti etmek için hangi etkenleri incelememiz gerektiğini gördük ve aynı zamanda performansın tutarlılıktan daha önemli olduğu durumlarda da hangi parametreler ile bunu sağlayabileceğimizi inceledik.

Bir yanıt yazın

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