Lamport'un dağıtık sistemlerle ilgili temel fikirlerini—mantıksal saatler, sıralama, uzlaşma ve doğruluk—ve neden modern altyapılara hâlâ yol gösterdiklerini öğrenin.

Leslie Lamport, “teorik” çalışmaları gerçek bir sistemi her göndermenizde karşınıza çıkan nadir araştırmacılardan biridir. Bir veritabanı kümesini, mesaj kuyruğunu, iş akışı motorunu çalıştırdıysanız ya da istekleri yeniden deneyen ve hatalardan kurtulan herhangi bir şeyle uğraştıysanız, Lamport’un adlandırdığı ve çözdüğü problemlerin içinde yaşıyorsunuz demektir.
Fikirlerinin kalıcı olmasının sebebi, belirli bir teknolojiye bağlı olmayışlarıdır. Bu fikirler, birden fazla makinenin tek bir sistem gibi davranmaya çalıştığı her durumda ortaya çıkan rahatsız edici gerçekleri tanımlar: saatler uyuşmaz, ağ mesajları geciktirir veya düşürür, ve hatalar normaldir—istisna değil.
Zaman: Dağıtık bir sistemde “saat kaç?” basit bir soru değildir. Fiziksel saatler farklı hızlarda sapar ve farklı makineler olayları farklı sıralarda gözleyebilir.
Sıralama: Tek bir saate güvenemediğinizde, hangi olayların önce geldiğini konuşmak için başka yollar gerekir—ve ne zaman herkesin aynı sırayı izlemesini zorunlu kılmanız gerektiğini belirlemek gerekir.
Doğruluk: “Genelde çalışıyor” bir tasarım değildir. Lamport alanı daha net tanımlara (safety vs. liveness) ve sadece test etmekle kalmayıp üzerine akıl yürütülebilecek spesifikasyonlara doğru götürdü.
Kavramlara ve sezgiye odaklanacağız: problemler, net düşünmek için gereken asgari araçlar ve bu araçların pratik tasarımları nasıl şekillendirdiği.
İşte harita:
Neden ortak bir saat yoksa tek bir küresel olay hikâyeniz olmaz
Nedenlilik ("happened-before") nasıl mantıksal saatlere ve Lamport zaman damgalarına yol açar
Kısmi sıra ne zaman yeterli değildir ve tek bir zaman çizelgesine ne zaman ihtiyaç duyarsınız
Uzlaşma ve Paxos'un sır üzerinde anlaşmayla nasıl ilgili olduğu
Sıralama paylaşıldığında durum makinesi replikasyonunun nasıl çalıştığı
Spesifikasyonlarda doğruluğu nasıl konuşacağınız—ve TLA+ gibi modelleme araçlarının nasıl yardımcı olduğu
Bir sistem, işi bir ağ üzerinden koordine eden birden fazla makineden oluştuğunda “dağıtık” sayılır. Bu kulağa basit geliyor; ta ki iki gerçeği kabul edene kadar: makineler bağımsız olarak başarısız olabilir (kısmi hatalar) ve ağ mesajları gecikebilir, düşebilir, çoğaltılabilir veya yeniden sıralanabilir.
Tek bir bilgisayarda tek bir program çalıştırıyorsanız genellikle “önce ne oldu” diye işaret edebilirsiniz. Dağıtık bir sistemde farklı makineler farklı olay sıraları gözleyebilir—ve her ikisi de kendi yerel bakış açısından doğru olabilir.
Koordinasyonu her şeyi zaman damgalamakla çözmek cazip gelebilir. Ama makineler arasında güvenebileceğiniz tek bir saat yoktur:
Bu yüzden bir hostta “olay A 10:01:05.123'te oldu” demek başka bir hosttaki “10:01:05.120” ile güvenilir biçimde karşılaştırılamaz.
Ağ gecikmeleri gördüğünüz şeyi tersine çevirebilir. Bir yazma önce gönderilmiş olabilir ama sonra ulaşabilir. Bir yeniden deneme orijinalinden sonra gelebilir. İki veri merkezi “aynı” isteği ters sırayla işleyebilir.
Bu da hata ayıklamayı benzersiz şekilde kafa karıştırıcı yapar: farklı makinelerden gelen loglar uyuşmayabilir ve “zaman damgasına göre sırala” asla gerçekten yaşanmamış bir hikâye oluşturabilir.
Tek bir zaman çizelgesi olduğunu varsaydığınızda somut hatalarla karşılaşırsınız:
Lamport’un ana içgörüsü burada başlar: zamanı paylaşamıyorsanız, sıralamayı farklı şekilde akıl yürütmelisiniz.
Dağıtık programlar olaylardan oluşur: bir düğümde (süreç, sunucu veya thread) gerçekleşen şeyler. Örnekler: “istek alındı”, “bir satır yazıldı” veya “bir mesaj gönderildi”. Bir mesaj, düğümler arasındaki bağlantıdır: bir olay gönderme ise başka bir olay almadır.
Lamport’un temel içgörüsü şudur: güvenilir ortak bir saat olmayan bir sistemde, takip edebileceğiniz en dayanıklı şey nedenselliktir—hangi olayların hangi diğer olayları etkileyebileceği.
Lamport, happened-before adını verdiği basit bir kural tanımladı, A → B şeklinde okunur (A olayı B olayından önce oldu):
Bu ilişki size bir kısmi sıra verir: bazı çiftlerin sırası bellidir, ama hepsi değil.
Bir kullanıcı “Satın Al” tıklar. Bu tıklama bir API sunucusuna istek gönderir (olay A). Sunucu veritabanına bir sipariş satırı yazar (olay B). Yazma tamamlandığında sunucu “sipariş oluşturuldu” mesajı yayınlar (olay C) ve bir önbellek servisi bunu alıp önbelleği günceller (olay D).
Burada A → B → C → D. Saatler uyumsuz olsa bile, mesajlar ve program yapısı gerçek nedensel bağlantılar yaratır.
İki olay eşzamanlıdır (concurrent) eğer birbirini neden olmuyorsa: ne (A → B) ne de (B → A) geçerlidir. Eşzamanlılık “aynı an” anlamına gelmez—bağıntı yokluğu anlamına gelir. Bu yüzden iki servis de “önce ben yaptım” diyebilir ve her ikisi de doğru olabilir; ta ki bir sıralama kuralı getirmedikçe.
Birden fazla makine arasında "önce ne oldu"yu yeniden oluşturmayı denediyseniz temel problemle karşılaşmışsınızdır: bilgisayarlar kusursuz eşzamanlı bir saate sahip değil. Lamport’un çözümü mükemmel zamanı kovalamayı bırakıp onun yerine sıralamayı takip etmektir.
Bir Lamport zaman damgası, bir süreçteki her anlamlı olaya iliştirdiğiniz sadece bir sayıdır (hizmet örneği, düğüm, thread—hangi düzeyi seçerseniz). Bunu, duvar saati güvenilmez olduğunda bile “bu olay o olaydan önce oldu” demek için tutarlı bir yol veren bir "olay sayacı" olarak düşünün.
Yerelde artır: bir olayı kaydetmeden önce (örn. “DB'ye yazıldı”, “istek gönderildi”, “log'a ekleme yapıldı”) yerel sayacınızı artırın.
Alındığında max + 1 alın: göndericinin zaman damgasını içeren bir mesaj aldığınızda sayaçınızı şu şekilde ayarlayın:
max(local_counter, received_counter) + 1
Sonra alma olayını bu değerle damgalayın.
Bu kurallar zaman damgalarının nedenselliğe saygı göstermesini sağlar: eğer A olayı B'yi etkileyebilecekse (mesaj yoluyla bilgi aktarıldıysa), A'nın zaman damgası B'den küçük olur.
Bunlar nedensel sıralama hakkında bilgi verir:
TS(A) < TS(B) ise, A muhtemelen B'den önce olmuş olabilir.TS(A) < TS(B) olacaktır.Gerçek zaman hakkında ise söyleyemezler:
Yani Lamport zaman damgaları sıralama için iyidir; gecikmeyi ölçmek veya "saat kaçtı" sorusuna cevap vermek için değildir.
Diyelim ki Servis A, Servis B'yi çağırıyor ve her ikisi de denetim (audit) logları yazıyor. Nedensellik zincirini koruyan birleşik bir log görünümü istiyorsunuz.
max(local, 42) + 1 olarak ayarlar, diyelim 43, ve “kart doğrulandı” kaydını yapar.Artık her iki servisten gelen logları (lamport_timestamp, service_id) ile sıraladığınızda, duvar saatleri kaymış veya ağ gecikmiş olsa bile gerçek etki zinciriyle uyuşan istikrarlı, açıklanabilir bir zaman çizelgeniz olur.
Nedensellik size bir kısmi sıra verir: bazı olaylar açıkça diğerlerinden "önce"dir (çünkü mesaj veya bağımlılık bağlar), ama birçok olay basitçe eşzamanlıdır. Bu bir hata değil—dağıtık gerçekliğin doğal şeklidir.
Eğer "bu neyi etkileyebilirdi?" diye hata ayıklıyorsanız ya da "bir yanıt isteği takip etmeli" gibi kuralları uyguluyorsanız, kısmi sıra tam olarak ihtiyacınız olan şeydir. Sadece happened-before kenarlarına uymanız yeterli; diğer her şey bağımsız kabul edilebilir.
Bazı sistemler "her iki sıra da uygundur" diyemez. Özellikle aşağıdakiler için tek bir sıra gerekebilir:
Tam sıra yoksa, iki replika yerel olarak "doğru" olsa bile küresel olarak farklılaşabilir: biri A sonra B uygular, diğeri B sonra A uygular ve farklı sonuçlar elde edersiniz.
Sıralama yaratan bir mekanizma eklersiniz:
Tam sıra güçlüdür, ama bir maliyeti vardır:
Tasarım tercihi basitçe şöyle özetlenir: doğruluk tek bir paylaşılan anlatı gerektiriyorsa, bunun için koordinasyon maliyetini ödersiniz.
Uzlaşma, birden fazla makinenin bir karar—bir değerde, bir liderde, bir konfigürasyonda—uzlaşmasını sağlamaktır; her makine yalnızca kendi yerel olaylarını ve eline geçen mesajları görür.
Bu kulağa basit geliyor; ta ki dağıtık sistemin yapabileceklerini hatırlayana kadar: mesajlar gecikebilir, çoğaltılabilir, yeniden sıralanabilir veya kaybolabilir; makineler çökecek ve yeniden başlayabilir; ve nadiren "bu düğüm kesinlikle öldü" gibi temiz bir sinyal alırsınız. Uzlaşma, bu koşullar altında anlaşmayı güvenli kılmakla ilgilidir.
Eğer iki düğüm geçici olarak konuşamıyorsa (ağ bölünmesi), her iki taraf da kendi başına "ilerlemeye" çalışabilir. Her iki taraf farklı değerler seçerse split-brain davranışı olabilir: iki lider, iki farklı konfigürasyon veya birbirine rakip geçmişler.
Bölünmeler olmasa bile gecikme sorun yaratır. Bir düğüm bir teklifi duyduğunda diğerleri çoktan ilerlemiş olabilir. Ortak saat olmadığında "A teklifi B'den önce oldu" demek fiziksel zamana dayanarak güvenilir değildir.
Günlük işlerinizde buna "uzlaşma" demeyebilirsiniz, ama altyapıda şu görevlerde karşınıza çıkar:
Her durumda, sistemin ya herkesin üzerinde birleşeceği tek bir sonucu olması gerekir ya da en azından çelişkili sonuçların her ikisinin de geçerli sayılmasını engelleyen bir kural.
Lamport’un Paxos'u, bu "güvenli anlaşma" problemine temel bir çözümdür. Temel fikir, sihirli bir zaman aşımı ya da kusursuz lider değil—gecikmeli mesajlar ve başarısız düğümler varken bile sadece bir değerin seçilebileceğini garanti eden bir dizi kuraldır.
Paxos güvenliği (safety) ve ilerlemeyi (progress) ayırır; bu da onu pratik bir şablon yapar: gerçek dünya performansı için ayarlamalar yaparken temel garantiyi korursunuz.
Paxos okumayı zor kılan şey bir algorithma eksikliğinden ziyade "Paxos"un bir dizi yakından ilişkili desenden oluşmasıdır. Güvenlik sezgisi ise gayet anlaşılırdır.
Kim öneriyor, kim onaylıyor ayrımını yapmak faydalıdır.
Unutulmaması gereken yapısal fikir: her iki çoğunluk da birbirleriyle kesişir. O kesişim güvenliğin yaşadığı yerdir.
Paxos güvenliği şu şekilde basitçe ifade edilir: bir değer seçildikten sonra sistem asla farklı bir değer seçmemelidir—split-brain kararları olmamalı.
Ana sezgi şudur: teklifler numaralar taşır (oy pusulası ID'leri gibi). Acceptor'lar daha yeni numaralı teklifleri gördükten sonra daha eski teklifleri yok saymaya söz verirler. Yeni bir proposer yeni bir numarayla denediğinde önce bir çoğunluktan daha önce kabul edilen değerleri sorar.
Çünkü çoğunluklar çakışır, yeni proposer en son kabul edilen değeri "hatırlayan" en az bir acceptor'dan mutlaka bilgi alır. Kural şudur: eğer kotalardan birinde bir şey kabul edilmişse, o değeri (veya en yenisini) teklif etmelisiniz. Bu kısıtlama iki farklı değerin seçilmesini engeller.
Liveness, makinenin makul koşullar altında eninde sonunda bir şey seçmesini ifade eder (örneğin, kararlı bir lider ortaya çıkar ve ağ mesajları eninde sonunda iletilir). Paxos kaos halinde hız vaat etmez; doğruluk vaat eder ve işler sakinleştiğinde ilerleme sağlar.
Durum makinesi replikasyonu (SMR), birçok "yüksek kullanılabilirlik" sisteminin temel desenidir: tek bir sunucunun kararlar alması yerine, aynı sıradaki komutları işleyen birden fazla replika çalıştırırsınız.
Merkeze, "put key=K value=V" veya "A'dan B'ye 10$ aktar" gibi komutların sıralı bir listesi olan çoğaltılmış log konur. İstemciler komutları doğrudan her replikaya göndermek yerine kümeye gönderir; sistem bu komutlar için tek bir sıra üzerinde anlaşır ve ardından her replikayı yerel olarak bu sırayla uygular.
Eğer her replik başlangıçta aynı durumda başlıyorsa ve aynı komutları aynı sırayla yürütürse, hepsi aynı durumda biter. Bu temel güvenlik sezgisidir: birden fazla makineyi zamanla senkronize etmeye çalışmıyorsunuz; deterministiklik ve paylaşılan sıralama ile onları aynı hale getiriyorsunuz.
Bu yüzden uzlaşma (Paxos/Raft tarzı protokoller) genellikle SMR ile birlikte kullanılır: uzlaşma bir sonraki log girdisini kararlaştırır ve SMR bu kararı replikalar arasında tutarlı duruma çevirir.
Log sonsuza kadar büyürse yönetmeniz gerekir:
SMR sihir değildir; "sıralama üzerinde anlaşma"yı "durum üzerinde anlaşma"ya çeviren disiplinli bir yaklaşımdır.
Dağıtık sistemler tuhaf şekillerde başarısız olur: mesajlar gecikebilir, düğümler yeniden başlatılabilir, saatler uyuşmayabilir ve ağ bölünür. "Doğruluk" bir his olmayıp, her duruma (hatayı da kapsayacak şekilde) netçe ifade edilebilen bir dizi sözdür.
Safety “kötü bir şeyin asla olmaması” demektir. Örnek: çoğaltılmış bir anahtar-değer deposunda aynı log index'i için iki farklı değerin kesinlikle taahhüt edilmemesi. Başka bir örnek: bir kilit servisi aynı kilidi iki müşteriye aynı anda kesinlikle vermemelidir.
Liveness “iyi bir şeyin eninde sonunda olması” demektir. Örnek: çoğunluk replika ayakta ve ağ nihayet teslimat yaparsa, bir yazma isteği eninde sonunda tamamlanmalıdır. Bir kilit isteği sonsuz beklemek yerine eninde sonunda evet veya hayır almalıdır.
Safety çelişkileri önlemekle; liveness ise kalıcı tıkanmaları önlemekle ilgilidir.
Bir invariant, her erişilebilir durumda her zaman geçerli olması gereken bir koşuldur. Örneğin:
Bir invariant çökme, zaman aşımı, yeniden deneme veya bölünme sırasında ihlal edilebiliyorsa, o zaman o invariant gerçekten uygulanmamıştır.
Bir kanıt, sadece normal yol için değil, tüm olası yürütmeleri kapsayan bir argümandır. Her durumu düşünürsünüz: mesaj kaybı, çoğaltma, yeniden sıralama; düğüm çökmeleri ve yeniden başlatmalar; rakip liderler; istemcilerin yeniden denemeleri.
Açık bir spes, durum, izin verilen eylemler ve gereken özellikleri tanımlar. Bu, "sistem tutarlı olmalı" gibi belirsiz beklentilerin çatışmaya dönüşmesini engeller. Spes, bölünmeler sırasında ne olacağını, "commit" in ne anlama geldiğini ve istemcilerin neye güvenebileceğini üretime geçmeden önce söylemenizi zorunlu kılar.
Lamport’un en pratik derslerinden biri şu: bir dağıtık protokolü koddan önce daha yüksek seviyede tasarlayabilirsiniz (ve çoğu zaman tasarlamalısınız). Thread'ler, RPC'ler ve yeniden deneme döngüleri hakkında endişelenmeden önce sistemin kurallarını yazabilirsiniz: hangi eylemlere izin verilir, hangi durum değişebilir ve ne asla olmamalıdır.
TLA+ eşzamanlı ve dağıtık sistemleri tanımlamak için bir spesifikasyon dili ve model denetleme aracıdır. Sisteminizin basit, matematik-benzeri bir modelini yazarsınız—durumlar ve geçişler—ve de umursadığınız özellikleri (örneğin, "en fazla bir lider" veya "kesinleştirilmiş bir giriş kaybolmaz") tanımlarsınız.
Ardından model denetleyici olası sıralemeleri, mesaj gecikmelerini ve hataları keşfederek bir karşı örnek bulur: özelliğinizi bozan somut bir adım dizisi. Toplantılarda kenar durumları tartışmak yerine elle tutulur bir argüman elde edersiniz.
Çoğaltılmış logta bir "commit" adımını düşünün. Kodda nadir zamanlarda iki farklı düğümün aynı index için iki farklı girişi commit etmesine izin verebilecek bir hata kolayca oluşabilir.
Bir TLA+ modeli şu izlemeyi bulabilir:
Bu, nadiren üretimde görülebilecek ama modelde çabucak ortaya çıkan bir güvenlik ihlalidir. Benzer modeller kaybolan güncellemeleri, çift uygulanmaları veya "ack ama kalıcı değil" durumlarını yakalar.
TLA+ lider seçimi, üyelik değişiklikleri, uzlaşma-benzeri akışlar ve sıralama ile hata işleme etkileşime girdiği kritik koordinasyon mantıkları için en değerlisidir. Bir hata veriyi bozacaksa veya manuel iyileştirme gerektirecekse, küçük bir model genellikle sonrasında düzeltmekten daha ucuztur.
Bu fikirlerle ilgili dahili araçlar inşa ediyorsanız, pratik bir iş akışı şöyle olabilir: hafif bir spes yazın (hatta gayri resmi), sonra sistemi uygulayın ve spesin invariantlarından testler türetin. Koder.ai gibi platformlar burada inşa-test döngüsünü hızlandırmaya yardımcı olabilir: sırala/uzlaşma davranışını düz bir dille tarif edebilir, servis iskeletini (React ön yüzleri, Go arka uçlar ile PostgreSQL veya Flutter istemciler) hızlıca iterasyon yapabilir ve üretirken "asla olmaması gereken" durumları görünür tutabilirsiniz.
Lamport'un uygulayıcılara verdiği büyük hediye bir zihniyettir: zamanı ve sıralamayı varsayılan kabul etmeyin; bunları modelleyin. Bu zihniyet, pazartesi uygulanabilecek bir dizi alışkanlığa dönüşür.
Mesajlar gecikebilir, çoğaltılabilir veya sıra dışı gelebilir; her etkileşimi bu koşullar altında güvenli olacak şekilde tasarlayın.
Zaman aşımı bir gerçek değil; bir politikadir. Bir zaman aşımı sadece "zamanında cevap gelmedi" demektir, "diğer taraf hareketsiz kaldı" değil. Bunun iki somut sonucu vardır:
İyi hata ayıklama araçları sadece zaman damgası değil, sıralamayı kodlar.
Dağıtık bir özellik eklemeden önce şu sorularla netlik sağlayın:
Bu sorular için PhD olmanıza gerek yok—sadece sıralama ve doğruluğu ürün gereksinimleri olarak birinci sınıf elemanlar olarak ele alma disiplini gerekir.
Lamport'un kalıcı hediyesi, sistemlerin saat paylaşmadığında ve "ne oldu" konusunda varsayılan olarak anlaşmadığında net düşünme yoludur. Kusursuz zamanı kovalamak yerine nedenselliği takip edersiniz (ne neyi etkileyebilir), bunu mantıksal zaman ile temsil edersiniz (Lamport zaman damgaları) ve ürün tek bir geçmiş gerektiriyorsa uzlaşma kurarsınız ki her replik aynı karar dizisini uygulasın.
Bu konu pratiğe dönüştüğünde ortaya çıkan mühendislik zihniyeti şudur:
Söylemeniz gereken kuralları yazın: asla olmaması gerekenler (safety) ve eninde sonunda olması gerekenler (liveness). Sonra uygulayın ve sistemi gecikme, bölünmeler, yeniden denemeler, çoğaltılmış mesajlar ve düğüm yeniden başlatmaları altında test edin. Birçok "gizemli kesinti" aslında "bir istek iki kez işlenebilir" veya "liderler her zaman değişebilir" gibi eksik ifadelerdir.
Daha fazla dalmak ama formalizmde boğulmadan:
Sahip olduğunuz bir bileşeni seçin ve bir sayfa "hata sözleşmesi" yazın: ağ ve depolama hakkında ne varsayıyorsunuz, hangi işlemler idempotent, ve hangi sıralama garantilerini sağlıyorsunuz.
Bu egzersizi somutlaştırmak isterseniz, küçük bir "sıralama demo" servisi yapın: komutları bir log'a ekleyen bir istek API'si, bunları uygulayan bir arka plan işçisi ve nedensellik meta verilerini ve yeniden denemeleri gösteren bir yönetici görünümü. Bu tür bir deneyi hızla yinelemek için Koder.ai üzerinde çalışmak faydalı olabilir—özellikle hızlı iskelet çıkarma, dağıtım/barındırma, deneyler için snapshot/geri alma ve memnun kaldığınızda kaynak kodu dışa aktarma imkânı sunduğunda.
İyi yapıldığında, bu fikirler kesintileri azaltır çünkü daha az davranış örtük kalır. Ayrıca akıl yürütmeyi kolaylaştırır: zamandan tartışmayı bırakır, sisteminiz için sıra, uzlaşma ve doğruluğun gerçekten ne anlama geldiğini kanıtlamaya başlarsınız.