Bir sipariş bir veritabanına, ödeme ise başka bir veritabanına yazılır. İkisi de gerçekleşmeli ya da hiçbiri gerçekleşmemelidir. Her veritabanı kendi payını atomik olarak commit edebilir, ama yerel hiçbir şey birinin commit edip diğerinin çökmesini engellemez. İki aşamalı commit (2PC) bu sorun için klasik protokoldür; XA standardı aracılığıyla çoğu ilişkisel veritabanına yerleşiktir ve bugün dağıtık SQL veritabanlarının içinde de kullanılır.
Ünlü bir zayıflığı da vardır: Koordinatör yanlış anda çökerse katılımcılar, kendi başlarına karar verme imkânı olmadan kilitleri tutarak takılıp kalabilir. Bu yazı protokolü bir sekans diyagramıyla adım adım anlatıyor; her arıza senaryosunu, gerçek sistemlerin onu nasıl uyguladığını ve saga ya da outbox'ın ne zaman daha iyi bir araç olduğunu da ele alıyor.
Roller
- Koordinatör ya da işlem yöneticisi (transaction manager): Protokolü yürütür ve sonuca karar verir. Uygulama sunucunuzun içindeki bir kütüphane, ayrı bir servis ya da dağıtık bir veritabanının bir bileşeni olabilir.
- Katılımcılar ya da kaynak yöneticileri (resource manager): İşlemin bir parçasını tutan veritabanları ya da kuyruklar.
Her taraf kalıcı bir log tutar. Log yazmaları bir uygulama ayrıntısı değildir; protokolün çökmelere dayanmasını sağlayan şey onlardır.
Protokol
1. aşama: prepare.
- Uygulama, tek bir global işlem içinde her katılımcıdaki işini yapar, ardından koordinatörden commit etmesini ister.
- Koordinatör her katılımcıya PREPARE gönderir.
- Her katılımcı, bundan sonra ne olursa olsun commit edebileceğinden emin olur: İşlemin değişikliklerini ve bir prepare kaydını kalıcı loguna yazar, kilitlerini tutar ve YES oyu verir. Commit edemiyorsa, örneğin bir kısıt ihlal edildiği için, NO oyu verir ve geri alır.
2. aşama: commit ya da abort.
- Tüm oylar YES ise koordinatör kendi loguna bir COMMIT kararı yazar. Bu yazma commit noktasıdır: Bu andan itibaren, tüm makineler bir milisaniye sonra çökse bile işlem commit edilmiştir.
- Koordinatör her katılımcıya COMMIT gönderir. Her biri commit eder, kilitlerini bırakır ve onay verir.
- Herhangi bir katılımcı NO oyu verirse ya da zamanında yanıt vermezse koordinatör ABORT kararı verir ve bunun yerine ROLLBACK gönderir.
YES oyu bir sözdür. YES oyu verdikten sonra katılımcı tek başına karar verme hakkından vazgeçmiştir: Commit etmesi söylenirse, yeniden başlatmadan sonra bile commit etmek zorundadır ve kendi başına geri alamaz, çünkü koordinatör commit etmeye çoktan karar vermiş olabilir.
Maliyet
N katılımcı için başarılı bir commit, koordinatörden iki gidiş dönüş ve katılımcı başına dört mesaj gerektirir: PREPARE, oy, COMMIT ve onay. Her katılımcı logunu iki kez diske zorlar, prepare'de ve commit'te; koordinatör de kararını bir kez zorlar. Kilitler tüm süre boyunca tutulur; dolayısıyla diğer her katılımcının çakışan işleri ne kadar süre bloke edeceğine en yavaş katılımcı karar verir.
İşler ters gittiğinde ne olur
| Arıza | Ne zaman | Sonuç |
|---|---|---|
| Katılımcı çöker ya da zaman aşımına uğrar | Oy vermeden önce | Koordinatör işlemi iptal eder |
| Katılımcı NO oyu verir | 1. aşama | Koordinatör işlemi iptal eder |
| Katılımcı çöker | YES oyu verdikten sonra | Yeniden başladığında prepare kaydını bulur ve sonucu koordinatöre sorar; bu arada kilitlerini tutar |
| Koordinatör çöker | Kararı yazmadan önce | Hazırlanmış katılımcılar bekler. Kurtarmada, logunda karar olmadığı için koordinatör iptal eder |
| Koordinatör çöker | COMMIT'i yazdıktan sonra | Kurtarmada logunu okur ve COMMIT'i yeniden gönderir |
| Ağ bölünmesi | Bir katılımcı YES oyu verdikten sonra | O katılımcı koordinatöre ulaşabilene kadar bloke olur |
Önemli olan satırlar, bir katılımcının YES oyu verdiği ve kararı duyamadığı satırlardır. Commit edemez, çünkü başka bir katılımcı NO oyu vermiş olabilir. İptal edemez, çünkü koordinatör commit etmiş olabilir. Koordinatöre ulaşılamadığı sürece kilitlerini tutarak bekler. O satırlara dokunan diğer her işlem de bekler. İnsanların 2PC'ye bloklayan bir protokol demesinin anlamı budur.
Pratikte takılıp kalan hazırlanmış işlemler
PostgreSQL katılımcı tarafını PREPARE TRANSACTION, COMMIT PREPARED ve ROLLBACK PREPARED ile uygular. Özellik varsayılan olarak kapalıdır (max_prepared_transactions 0'dır) ve belgeler, uygulamalar için değil harici işlem yöneticileri için tasarlandığı konusunda uyarır. Unutulmuş bir hazırlanmış işlem kilitlerini tutar ve VACUUM'un depolama alanını geri kazanmasını engeller.
Birini bulup çözmek şöyle görünür:
-- Prepared transactions waiting for a decision
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts
ORDER BY prepared;
-- Only after checking the coordinator's log for the real outcome:
COMMIT PREPARED 'order-7f3a';
-- or
ROLLBACK PREPARED 'order-7f3a';
Hazırlanmış bir işlemi asla tahminle çözmeyin. Koordinatör commit ettiyse ve siz bir katılımcıyı geri alırsanız, tam da 2PC'nin önlemek için var olduğu tutarsızlığı yaratmış olursunuz. MySQL de aynı mekanizmayı XA PREPARE, XA COMMIT ve XA RECOVER ile sunar.
Acıyı azaltan varyasyonlar
- Presumed abort (varsayılan iptal). Koordinatörde bir işleme dair kayıt yoksa yanıt iptaldir. Bu, iptal edilen işlemler için log yazmalarından tasarruf sağlar ve kurtarmayı basitleştirir; çoğu uygulama bunu kullanır.
- Salt okunur katılımcılar. Hiçbir şeyi değiştirmemiş bir katılımcı “read-only” oyu verip 2. aşamayı atlayabilir.
- Üç aşamalı commit. Katılımcıların koordinatör olmadan işi bitirebilmesi için bir pre-commit turu ekler, ama yalnızca mesaj gecikmeleri sınırlıysa ve ağ hiç bölünmüyorsa. Bu varsayımlar nadiren geçerlidir, bu yüzden 3PC çoğunlukla bir ders kitabı konusudur.
- Koordinatörü ve katılımcıları çoğaltın. Bloklanmanın gerçek çözümü, her rolü bir makinenin kaybına dayanan çoğaltılmış bir grup yapmaktır. Google'ın Spanner veritabanı tam da bu nedenle iki aşamalı commit'i Paxos grupları üzerinde çalıştırır; diğer dağıtık SQL veritabanları da aynı fikri Raft ile kullanır. Sonuca yine iki aşamalı commit karar verir; uzlaşma her tarafı erişilebilir tutar.
2PC ne zaman kullanılır, ne zaman kullanılmaz
2PC; tüm katılımcılar prepare adımını destekleyen veritabanları ya da broker'lar olduğunda, hepsini siz kontrol ettiğinizde, işlemler kısa olduğunda ve aralarında atomiklik ile izolasyona ihtiyaç duyduğunuzda uygundur. Başka ekiplerin sahip olduğu servisler arasında, prepare adımı olmayan HTTP API'leri arasında (bir ödeme sağlayıcısı tahsilatınızı hazırlanmış durumda tutmaz) ve dakikalar ya da günler süren iş süreçlerinde kötü çalışır.
| İki aşamalı commit | Saga | Transactional outbox | |
|---|---|---|---|
| Tutarlılık | Atomik; kilitler izolasyon sağlar | Nihai, telafi adımlarıyla | Bir veritabanı ile bir broker arasında nihai |
| Bloklanma | Evet, bir karar beklenirken | Hayır | Hayır |
| Gerektirdiği | Prepare'i destekleyen katılımcılar | Her adım için telafi mantığı | Bir veritabanı artı bir aktarıcı |
| Tipik kullanım | Sizin çalıştırdığınız veritabanları ya da shard'lar | Servisler arası iş akışları | Bir yazmadan sonra olayları güvenilir şekilde yayımlamak |
Saga deseni diyagramı telafiye dayalı alternatifi gösteriyor; transactional outbox deseni ise ekiplerin 2PC'ye uzandığı en yaygın durumu ele alıyor: bir veritabanına yazmak ve bir mesajı birlikte yayımlamak. Çoğu zaman en iyi çözüm yapısaldır: Birlikte değişmesi gereken verileri, veritabanı sharding diyagramında anlatıldığı gibi, tek bir veritabanına ya da tek bir shard'a koyun.
İki aşamalı commit diyagramı
Diyagram içeriği
- Application
- Coordinator
- Orders DB
- Payments DB
- phase 1: prepare
- phase 2: all votes YES
- commit()
- PREPARE
- YES (prepare record on disk)
- COMMIT
- ACK, locks released
- ACK
- ROLLBACK
- committed or aborted
Nasıl okunur:
- Dört yaşam çizgisi. Uygulama, koordinatör, sipariş veritabanı ve ödeme veritabanı. Zaman aşağı doğru akar.
- par parçası 1. aşamadır. PREPARE aynı anda iki katılımcıya da gider ve her biri prepare kaydını yazdıktan sonra bir oy döndürür.
- alt parçası 2. aşamadır. Üst dal normal durumdur: Tüm oylar YES'tir, koordinatör COMMIT'i loglar ve iki katılımcı da commit edip onay verir. Alt dal iptal yoludur.
- Not, belirsizlik penceresini işaretler: YES oyu ile karar arasındaki, katılımcıların kilit tuttuğu ve tek başına karar veremediği aralık.
- Markdown bloğu, yukarıdaki tablodaki arıza durumlarını listeler.
Dağıtık işlemleri dürüstçe çizmek
- Log yazmalarını gösterin. “Prepare kaydını yaz” ve “kararı yaz” adımları olmayan bir 2PC diyagramı, onu doğru kılan kısmı dışarıda bırakır.
- Commit noktasını işaretleyin. Sonuca tek bir mesaj ya da log yazması karar verir; onu gösterin.
- İşlerin bloke olduğu pencereyi çizin. Ekiplerin 2PC'den kaçınmasının nedeni odur; onu gizleyen bir diyagram yanıltır.
- Arıza yollarını kendi diyagramlarına koyun. Prepare'den sonra bir koordinatör çökmesi, kurtarma adımlarıyla birlikte kendi sekansını hak eder.
Gösterimin kendisi için sekans diyagramı örneği yaşam çizgilerini, parçaları ve adlandırmayı ele alıyor.
Archboard'da oluşturun
- Google ya da GitHub ile giriş yapın ve bir diyagram oluşturun.
- Sekans paketinden dört Yaşam çizgisi düğümü ekleyin: Application, Coordinator, Orders DB ve Payments DB.
- Mesajları yukarıdan aşağıya kenarlar olarak çizin: commit isteği, her veritabanına PREPARE, YES oyları, her veritabanına COMMIT ve onaylar.
- Prepare mesajlarının çevresine Sekans paketinden
paroperatörlü bir Parça, commit ve rollback dallarının çevresine dealtoperatörlü ikinci bir Parça ekleyin. - Belirsizlik penceresi için bir Not ve Temel paketinden arıza tablosunu içeren bir Markdown bloğu ekleyin.
Örnek 7 diyagram düğümü kullanıyor ve Free planın sınırları içinde kalıyor.
SSS
İki aşamalı commit'e neden bloklayan bir protokol denir? YES oyu vermiş bir katılımcı kendi başına ne commit edebilir ne de iptal edebilir. Koordinatör kararı göndermeden önce ulaşılamaz hâle gelirse katılımcı kilitlerini tutarak bekler; o satırlara ihtiyaç duyan her işlem de bekler.
İki aşamalı commit dağıtık işlemle aynı şey mi? Dağıtık bir işlemi commit etmenin en yaygın yoludur. Dağıtık bir işlem birden fazla kaynağa yayılır; iki aşamalı commit onun commit'ini atomik yapan protokoldür.
Üç aşamalı commit bloklanma sorununu çözer mi? Yalnızca nadiren geçerli olan varsayımlar altında: sınırlı mesaj gecikmeleri ve ağ bölünmesi olmaması. Pratik çözüm, Spanner'ın yaptığı gibi koordinatörü ve katılımcıları uzlaşma ile çoğaltmaktır.
İki aşamalı commit'i mikroservisler arasında kullanabilir miyim? Nadiren iyi sonuç verir. Servislerin bir prepare adımı sunması ve birbirleri için kilit tutması gerekir; bu da erişilebilirliklerini birbirine bağlar. Servisler arasında olağan seçimler saga'lar ve outbox desenidir.
İki aşamalı commit izolasyonu garanti eder mi? Atomikliği garanti eder. İzolasyon, katılımcıların kilitlerinden gelir; 2PC bu kilitleri karar gelene kadar tutar, böylece eşzamanlı işlemler kısmi sonuçları görmez.