- Jepsen testlerinde Amazon RDS for PostgreSQL Multi-AZ kümelerinin, tüm düğümler genelinde en güçlü yalıtım seviyesi olan Snapshot Isolation'ı korumadığı örnekler doğrulandı
- Temel neden, primary üzerinde transaction'ların görünür olma sırasının bellek içi kilitlerle belirlenmesine karşılık secondary üzerinde WAL sırasının izlenmesi ve bu iki sıranın birbirinden sapabilmesi
- Hata enjeksiyonu veya failover olmadan,
gp3depolama vedb.m6id.largeinstance kullanılan koşullarda bile yaklaşık 150 write TPS / 1600 read-only TPS altında birkaç dakikada bir G-nonadjacent cycle ortaya çıktı - Anomali Long Fork kapsamına giriyor; AWS'in desteklediği PostgreSQL 13.15'ten 17.4'e kadar test edilen tüm sürümlerde görüldü, Short Fork/Write Skew ise gözlemlenmedi
- Güvenliğin kritik olduğu transaction'lar, read-only secondary kullanıldığında yürütme sırasını farklı görebileceğinden yalnızca writer endpoint kullanma veya en az 1 write içeren bir yöntem değerlendirilmelidir
Long Fork nedenine dair güncelleme
- AWS'ten Sergey Melnik ile HN yorum katılımcıları matashii ve Ants Aasma, PostgreSQL kümelerindeki Long Fork nedenini belirledi
- PostgreSQL primary, transaction'ları görünür kılma sırasını bellek içi kilitlerle belirler
- Secondary, transaction'ları Write-Ahead Log (WAL) içindeki sıraya göre görünür kılar
- Kilit sırası ile WAL sırası farklılaştığında primary ve secondary, transaction'ların görünen sırasını farklı görebilir
- Bu davranış 2013 tarihli bir PostgreSQL e-posta listesi yazısında ele alınmıştı; Melnik ayrıca AWS blogunda PostgreSQL kümelerinde ve read replica'larda transaction visibility konusunu açıklayan bir yazı yazdı
- Jepsen, AWS ve PostgreSQL'in bu sorunu düzeltme çalışmalarıyla birlikte belgelemesini öneriyor
RDS for PostgreSQL'in yalıtım seviyeleri ve yapısı
- PostgreSQL genel amaçlı, açık kaynaklı bir SQL veritabanıdır ve MVCC ile üç transaction yalıtım seviyesi sunar
Read UncommittedveRead Committedikisi de Read Committed olarak çalışırRepeatable Read, gerçekte Repeatable Read değil Snapshot Isolation sağlarSerializable, Serializability sağlar
- Amazon RDS for PostgreSQL, yönetilen PostgreSQL kümeleri sunan bir AWS servisidir
- Provisioning, depolama yönetimi, replikasyon, yedekleme, yükseltme vb. işlemleri otomatikleştirir
- Multi-AZ deployments, veritabanı düğümlerini birden çok Availability Zone'a dağıtarak ilişkili arıza olasılığını azaltır
- RDS, hem primary hem de en az 1 secondary instance üzerinde transaction dayanıklılığı sağlandıktan sonra yanıt vermek için senkron replikasyon kullanır
- Kullanıcılara PostgreSQL wire protocol konuşan iki URL sağlanır
- primary endpoint: read-write transaction'lar için
- reader endpoint: read-only transaction'lar için
- Primary endpoint tüm PostgreSQL yalıtım seviyelerini destekler, ancak secondary Serializable'ı desteklemez
- Tüm düğümlerde kullanılabilen en güçlü yalıtım seviyesi, PostgreSQL'in
Repeatable Readadını verdiği Snapshot Isolation'dır
Test tasarımı
- Jepsen, PostgreSQL için test kütüphanesini Amazon RDS for PostgreSQL'e uyarladı ve küçük bir wrapper program kullandı
- Her test turunda AWS'in CreateDBCluster API'siyle bir RDS kümesi provision edildi
- Depolama
gp3 - Instance
db.m6id.large
- Depolama
- Test çalıştırmak için 1 EC2 düğümü başlatıldı ve RDS kümesinin main endpoint'i ile read-only endpoint'i sağlandı
- Hata enjeksiyonu yapılmadı ve failover da tetiklenmedi
- Ana workload, benzersiz tamsayı listeleriyle çalışan transaction'lardan oluşuyordu
- Her liste tek bir row'da saklandı ve virgülle ayrılmış değerler içeren bir
TEXTalanı olarak encode edildi - Transaction'lar listeleri primary key ile okudu veya
CONCATile listeye benzersiz bir tamsayı append etti
- Her liste tek bir row'da saklandı ve virgülle ayrılmış değerler içeren bir
- Bu workload sayesinde Elle checker, transaction'lar arası veri akışı bağımlılıklarını çıkarıp graf cycle'ları bularak çeşitli yalıtım seviyelerini doğrulayabiliyor
G-nonadjacent cycle gözlemi
- Normal koşullarda ve orta düzey eşzamanlılıkta bile Amazon RDS for PostgreSQL 17.4 birkaç dakikada bir G-nonadjacent cycle gösterdi
- Bir 2 dakikalık test çalıştırması yaklaşık 150 write TPS ve 1600 read-only TPS gerçekleştirdi ve 4 transaction'lı bir cycle içeriyordu
- Örnek cycle dört transaction'dan oluşuyor:
T1,T2,T3,T4T1, row 89'a9append ederek[4 9]listesini oluşturdu veT2bunu gözlemlediT3, row 90'a11append ederek[11]listesini oluşturduT4, row 90'a3append etti ve sonuç liste[11, 3]ü okuyarakT3ün version'ının üzerine yazdıT2, row 89'daT1in append işlemini gözlemledi ancak row 90'daT3ün append işlemini göremedi- Buna karşılık
T4, row 90'daT3ün append işlemini gözlemledi ancak row 89'daT1in append işlemini kaçırdı
- Bu cycle, birbirine komşu olmayan read-write dependency içerdiğinden Snapshot Isolation ihlali olan bir G-nonadjacent cycle'dır
- Standart PostgreSQL'in
Repeatable Readseviyesinde böyle bir davranışın oluşmaması gerekir ve Jepsen bunu standart PostgreSQL'de gözlemlemedi
Snapshot Isolation ile neden çelişiyor?
- Snapshot Isolation'da her transaction, başlangıç timestamp'i
sanındaki veritabanı snapshot'ı üzerinde çalışıyormuş gibi görünmelidir - Transaction'ın etkileri daha sonraki commit timestamp'i
canında diğer transaction'lara görünür olur - Örnek cycle'daki gözlemler timestamp ilişkileriyle yazıldığında birbirleriyle çelişir
T2,T1in append işlemini okuduğu içinT2nin başlangıcıT1in commit'inden sonra olmalıdır:c1 < s2T2,T3ün append işlemini gözlemlemediği içins2 < c3T4,T3ün üzerine yazıp onu gözlemlediği içinc3 < s4T4,T1in append işlemini gözlemlemediği içins4 < c1
- Bu ilişkilerin hepsi aynı anda geçerli olamaz; dolayısıyla Snapshot Isolation'ın timestamp modeliyle çelişir
Long Fork ve sürümlere göre sonuçlar
- Bu cycle aynı zamanda bir Long Fork örneğidir
- Birinci ve ikinci transaction tek bir mantıksal durum fork'u oluşturur
- Üçüncü ve dördüncü transaction ikinci fork'u oluşturur
- İki fork farklı row'ları günceller ama birbirlerinin etkilerini gözlemleyemez
- Short Fork, yani Write Skew gözlemlenmedi
- Bu sonuç, Amazon RDS for PostgreSQL'in Snapshot Isolation'dan biraz daha zayıf olan Parallel Snapshot Isolation sağlıyor olabileceğine işaret ediyor
- G-nonadjacent anomalileri; yalnızca write-read edge'leriyle bağlanan durumlar ve 4'ten fazla transaction içeren durumlar dahil olmak üzere çeşitli biçimlerde ortaya çıktı
- AWS'in desteklediği en eski sürüm olan PostgreSQL 13.15'ten en yeni sürüm olan 17.4'e kadar test edilen tüm sürümlerde aynı tür anomali oluştu
Kullanıcıların kontrol etmesi gerekenler
- Long Fork ve diğer G-nonadjacent cycle'lar bulunduğundan Amazon RDS for PostgreSQL Multi-AZ kümeleri Snapshot Isolation garantisi vermez
- Bu açıdan RDS for PostgreSQL Multi-AZ kümeleri, önceki Jepsen testlerinde Strong Snapshot Isolation sağlıyor gibi görünen tek düğümlü PostgreSQL'den daha zayıf güvenlik semantiği sunar
- Kullanıcılar transaction yapılarının Long Fork'a açık olup olmadığını inceleyebilir veya amaçladıkları invariant'ların korunup korunmadığını deneylerle doğrulayabilir
- Read transaction'ları, transaction yürütme sırası hakkında diğer transaction'lardan farklı sonuçlar görebilir
- Anomali, read-only secondary'ye yapılan sorgularla ilişkili göründüğünden Snapshot Isolation'ı geri kazanmak için şu yöntemler olasıdır
-
Yalnızca writer endpoint kullanın
- Güvenliğin kritik olduğu tüm transaction'lara en az 1 write ekleyin
- Jepsen'in doğrulaması deneysel bir yaklaşımdır; hatanın varlığını kanıtlayabilir ama yokluğunu kanıtlayamaz
- Bu rapor, RDS for PostgreSQL davranışını ayrıntılı biçimde inceleyen bir sonucun değil, ön keşif niteliğindeki bir çalışmanın ürünüdür
-
1 yorum
Hacker News yorumları
Keşke yazılım dünyasındaki yazılar daha sık şöyle olsa: “Amazon RDS for PostgreSQL, PostgreSQL veritabanlarının yönetilen örneklerini sunan bir Amazon Web Services (AWS) hizmetidir. Amazon RDS for PostgreSQL multi-AZ cluster’larının, tüm endpoint’lerde desteklenen en güçlü tutarlılık modeli olan snapshot isolation’ı ihlal ettiğini gösteriyoruz…”
Doğrudan, öz ve süssüz; bu yönüyle diğer STEM alanlarında araştırma sonuçlarının paylaşılma biçimine benziyor. Bir zamanlar konuyu mem’lerle açıklayan esprili blog yazılarını severdim, ama artık sade ve basit yazıları özlüyorum.
Çok derin teknik yazılar yazınca neredeyse hiç beğeni ve yorum gelmezdi; hatta bir Staff Engineer “hedef kitleyi daha dar tutman daha iyi olur” demişti. Buna karşılık, erken dönem Kubecost’u test ederken önerilerinin maliyet tasarrufunun küçük olduğunu ve konteyner performansı sorunları yaratabileceğini yazmıştım; CPU throttling ve cgroups’u ele alan oldukça teknik bir yazı olmasına rağmen içine mem koyunca insanlar buna bayıldı.
Daha sonra C ile küçük bir Python harici kütüphanesi oluşturup buna ctypes ile eriştiğim, stack/heap tahsisini karşılaştırdığım daha kuru bir yazıya da mem ekledim; benzer sonuç aldım. Bu gidişat hoşuma gitmiyor ama geniş bir okur kitlesine ulaşmak için bundan kaçınmanın başka bir yolunu da pek bilmiyorum. Jensen böyle bir kitleyi hedeflememişti; titiz ve saf bir yazım tarzı takdiri hak ediyor.
Başlıkta yok ve yazıda da çok açık değil; bu sorun RDS’in görece yeni bir özelliği olan multi-AZ cluster ile sınırlı. Birçok kişinin aşina olduğu multi-AZ instance’tan farklı.
Multi-AZ instance, birincil DB’nin başka bir erişilebilirlik alanındaki ikincil DB’ye senkron kopyalandığı ve birincil başarısız olursa RDS’in ikincile failover yaptığı eski bir özellik.
Multi-AZ cluster’da iki ikincil vardır ve transaction bunlardan en az birine senkron kopyalanır. İkincillerden biri başarısız olduğunda veya performansı düştüğünde multi-AZ instance’a göre daha dayanıklıdır; ayrıca ikincillere salt okunur erişim de mümkündür.
Ancak multi-AZ cluster’ın içinde PostgreSQL’in yerleşik özellikleri olmayan ek bir sihir muhtemelen daha vardır; sanırım Jepsen testinde bu yüzden başarısız oldu.
Ancak PostgreSQL’de bu paterne benzer bir sorunu mümkün kılan bir kusur hâlâ var. İstemci commit sırasında ortadan kaybolursa, replike edilmemiş transaction hemen görünür hale gelir. Örnekte T1 ayrılmış liderde gerçekleşir ve commit sırasında bağlantı koparsa, T2 de ayrılmış node’da gerçekleşirse ve T3/T4 daha sonra yeni liderde gerçekleşirse aynı sonuç görülebilir. Ama bu, testte fault injection yapılmadığı açıklamasıyla pek uyuşmuyor.
Düzeltme: Bu paternin replika ile birincil node arasındaki commit sırası uyumsuzluğu ile açıklandığını yazıda görmemişim. Bu sorunu düzeltmenin bir yolunu daha önce sunmuş olduğum için biraz utanç verici.
İyi bir inceleme. Günümüzde yazılım geliştiricilerin çoğu transaction kavramının kendisini bile iyi bilmiyor; farklı transaction modellerini ise daha da az biliyorlar. Hatta “senior developer” diye anılan CRUD geliştiricileri arasında, veritabanı transaction’larından hiç anlamayan birini görmüştüm.
Gerçekte trafik ölçeği olduğunda ve yazılım önemsiz olmayan problemleri çözdüğünde, transaction’lar ve transaction modelleri performans ve hatasız kod için çok önemlidir.
Örneğin büyük bir projede, uzun analizlerden sonra SQL Server’ın varsayılan Read Committed düzeyinden Read Committed Snapshot Isolation’a geçtik; lock çekişmesi büyük ölçüde ortadan kalktı ve kullanıcılar bundan çok memnun kaldı. O projedeki yazılım mühendisleri transaction’ları yoğun kullanıyordu ama temel konuları öğretmeden önce transaction modellerini ya da lock’ları hiç bilmiyorlardı.
Çoğunlukla perakende alanında çalıştığım için yarış durumu benzeri hatalarla dolu sistemleri sık görüyorum; bu isolation level’ların çok yardımcı olabileceği yerler olduğu için bu daha da üzücü.
Ancak bu tür durumları daha çok startup mühendislerinde gördüm; büyük şirketlerdeki tipik Oracle/MSSQL geliştiricilerinin en azından temelleri doğru olduğu için onları oldukça yüksek değerlendiriyorum.
Kariyerim boyunca birkaç kez bu yaklaşımın gerçekten kötü sonuçlar doğurduğunu gördüm.
Ancak bu geçişte dikkat edilmesi gereken şey, bloklayıcı okumalara dayanan tüm kodların bozulacağıdır. Örneğin
select with existsgibi kodların açık lock’lar veya başka yöntemlerle yeniden yazılması gerekir.Eski şirketimde yedekleme betiğindeki
pg_dumpkomutunu değiştirip paralel worker’lar (-jbayrağı) kullanmaya başladığımızda, restore sırasında duplicate key hataları ve foreign key constraint hataları gibi tutarsızlığa işaret eden hataları nadiren görüyorduk.O sırada AWS’ye ve PostgreSQL mailing list’e rapor etmeye çalıştık ama kolayca yeniden üretemediğimiz için ilerleme olmadı; sonunda vazgeçip tek thread’li dump’a geri döndük. O zaman gördüğümüz olgunun bu sorunla ilişkili olup olmadığını merak ediyorum.
Bu yazıyı okuyunca gerçek etkinin, aynı satıra yazmanın hemen ardından hızlı bir okuma yapıldığında eski verinin dönebileceği şeklinde olduğu anlaşılıyor. Yazma transaction’ı tamamlandı olarak işaretleniyor ama multi-AZ RDS instance’ının dağıtık katmanının tamamı bütünüyle güncellenmeden önce aynı satır hemen okunursa, satır henüz yokmuş gibi görünebilir ya da sütun tam güncellenmediği için eski değer dönebilir.
PostgreSQL’in snapshot yaklaşımı gereği, çok baytlı sütun tiplerinin yalnızca bazı baytlarının güncellenip anlamsız değerler okunması anlamına gelmiyor gibi görünüyor.
Sonuçta zamanla yakınsayan bir race condition gibi duruyor. Yoksa “long fork”taki sonraki transaction’ların normal koşullarda bile sonsuza dek tamamlanmayabileceği anlamında okuyan var mı merak ediyorum.
“Bu çalışma Jepsen tarafından herhangi bir ücret almadan bağımsız olarak yürütüldü” ifadesi, RDBMS tarafında çıkarı olanların iyi bir günde bile görmek istemeyeceği türden bir şey. İçeride endişe dolu birkaç e-posta dönmüş olmalı. Her zamanki gibi aphyr’a saygılar.
Bunun multi-instance upstream PostgreSQL kümelerinde sorun olup olmadığı tamamen net değil. AWS’nin küme ayarlarında bir şey yapıp yapmadığını ya da bu davranışı tetikleyen bir yama eklediğini varsaymak doğru mu, merak ediyorum
PostgreSQL replikasyonunda genel olarak farklı yöntemler var ve sonuçlar da değişiyor. Örneğin Bin Wang’in Patroni raporu var: https://www.binwang.me/2024-12-02-PostgreSQL-High-Availabili...
Burada da bulunan şey, PostgreSQL’in şu anda birincil düğüm ile replikalar arasında tutarlı snapshot davranışı sağlamadığı. Muhtemelen salt okunur işlem T2 ikincil düğümde, değişiklik yapan işlemler T1/T3/T4 ise birincil düğümde çalıştı.
Arka plana bakarsak, ikincil PostgreSQL düğümündeki snapshot, hangi işlemlerin görünür olduğuna karar verirken işlem kalıcılık sırasına, yani WAL’daki commit kaydının konumuna dayanır. Buna karşılık birincil düğümdeki görünürlük sırası, ilgili işlemi kabul eden backend’in işlemin tamamen commit edildiği bildirimini ilk aldığı an ve sonrasında commit işaretini koyduğu an tarafından belirlenir.
Birincil ve ikincil düğümlerin her birinde bağlı backend’ler arasındaki commit sırası tutarlıdır, ancak birincil ile ikincil arasındaki commit sırası bir miktar farklılaşabilir. Bunu iyileştirmeye yönelik çalışmalar sürüyor, fakat hâlâ çok devam eden bir aşamada.
AWS, PostgreSQL’i yamalayıp iki instance’a replike ediyor ve ikisinden biri değişikliği onayladığında bunu yeterli sayıyor gibi görünüyor. Bu onayın ne zaman gerçekleştiği kamuya açık bilgi değil.
Kişisel olarak PostgreSQL için drbd gibi dosya sistemi düzeyi replikasyonun daha iyi olduğunu düşünüyorum. Eski tarz AWS Multi-AZ instance’ları muhtemelen bu yöntemi kullanıyordu. Ancak throughput düşer ve ikincil instance’tan okuma yapılamaz.
Özellikle şu nokta: https://youtu.be/fLqJXTOhUg4?t=434
Gönderilen başlık asıl noktayı soruyor. RDS for PostgreSQL 17.4 snapshot isolation’ı doğru şekilde uygulamıyor.
Başlığın sisteme fazla sert mi davrandığı, fazla dostça mı olduğu, bulunan on küsur sorun arasından en anlamlı olanı içerip içermediği, Jepsen’in veritabanı güvenliği sonuçları için dürüst bir aracı olma standardına göre adil olup olmadığı, 10 yıl sonra insanlar hâlâ bağlantı verdiğinde ama artık güncel sürümler için geçerli olmadığında nasıl yorumlanacağı gibi tartışmalar epey hararetli hâle gelebilir.
Birkaç sinir bozucu denemeden sonra, tüm rapor başlıklarını “Jepsen: ” biçiminde başlatma politikasıyla bu sorundan kaçınıyoruz. HN daha açıklayıcı veya daha renkli bağlantı metni istiyorsa elbette kendisi seçebilir.
Yine de bunu işlem garantilerinin Chuck Norris’i sayılabilecek Kyle Kingsbury yazdığı için AWS’nin yanıt vermesi ya da açıklama yapması gerekiyor. PostgreSQL için RDS’deki iki seçenekten yalnızca biri olan multi-AZ cluster için geçerli görünüyor olsa bile bu böyle. Multi-AZ dağıtımda bir ya da iki standby DB instance’ı olabilir; burada iki standby DB instance’lı yapılandırmadan söz ediliyor.
AWS belgelerinde böyle bir taahhüt yok. RDS’nin 5494 sayfalık kılavuzu da her motorun parametre belgelerinde isolation veya serializable’dan ancak çok az bahsediyor.
Multi-AZ cluster’ın global okuma tutarlılığı hakkında da bir şey yok. Yarı senkron replikasyon olduğu için writer’ın bir standby’nin log kaydı onayını beklediği söyleniyor, ancak iki reader farklı snapshot’lar üzerinde olabilir.
[1] - "New Amazon RDS for MySQL & PostgreSQL Multi-AZ Deployment Option: Improved Write Performance & Faster Failover" - https://aws.amazon.com/blogs/aws/amazon-rds-multi-az-db-clus...
[2] - "Amazon RDS Multi-AZ with two readable standbys: Under the hood" - https://aws.amazon.com/blogs/database/amazon-rds-multi-az-wi...
Geliştirici snapshot isolation olduğunu varsaymışken Amazon RDS for PostgreSQL gerçekte yalnızca parallel snapshot isolation sağlıyorsa, özellikle okuma replikası endpoint’ini kullanan multi-AZ yapılandırmalarda ne tür güvenlik hataları veya uygulama düzeyi hatalar ortaya çıkabileceğini merak ediyorum
git pushbenzeri bir akışı düşünebilirsiniz. Bir transaction başlatır, mevcut durumu okur, beklenen durumla eşleşip eşleşmediğini kontrol eder, yeni durumu yazar ve yeni durum hash’iyle birlikte commit edersiniz. Talihsiz bir durumda, geçerli hiçbir durumla eşleşmeyen bir commit hash’i oluşabilirBöyle bir şeyi akıl yürütmenin zor olması başlı başına, sorundan kaçınmayı zorlaştırıyor. Bu yüzden en kolay çözüm, okumalara koşullu yazmalarda “yalnızca writer endpoint’i kullanırsanız snapshot isolation’ı geri kazanmanız mümkün olabilir” yaklaşımına yakın görünüyor
Ancak “yalnızca writer endpoint’i kullanma” yönteminin, özellikle erişilebilirlik kaybı durumlarında test edilmemiş olması şaşırtıcı
User1 yorum yapıyor, ardından User2 yorum yapıyor; sonra User1 ayrı bir transaction’da yalnızca 1 yorum olduğunu doğrulayıp rozeti alıyor. User2 de ayrı bir transaction’da aynı kontrolü yapıp yalnızca kendi yorumunu görerek rozeti alabiliyor
Snapshot isolation altında bu mümkün değildir. Ayrı transaction’lardan en az biri 2 yorumu görmek zorundadır
Parallel snapshot hakkındaki özgün makale de okumaya değer: https://scispace.com/pdf/transactional-storage-for-geo-repli...
“Bu durum test edilen tüm sürümlerde, 13.15’ten 17.4’e kadar ortaya çıktı” cümlesini görünce, major sürümü yükseltmenin yanlış bir tercih olup olmadığından endişelendim; ama öyle değil gibi. Bu bir regression değil, daha çok bir özellik isteği ya da eski bir hataya benziyor