Dağıtık Veri Sistemlerinde Eşzamanlılık

Dağıtık Veri Sistemlerinde Eşzamanlılık

Dağıtık veri sistemlerinde eşzamanlılık; MVCC, izolasyon seviyesi, kayıp güncelleme, write skew ve çoğaltma gecikmesini aynı tutarlılık modeli içinde ele alır.

İzolasyon Seviyesi Tek Başına Yeterli Değildir

Dağıtık bir veri sisteminde “hangi izolasyon seviyesi kullanılıyor?” sorusu tek başına yeterli değildir. Asıl belirleyici olan; aynı kaydı güncelleyen işlemlerin nasıl çakıştığı, okumanın liderden mi replikadan mı yapıldığı ve uygulamanın hangi anomalileri tolere edebildiğidir. READ COMMITTED kirli okumayı engelleyebilir, fakat kayıp güncelleme veya write skew gibi sonuçları kendiliğinden ortadan kaldırmaz.

Bu nedenle eşzamanlılık sözleşmesini üç katmanda okumak daha sağlıklıdır: işlem izolasyonu, çatışma çözümü ve çoğaltma görünürlüğü. Uygulama retry yapıyorsa idempotentlik ve güvenli yeniden deneme de aynı sözleşmenin parçası olur. Bir katmanda verilen garanti diğer katmanda varsayılmamalıdır.

Bir işlemin diğerinden önce gerçekleşip gerçekleşmediği (happens-before ilişkisi), veri sistemlerindeki eşzamanlılığın matematiksel temelini oluşturur. Eğer iki işlem birbirinin varlığından habersizse ve aralarında nedensel bir bağımlılık yoksa, bu işlemler fiziksel zamanlarından bağımsız olarak eşzamanlı (concurrent) kabul edilir. Ortak verilere yönelik eşzamanlı okuma ve yazma işlemleri, sistemlerde veri tutarsızlıklarına ve yarış durumlarına (race conditions) yol açar. İşlemleri mantıksal bir birim olarak gruplayan (transaction) mekanizmalar, bu tür anomalileri önlemek için tasarlanmıştır ve atomiklik, tutarlılık, izolasyon ile dayanıklılık (ACID) garantilerine dayanır.

İzolasyon (isolation), eşzamanlı yürütülen işlemlerin birbirlerinin ara durumlarını görmesini engelleyerek sistemin sanki işlemler seri olarak yürütülüyormuş gibi (serializability) davranmasını sağlar. Ancak tam serileştirilebilirlik mekanizmaları ciddi performans maliyetleri getirdiğinden, modern veri tabanları genellikle daha zayıf izolasyon algoritmaları uygular.

Zayıf İzolasyon ve MVCC

En temel işlem izolasyon seviyesi olan okunmuş veriyi okuma (read committed), kirli okumaları (dirty reads) ve kirli yazmaları (dirty writes) engeller. Veri tabanları, kirli yazmaları önlemek için yazma işlemi süresince satır seviyesinde kilitler (satır-level locks) kullanarak yalnızca bir işlemin veriyi güncellemesine izin verir. Kirli okumaları engellemek için ise, okuma kilitlerinin yaratacağı performans darboğazlarından kaçınılır ve bunun yerine yazma işlemi tamamlanana kadar diğer işlemlere verinin eski onaylanmış (committed) sürümü sunulur.

Buna rağmen read committed seviyesi, bir işlemin farklı zamanlarda aynı veriyi okuduğunda farklı sonuçlar elde ettiği okuma sapması (read skew) veya tekrarlanamayan okuma (nonrepeatable read) anomalilerine karşı savunmasızdır. Bu durum, uzun süren analitik sorgularda veya yedekleme (backup) işlemlerinde kalıcı tutarsızlıklara neden olabilir. Okuma sapmasını çözmek için anlık görüntü izolasyonu (anlık görüntü isolation) devreye girer. Anlık görüntü izolasyonunda her işlem, başladığı andaki veri tabanının tutarlı bir anlık görüntüsü üzerinden okuma yapar ve diğer işlemlerin sonradan yaptığı değişiklikleri görmez.

Bu yapı genellikle Çoklu Sürüm Eşzamanlılık Kontrolü (Multi-Version Concurrency Control, MVCC) ile uygulanır. MVCC mimarisinin temel prensibi, okuyucuların yazıcıları ve yazıcıların okuyucuları hiçbir zaman engellememesidir. Veritabanı, her satırın birden fazla sürümünü tutar ve bu sürümleri her işleme atanan tekil, sürekli artan işlem kimlikleri (transaction IDs) ile etiketler. Bir verinin görünür olabilmesi için, satırı ekleyen işlemin okuyucu işlemden önce onaylanmış olması ve ilgili satırın okuyucu başladığında silinmemiş olması şarttır.

Kayıp Güncelleme ve Yazma Sapması

MVCC okuma anormalliklerini çözse de, eşzamanlı yazma işlemlerindeki yarış durumları farklı karakterlerde ortaya çıkmaya devam eder. Okuyup-değiştirip-yazma (read-modify-write) döngülerinde iki işlem aynı veriyi güncellediğinde, birinin yaptığı değişiklik diğerinin üzerine yazılarak kayıp güncelleme (lost update) problemine yol açabilir. Kayıp güncellemeleri önlemek için atomik yazma işlemleri, açık kilitleme (SELECT FOR UPDATE) veya veri tabanının çatışmaları otomatik olarak tespit edip işlemi iptal etmesi (abort) gibi yöntemler kullanılır [22-24]. Ancak kaynakların sınırlı olduğu durumlarda, MySQL/InnoDB'nin "repeatable read" seviyesi kayıp güncellemeleri otomatik olarak tespit edemezken, PostgreSQL bu durumu başarılı bir şekilde yakalayabilir, bu davranış veri tabanı sürümüne ve mimarisine bağlıdır.

Daha karmaşık ve tespit edilmesi zor bir senaryo ise yazma sapmasıdır (write skew). İki işlem aynı nesneleri okuyup bu sorgu sonucuna (bir öncüle) dayanarak farklı nesneleri güncellediğinde yazma sapması meydana gelir. Örneğin, bir vardiyada en az bir doktorun nöbetçi kalmasını gerektiren bir sistemde, iki doktorun eşzamanlı olarak kendi kayıtlarını nöbetten çıkacak şekilde güncellemesi bu öncülü geçersiz kılar [28-30].

Bir işlemin gerçekleştirdiği yazmanın, diğer bir işlemin arama sorgusunun sonucunu değiştirmesi olayına hayalet (phantom) adı verilir. Anlık görüntü izolasyonu, hayaletlerin salt okunur sorguları bozmasını engellese de, okuma-yazma döngülerinde yazma sapmasına neden olmalarını durduramaz. Kilitleme yapılacak belirgin bir veri satırının bulunmadığı durumlarda, veri tabanına yapay nesneler eklenerek çatışmaları somutlaştırmak (materializing conflicts) mümkündür. Fakat bu yöntem, eşzamanlılık kontrol mekanizmasını uygulama veri modeline taşıdığı için yalnızca diğer tüm yöntemlerin tükendiği durumlarda uygulanmalıdır.

Çoğaltma Gecikmesi ve Tutarlılık

Tek düğümlü işlemlerden dağıtık çoğaltma (replication) mimarilerine geçildiğinde izolasyon problemleri, ağ kesintileri ve çoğaltma gecikmesi (replication lag) ile doğrudan etkileşime girer. Lider tabanlı (single-leader) asenkron çoğaltmada, okuma ölçeklendirmesi (read scaling) amacıyla takipçi (follower) düğümlerden yapılan sorgular güncel olmayan veriler döndürebilir ve bu durum sistemin yalnızca nihai tutarlılık (eventual consistency) sağlamasıyla sonuçlanır [34-36].

Çoğaltma gecikmesinin neden olduğu bir diğer problem, kullanıcıların kendi yaptıkları değişiklikleri hemen görememesidir. Kendi yazdığını okuma (read-after-write) tutarlılığı, kullanıcının doğrudan değiştirdiği verilerin her zaman lider düğümden veya senkron bir takipçiden okunmasını sağlayarak bu asimetriyi çözer. Eğer kullanıcı birden fazla düğümden sıralı okuma yaparsa, eski bir kopyaya denk geldiğinde zamanın geriye aktığı yanılgısına düşebilir, monotonik okumalar (monotonic reads) garantisi, kullanıcının her zaman aynı kopyadan okuma yapmasını zorunlu kılarak bu problemi önler [39-41]. Ayrıca, nedensel bir bütünlüğe sahip verilerin farklı hızlarda çoğaltılması nedeniyle sonuçların sebeplerden önce görünmesi riskine karşı tutarlı önek okumaları (consistent prefix reads) kullanılarak yazma işlemlerinin doğru sırayla okunması garanti altına alınır [42-44].

Çoklu Lider ve Lidersiz Sistemlerde Çatışma

Çoklu lider (multi-leader) ve lidersiz (leaderless) çoğaltma topolojilerinde, yazma işlemleri aynı anda birden fazla düğümde gerçekleşebildiğinden küresel bir ağ sıralaması (global ordering) dayatılamaz. Bu yapılar, düğüm hatalarına ve bölgesel ağ kesintilerine karşı dayanıklı (fault-tolerant) olsalar da, eşzamanlı yazma çatışmaları kaçınılmazdır.

Çatışma çözümünde yaygın kullanılan son yazan kazanır (last write wins, LWW) stratejisi, her yazma işlemine bir zaman damgası (timestamp) atar ve en büyük zaman damgasına sahip işlemi geçerli kılar. Ancak LWW, zaman damgası sırasının eşzamanlı işlemlerde teknik olarak anlamsız olması nedeniyle arka planda başarılı olarak işlenmiş diğer yazmaları sessizce iptal eder ve kesin veri kaybına (data loss) yol açar.

Daha deterministik otomatik çatışma çözümü (automatic conflict resolution) sağlamak adına Çatışmasız Çoğaltılan Veri Tipleri (Conflict-free Replicated Datatypes, CRDTs) veya İşlemsel Dönüşüm (Operational Transformation, OT) algoritmaları devreye girer. OT, karakter indekslerini daha önce uygulanmış eşzamanlı işlemlere göre dönüştürerek metin tutarlılığını sağlarken, CRDT'ler her bir öğeye benzersiz ve değiştirilemez bir kimlik (ID) atayarak konumları bu kimlikler üzerinden matematiksel olarak hesaplar. Yerel-öncelikli (local-first) senkronizasyon motorları (sync engines), bu matematiksel yaklaşımlarla birlikte cihazların çevrimdışıyken bile yerel kopyalar üzerinde lider gibi davranmasını ve ağa bağlanıldığında tüm eşzamanlı güncellemelerin kayıpsız şekilde birleştirilmesini (merge) mümkün kılar [55-57].

2PL, Seri Çalıştırma ve SSI

Veri sistemlerinde hayaletler (phantoms) veya yazma sapması (write skew) gibi anormallikleri tamamen engellemenin tek matematiksel yolu gerçek serileştirilebilirlik (serializability) garantisi sunmaktır. Dağıtık ve tek düğümlü sistemlerde serileştirilebilirlik genellikle üç farklı algoritma üzerinden sağlanır.

Birinci yaklaşım, en eski ve geleneksel mekanizma olan İki Aşamalı Kilitleme'dir (Two-Phase Locking, 2PL). 2PL, yazıcıların diğer yazıcıları ve okuyucuları engellediği, okuyucuların ise yazıcıları beklettiği kötümser (pessimistic) bir eşzamanlılık kontrol mekanizmasıdır. İşlemler yürütülürken kilitlerin alındığı (birinci aşama) ve işlemin sonunda kilitlerin bırakıldığı (ikinci aşama) bu modelde, hayalet okumaları önlemek için tüm sorgu koşulunu kapsayan yüklem kilitleri (predicate locks) teorik olarak gereklidir. Ancak yüklem kilitlerinin performans yükü nedeniyle pratikte bunun bir yaklaşımı olan indeks aralığı kilitleme (index-range locking) kullanılır. Tüm izolasyon kusurlarını önlemesine rağmen 2PL, eşzamanlılığı ciddi şekilde düşürdüğü ve kilitlenmelere (deadlocks) yatkın olduğu için performansı dengesizleştirir.

İkinci yöntem, eşzamanlılık koordinasyonunu tamamen denklemden çıkaran gerçek seri çalıştırma (actual serial execution) yaklaşımıdır. Bütün işlemler tek bir iş parçacığı (single iş parçacığı) üzerinde arka arkaya işletilir. Bu mimari, aktif veri setinin belleğe (RAM) sığdığı ve tüm işlemlerin ağ veya disk I/O işlemlerini beklemeden önceden derlenmiş saklı yordamlar (stored procedures) olarak yürütüldüğü özel durumlarda yüksek verim (işlem hacmi) sağlar.

Üçüncü ve en modern yaklaşım olan Serileştirilebilir Anlık Görüntü İzolasyonu (Serializable Anlık görüntü Isolation, SSI), anlık görüntü izolasyonunun performans avantajlarını serileştirilebilirlik ile birleştiren iyimser (optimistic) bir algoritmadır (2008 yılında literatüre sunulmuştur). SSI'da işlemler kilit beklemek yerine hemen çalıştırılır, ancak işlem tamamlanıp veri tabanına işlenmeden (commit) hemen önce izolasyonun ihlal edilip edilmediği denetlenir. Veritabanı, bir işlemin eski bir MVCC anlık görüntüsünden okuma yapıp yapmadığını veya önceki okumaları etkileyen sonradan yapılmış bir yazma olup olmadığını tespit eder. Bir işlemin verdiği kararın dayandığı öncül (premise) eşzamanlı bir yazma ile eskimişse, işlem abort edilerek yeniden denemeye zorlanır. SSI, okuyucuların yazıcıları engellememesini garanti ettiği için özellikle ağır okuma yüküne sahip sistemlerde işlem hızlarını başarıyla ölçeklendirir.

Tasarım kararları değerlendirildiğinde veri yoğun sistemlerde izolasyon, basit bir donanım optimizasyonundan çok, veri mutasyonlarının tutarlılığını doğrulayan bir bilgi işlem dinamiğidir. Sistem mühendislerinin, disk gecikmeleri ve ağ bölümlenmeleri altında veri bozulmalarını önleyebilmesi için mantıksal nedenselliğin sınırlarını doğru analiz etmeleri gerekir. Güçlü kilit algoritmalarının veri bütünlüğünü dayattığı 2PL modeli ile, CRDT gibi çözümlerin çatışmaları gecikmeli olarak entegre ettiği çoğaltma mimarileri arasında yapılacak seçim, doğrudan fiziksel ağ kısıtları altında tolere edilebilecek hata senaryolarına bağlıdır. Sonuç olarak operasyonel kararlılık, uygulama mantığının varsaydığı izolasyon modeliyle, veri tabanının dağıtık altyapıda fiilen sağlayabildiği matematiksel garantilerin örtüşmesini zorunlu kılar.

Eşzamanlılık ile Yeniden Yürütme İlişkisi

Paylaşılan durum üzerindeki yarışlar, yeniden yürütme ve hata sonrası tekrar deneme semantiğinden bağımsız değildir: lock-free kuyruk, güvenli retry.

Eşzamanlılık Sorunu Çoğu Zaman Ağdan Önce Durum Sorunudur

Dağıtık sistemlerde iki işlemin aynı anda çalışması, tek makinedeki thread yarışından daha geniş bir problem üretir. Mesaj tekrar gelebilir, sıra değişebilir, bir node sonucu yazdığı halde yanıtı kaybolabilir. Böyle bir durumda idempotency, teslim garantisi ve yeniden deneme politikası aynı doğruluk modelinin parçalarıdır. At-Least-Once Delivery, Exactly-Once Semantics ve Transactional Outbox bu sınırları farklı açılardan tanımlar.

Pratikte en pahalı hatalardan biri, ağ hatasını yalnız timeout olarak görüp yeniden denemeyi sınırsızlaştırmaktır. Retry Storm ve Backpressure, başarısızlık anında kapasitenin neden doğruluk kadar önemli hale geldiğini gösterir.

Doğruluk koşulunu işlem izleriyle tanımlamak

Bir iş yükünde eşzamanlılığın kabul edilebilir sonucu, tekil SQL ifadelerinden önce işlemlerin koruması gereken değişmezlerle tanımlanır. Örneğin iki bağımsız kaydın toplamı belirli bir alt sınırın üzerinde kalmalıdır koşulu, her işlemin yalnız kendi satırını güncellemesiyle korunmayabilir. İki işlem aynı başlangıç görünümünü okuyup farklı satırlara yazdığında write skew ortaya çıkabilir. İşlemin bir satırda kilit alması ile işlemlerin birlikte okuduğu yüklemin korunması aynı garanti değildir.

Seri çalıştırılabilirlik, eşzamanlı işlemlerin etkisinin bir seri yürütme sırasına eşdeğer olmasını ister. Dış istemciler açısından gerçek zamanlı sıralamayı da koruyan doğruluk modeli lineerleştirilebilirliktir; nesne işlemleri ve çok satırlı veri tabanı işlemleri için değerlendirme sınırları farklı olabilir. Bir sistemin tutarlılık sözleşmesi, işlem günlüğü ve istek zamanları gibi gözlemlenebilir izlerle bağdaştırılmalıdır.

Yeniden yürütme ve işlemsel kimlik

İşlem tekrarlandığında ağ yanıtının kaybolmuş olması, sunucuda yan etkinin oluşmadığını göstermez. Bunun için isteğin işlemsel kimliği, işin hangi aşamada tamamlandığı ve aynı kimliğin tekrar işlendiğinde hangi sonucun döndürüleceği açıkça tanımlanır. İdempotency anahtarı, benzersiz kısıt ve işlemsel outbox gibi araçlar, farklı bileşenlerde aynı iş olayının tutarlı biçimde izlenmesini sağlar. Buradaki hedef, bütün dağıtık sistemi tek bir kilitle seri hâle getirmek değil, iş değişmezlerini koruyan en dar atomik sınırı tanımlamaktır.

Yeniden Deneme, Sahiplik ve Sıralama Garantileri

Dağıtık işlemde aynı isteğin iki kez yürütülmesi, verinin iki kez işlenmesi gerektiği anlamına gelmez. Ağ kesintisinden sonra istemci yanıtı alamamış olabilir; sunucudaki yan etki ise tamamlanmıştır. Bu nedenle yeniden deneme davranışı, kalıcı iş kimliği ve iş durumuyla tanımlanmalıdır. Bir isteğin tekrar gönderilmesi güvenli olsa bile dış yan etkilerin idempotent olması ayrıca ispatlanmalıdır.

Kilit süresi dolan bir işçinin çalışmaya devam etmesi, aynı anahtar için yeni sahibiyle yarışmasına neden olabilir. Sadece lease süresi yeterli değildir; eski sahibin yazmasını reddeden monoton fencing token veya eşdeğer sahiplik kontrolü gerekir. Bu denetim, sonucun yazıldığı gerçek kaynakta uygulanmadıkça istemci tarafındaki kontrolü atlama ihtimali kalır.

Gecikme ölçümünde kuyrukta bekleme, işlem yürütme, dış I/O ve yeniden deneme ayrı zaman bütçeleridir. İşlem hacmi artışı gecikme kuyruğunu büyütüyorsa ortalama süre düşük kalsa bile kuyruk sonu gecikmeleri kötüleşebilir. Tutarlılık sınırı, kabul edilen yeniden sıralama davranışı ve tekrar yürütme sözleşmesi yazılmadan yalnız throughput değerleriyle sistem güvenilirliği değerlendirilmez.

Dağıtık bir işlemin başında yapılan uygunluk veya çakışma kontrolü, kontrol ile yazma arasında başka bir katılımcının durumu değiştirmesini engellemez. İki katılımcı aynı anda uygun sonucu elde edip ayrı kayıtları güncellediğinde, her bir yerel işlem başarılı olsa bile ortak iş kuralı ihlal edilebilir. Bunun tipik örneği, iki satırın toplamına ilişkin değişmezin iki farklı snapshot üzerinden sınanmasıdır. İlgili değişmezin korunması, uygun serileştirilebilir işlem semantiği, ortak kilitleme alanı veya değişmezi atomik olarak doğrulayan veri modeli gerektirir.

Dağıtık kilidin süresi dolduğunda eski sahibi yürütmeye devam edebiliyorsa yalnız lease süresiyle korunma sağlanmaz. Monoton artan fencing token ancak son yazmayı kabul eden bileşen daha eski nesilleri reddediyorsa etkilidir. Birden fazla bağımsız veri kaynağında tüm güncellemelerin tek işlem gibi gerçekleşmesi ise ayrıca atomik commit veya açıkça tanımlanmış telafi semantiği gerektirir. Kilit, transaction ve yeniden deneme mekanizmalarının garantileri birbirinin yerine kullanılamaz.

Kaynakça

  • Leslie Lamport. (1978). Time, Clocks, and the Ordering of Events in a Distributed System. Communications of the ACM, 21(7), 558-565. doi:10.1145/359545.359563
  • Martin Kleppmann. (2017). Designing Data-Intensive Applications. O'Reilly Media. URL
  • Maurice P. Herlihy; Jeannette M. Wing. (1990). Linearizability: A Correctness Condition for Concurrent Objects. ACM Transactions on Programming Languages and Systems, 12(3), 463-492. doi:10.1145/78969.78972
Bu sayfanın QR kodu