Dağıtık Sistemler & Zaman Yönetimi 7 dk okuma 3 okunma

Dağıtık Sistemlerde Clock Skew ve Monotonik Saatler

Dağıtık sistemlerde fiziksel saatler (NTP ile senkronize edilse bile) düğümler arasında kayabilir. Bu kaymaya clock skew denir. Monotonik saatler (Stopwatch, monotonic clock), duvar saatinden bağımsız olarak yalnızca ileriye doğru ilerler ve süre ölçümü için güvenlidir. Bu yazıda, clock skew'in yol açtığı sorunlar, Lamport saatleri, Hybrid Logical Clocks ve .NET'te monotonik saat kullanımı ele alınır.

Dağıtık Sistemlerde Clock Skew ve Monotonik Saatler

Dağıtık Sistemlerde Clock Skew ve Monotonik Saatler

Dağıtık sistemlerde zaman, göründüğü kadar basit değildir. Her düğümün kendi fiziksel saati vardır ve bu saatler NTP (Network Time Protocol) ile senkronize edilse bile mükemmel değildir. İki düğüm arasındaki saat farkına clock skew (saat kayması) denir. Bu kayma, sıralama (ordering), timeout, distributed locking ve event sıralaması gibi kritik işlemlerde ciddi sorunlara yol açar. Bu yazıda, clock skew'in neden olduğu problemleri, monotonik saatlerin rolünü ve .NET'te doğru zaman yönetimi stratejilerini detaylıca ele alacağız.


1. Fiziksel Saatler Neden Güvenilmez?

Her bilgisayarın donanımında bir gerçek zamanlı saat (RTC - Real-Time Clock) bulunur. Bu saat, bir kuvars osilatörüne dayanır ve şu nedenlerle kayabilir:

  • Sıcaklık Değişimleri: Kuvars osilatörünün frekansı sıcaklıkla değişir.

  • Donanım Yaşı: Osilatör zamanla yaşlanır ve frekansı kayar.

  • NTP Senkronizasyonu: NTP, saati periyodik olarak düzeltir, ancak bu düzeltmeler ani sıçramalara (clock jump) yol açabilir.

  • Sanallaştırma: VM'lerde saat, hipervizör tarafından yönetilir ve ek gecikmeler oluşabilir.

Tipik Clock Skew Değerleri:

  • İyi senkronize edilmiş bir cluster'da: 1-10 milisaniye

  • NTP olmadan: saniyeler, dakikalar hatta saatler

  • Google Spanner gibi sistemlerde: ~7 milisaniye (TrueTime API ile)


2. Clock Skew'in Yol Açtığı Sorunlar

A. Sıralama (Ordering) Sorunu

İki düğümde meydana gelen olayların sırasını belirlemek istediğinizde, saatler kaymışsa yanlış sıralama yaparsınız.

text

Node A: Event X (saat: 10:00:00.100)
Node B: Event Y (saat: 10:00:00.050)  ← Aslında sonra oldu ama saat geride

Sistem, Event Y'yi Event X'ten önce sanır. Ancak gerçekte Event X önce olmuştur.

B. Timeout ve Distributed Locking Sorunu

Dağıtık kilitleme sistemlerinde, kilit süresi (TTL) yerel saate göre hesaplanır. Eğer iki düğümün saati farklıysa:

text

Node A: Kilidi 30 saniye TTL ile alır (yerel saat: 10:00:00)
Node B: Saati 5 saniye ileri (yerel saat: 10:00:05)
       30 saniye sonra kilit süresi dolar (Node B'ye göre 10:00:35)
       Ancak Node A'ya göre henüz 10:00:30 - kilit hala geçerli!

Bu, çit sorunu (fencing problem) olarak bilinir: Kilit süresi doldu sanılır ama aslında dolmamıştır.

C. Event Sourcing ve Audit Log Sorunu

Event'lerin zaman damgalarına göre sıralandığı sistemlerde (event sourcing, audit log), clock skew yanlış sıralamaya yol açar.

D. Last-Write-Wins (LWW) Çakışma Çözümü

LWW tabanlı çakışma çözümünde, en son yazılan kazanır. Ancak saatler kaymışsa, "en son" yanlış belirlenir ve veri kaybı olur.


3. Monotonik Saatler (Monotonic Clocks)

Monotonik saat, duvar saatinden (wall clock) bağımsız olarak yalnızca ileriye doğru ilerleyen bir saattir. Sistem saati NTP ile geriye alınsa bile, monotonik saat etkilenmez.

Özellikler:

  • Her Zaman İleri: Asla geriye gitmez.

  • Süre Ölçümü İçin İdeal: İki olay arasındaki süreyi doğru ölçer.

  • Mutlak Zaman Değil: "Saat kaç?" sorusunu yanıtlamaz; "ne kadar süre geçti?" sorusunu yanıtlar.

  • Sistem Başlangıcına Göre: Genellikle sistem açılışından itibaren geçen süreyi ölçer.

.NET'te Monotonik Saat:

csharp

// Stopwatch - monotonik saat
var stopwatch = Stopwatch.StartNew();
await DoWorkAsync();
stopwatch.Stop();
Console.WriteLine($"İşlem süresi: {stopwatch.ElapsedMilliseconds}ms");

// Environment.TickCount64 - monotonik (64-bit)
var start = Environment.TickCount64;
await DoWorkAsync();
var elapsed = Environment.TickCount64 - start;

// .NET 8+ TimeProvider ile
var timeProvider = TimeProvider.System;
var timestamp = timeProvider.GetTimestamp();
await DoWorkAsync();
var elapsed = timeProvider.GetElapsedTime(timestamp);

Ne Zaman Monotonik Saat Kullanılmalı?

  • Timeout ölçümü

  • Performans ölçümü (benchmark)

  • Retry backoff hesaplaması

  • Circuit breaker süreleri

  • Cache TTL (yerel)

Ne Zaman Duvar Saati Kullanılmalı?

  • Kullanıcıya gösterilecek zaman damgaları

  • Audit log (mutlak zaman gerekli)

  • Veritabanı kayıt zamanı (created_at)

  • Cron job zamanlaması


4. Lamport Saatleri (Mantıksal Saatler)

Fiziksel saatler güvenilmez olduğunda, mantıksal saatler (logical clocks) devreye girer. Lamport saati, Leslie Lamport tarafından 1978'de önerilen, olayların nedensel sırasını (causal ordering) belirlemek için kullanılan bir sayaçtır.

Kurallar:

  1. Her düğümün bir sayacı vardır (başlangıçta 0).

  2. Her yerel olayda sayaç 1 artırılır.

  3. Mesaj gönderirken, gönderen sayaç değerini mesaja ekler.

  4. Mesaj alındığında, alıcı sayacını max(kendi_sayacı, gelen_sayac) + 1 olarak günceller.

Örnek:

text

Node A: (1) → (2) → mesaj gönder (3)
Node B:            → mesaj al (max(0,3)+1 = 4) → (5)

Lamport Saatlerinin Sınırı: A ve B olayları arasında A → B (A, B'den önce) ilişkisi varsa, Lamport(A) < Lamport(B) doğrudur. Ancak tersi her zaman doğru değildir: Lamport(A) < Lamport(B) ise A'nın B'den önce olduğu kesin değildir (eşzamanlı olabilirler).


5. Vector Clocks (Vektör Saatleri)

Vector clocks, Lamport saatlerinin sınırını aşarak eşzamanlılık (concurrency) tespiti yapar. Her düğüm, tüm düğümlerin sayaçlarını içeren bir vektör tutar.

Örnek:

text

Node A: [A:1, B:0, C:0]
Node B: [A:0, B:1, C:0]
Node C: [A:0, B:0, C:1]

Karşılaştırma:

  • V1 < V2 ise: V1, V2'den önce.

  • V1 > V2 ise: V1, V2'den sonra.

  • Aksi halde: Eşzamanlı (concurrent) – çakışma var!

Vector clocks, DynamoDB ve Riak gibi sistemlerde çakışma tespiti için kullanılır.


6. Hybrid Logical Clocks (HLC)

HLC, fiziksel saatin yakınlığını ve mantıksal saatin nedensel sıralamasını birleştirir. CockroachDB, MongoDB ve YugabyteDB gibi modern dağıtık veritabanları HLC kullanır.

Çalışma Prensibi:

  • HLC, bir (physical_time, logical_counter) çiftidir.

  • Fiziksel saat ilerledikçe, HLC de ilerler.

  • Aynı fiziksel zaman içinde birden fazla olay olursa, logical counter artırılır.

  • Bir düğümün saati gerideyse, diğer düğümün HLC'sini alarak senkronize olur.

Avantajları:

  • Fiziksel saate yakın (okunabilir zaman damgaları)

  • Nedensel sıralama garantisi

  • Clock skew'e karşı dayanıklı


7. .NET'te Zaman Yönetimi En İyi Pratikleri

A. TimeProvider (NET 8+) Kullanın

TimeProvider, .NET 8 ile gelen, test edilebilir zaman soyutlamasıdır.

csharp

public class OrderService
{
    private readonly TimeProvider _timeProvider;

    public OrderService(TimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
    }

    public async Task<Order> CreateOrderAsync(CreateOrderCommand command)
    {
        var now = _timeProvider.GetUtcNow();  // Test edilebilir
        var timestamp = _timeProvider.GetTimestamp();  // Monotonik

        var order = new Order
        {
            CreatedAt = now,  // Duvar saati (audit için)
            ...
        };

        await SaveAsync(order);

        var elapsed = _timeProvider.GetElapsedTime(timestamp);
        _logger.LogInformation("Sipariş oluşturma süresi: {Elapsed}", elapsed);

        return order;
    }
}

// Test'te FakeTimeProvider kullanımı
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
var service = new OrderService(fakeTime);

B. Timeout için Stopwatch Kullanın

csharp

// ❌ YANLIŞ - Duvar saati kullanımı
var start = DateTime.UtcNow;
await DoWorkAsync();
if (DateTime.UtcNow - start > TimeSpan.FromSeconds(5))
    throw new TimeoutException();

// ✅ DOĞRU - Monotonik saat kullanımı
var stopwatch = Stopwatch.StartNew();
await DoWorkAsync();
if (stopwatch.Elapsed > TimeSpan.FromSeconds(5))
    throw new TimeoutException();

C. CancellationTokenSource ile Timeout

csharp

// CancellationTokenSource, dahili olarak monotonik saat kullanır
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await DoWorkAsync(cts.Token);

D. Distributed Locking'de Fencing Token

csharp

// Kilit alındığında monotonik artan token alın
var lockHandle = await _distributedLock.AcquireAsync("resource");
var fencingToken = lockHandle.FencingToken;  // Monotonik

// Kaynağa yazarken token'ı gönder
await _resource.WriteAsync(data, fencingToken);
// Kaynak, daha düşük token'lı istekleri reddeder

E. Event Sourcing'de Zaman Damgaları

csharp

public record OrderCreatedEvent(
    Guid OrderId,
    DateTimeOffset OccurredAt,   // Duvar saati (audit)
    long SequenceNumber,          // Monotonik sıra numarası
    string CorrelationId
);

Event'lerin sıralaması için sequence number kullanın, zaman damgasına güvenmeyin.


8. En İyi Pratikler

Pratik Açıklama
Süre Ölçümü için Monotonik Saat Kullanın Stopwatch, Environment.TickCount64, TimeProvider.GetTimestamp()
Mutlak Zaman için Duvar Saati Kullanın Audit log, kullanıcıya gösterilecek zaman damgaları
Sıralama için Mantıksal Saat Kullanın Lamport, Vector Clock veya HLC
NTP ile Saatleri Senkronize Edin Tüm düğümlerde NTP aktif olsun
Zaman Damgalarını UTC Saklayın Yerel saat dilimleri karışıklık yaratır
Sequence Number Kullanın Event sıralaması için zaman damgasına güvenmeyin
Fencing Token Kullanın Distributed locking'de clock skew'e karşı koruma
TimeProvider ile Test Edilebilir Olun Zaman bağımlı kodları test edilebilir hale getirin
Clock Skew'i İzleyin Düğümler arası saat farkını metrik olarak izleyin
Time API'lerini Kullanın .NET 8+ TimeProvider, TimeSpan, DateTimeOffset

9. Gerçek Dünya Senaryosu: E-Ticaret Sipariş Sıralaması

Senaryo: Bir e-ticaret uygulamasında, sipariş event'leri 3 servis tarafından üretiliyor: Order Service, Payment Service ve Shipment Service. Event'ler bir Kafka topic'ine yazılıyor ve sıralı olarak işlenmesi gerekiyor.

Problem: Servislerin saatleri 50ms kaymış. Event'ler yanlış sırada işleniyor.

Çözüm: HLC (Hybrid Logical Clock) veya sequence number kullanın.

csharp

public class HybridLogicalClock
{
    private long _lastPhysicalTime;
    private int _logicalCounter;
    private readonly object _lock = new();

    public (long PhysicalTime, int Counter) Now()
    {
        lock (_lock)
        {
            var physicalNow = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            
            if (physicalNow > _lastPhysicalTime)
            {
                _lastPhysicalTime = physicalNow;
                _logicalCounter = 0;
            }
            else
            {
                _logicalCounter++;  // Aynı fiziksel zaman içinde
            }

            return (_lastPhysicalTime, _logicalCounter);
        }
    }

    public void Update((long PhysicalTime, int Counter) received)
    {
        lock (_lock)
        {
            var physicalNow = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            var maxPhysical = Math.Max(physicalNow, Math.Max(_lastPhysicalTime, received.PhysicalTime));

            if (maxPhysical == _lastPhysicalTime && maxPhysical == received.PhysicalTime)
                _logicalCounter = Math.Max(_logicalCounter, received.Counter) + 1;
            else if (maxPhysical == _lastPhysicalTime)
                _logicalCounter++;
            else if (maxPhysical == received.PhysicalTime)
                _logicalCounter = received.Counter + 1;
            else
                _logicalCounter = 0;

            _lastPhysicalTime = maxPhysical;
        }
    }
}

Bu sayede, event'ler doğru sırada işlenir; clock skew sorun olmaktan çıkar.


Sonuç

Dağıtık sistemlerde zaman, göründüğü kadar basit değildir. Clock skew, fiziksel saatlerin doğal bir sonucudur ve sıralama, timeout, locking gibi kritik işlemlerde ciddi sorunlara yol açar. Monotonik saatler, süre ölçümü için güvenli tek seçenektir; asla geriye gitmez. Lamport saatleri, Vector Clocks ve Hybrid Logical Clocks, nedensel sıralamayı fiziksel saatlerden bağımsız olarak sağlar. .NET'te Stopwatch, Environment.TickCount64 ve TimeProvider ile monotonik saatleri kullanabilir; TimeProvider ile test edilebilir zaman yönetimi yapabilirsiniz. Unutmayın: Dağıtık sistemlerde "şu an" kavramı, her düğümde farklıdır. Güvenilir sistemler, zamanı bir gerçek olarak değil, bir "tahmin" olarak ele alır ve gerekli önlemleri alır.

Tüm yazılar Soru ya da yorumun mu var? Yaz