İçeriğe Atla
Mustafa Erbay
Kariyer · 11 dk okuma · görüntülenme Read in English

Dağıtık Kilit Kullanımının On-Call Maliyeti

Dağıtık kilitlerin (distributed locks) operasyonel yükünü, on-call mühendislerine çıkardığı gizli faturaları ve daha basit alternatifleri inceliyorum.

100%

Mikroservis mimarisine geçiş yapan ekiplerin alet çantasındaki en popüler araçlardan biri dağıtık kilitlerdir (distributed locks). Birden fazla uygulama sunucusunun aynı kaynağa (örneğin bir sipariş bakiyesi, envanter kalemi veya fatura numarası) aynı anda erişmesini engellemek kulağa teoride kusursuz bir çözüm gibi gelir. Ancak pratikte, dağıtık kilitlerin sisteme getirdiği operasyonel karmaşıklık, gece yarısı telefon çaldıran on-call nöbetlerinin bir numaralı sebebi haline dönüşebiliyor.

Kendi geliştirdiğim bir yan ürünün backend altyapısında ve danışmanlık verdiğim büyük ölçekli bir e-ticaret platformunda bu mimarinin sınırlarını defalarca zorladım. Dağıtık kilitlerin kağıt üzerinde durduğu gibi durmadığını, ağdaki küçük bir gecikme dalgalanmasının veya veritabanındaki ufak bir kilitlenme (lock starvation) durumunun nasıl birer operasyonel kabusa dönüştüğünü bizzat yaşadım. Bu yazıda, dağıtık kilitlerin arkasındaki gizli operasyonel maliyeti, Redlock gibi algoritmaların sınırlarını ve on-call yükünü azaltacak alternatif tasarım kalıplarını kendi deneyimlerim üzerinden ele alıyorum.

Dağıtık Kilitlerin Çekici Dünyası ve Gerçek Hayattaki Bedeli

Dağıtık sistemlerde “race condition” (yarış durumu) olarak adlandırılan tutarsızlıkları çözmek için ilk akla gelen yöntem, küresel bir kilit mekanizması kurmaktır. Uygulama sunucularınız yatayda büyüdükçe (horizontal scaling), yerel bellek kilitleri (örneğin Go’daki sync.Mutex veya Java’daki synchronized blokları) işlevini yitirir. Bu noktada, tüm sunucuların görebileceği ortak bir bellek sunucusuna (genellikle Redis veya Consul) gidip “Ben bu X kaynağını kilitledim, işim bitene kadar kimse dokunmasın” demek çok mantıklı görünür.

Ancak bu yaklaşımın getirdiği en büyük bedel, sisteminizin artık tek bir noktadaki gecikmeye (latency) ve ağ durumuna (network partitioning) aşırı hassas hale gelmesidir. Dağıtık bir kilit kullandığınızda, uygulamanızın kritik bir iş mantığı (business logic) doğrudan ağ üzerinden yapılacak olan kilit edinme (lock acquisition) süresine bağımlı hale gelir. Yoğun kampanya dönemlerinde çok yüksek istek alan bir API’nin, kilit sunucusundaki küçük gecikmeler yüzünden nasıl domino etkisiyle çökebildiğine şahit oldum.

+------------------+      1. Kilit İsteği (SET NX)     +-------------------+
|  Uygulama Pod 1  | --------------------------------> |                   |
|                  | <-------------------------------- |   Redis Cluster   |
+------------------+       2. Kilit Alındı (OK)        |                   |
                                                       |                   |
+------------------+      3. Aynı Kilidi İsteme        |                   |
|  Uygulama Pod 2  | --------------------------------> |                   |
|                  | <-------------------------------- |                   |
+------------------+         4. Reddedildi (NIL)       +-------------------+

Kilit sunucusu ayakta olsa bile, istemci (client) tarafındaki kütüphanelerin kilit zaman aşımı (timeout) sürelerini yanlış yönetmesi, kilitlerin “asla serbest bırakılamaması” (deadlock) gibi durumları doğurur. Eğer kilit açma (unlock) komutu ağdaki bir paket kaybı yüzünden hedefine ulaşamazsa, o kaynak kilit süresi (TTL - Time to Live) bitene kadar kilitli kalır. Bu durum, on-call mühendisinin gece yarısı sistem loglarında aşağıdaki gibi satırları arayarak uykusuz kalması demektir:

[2026-05-30 03:14:22] ERROR [order-service] LockAcquisitionException: 
Could not acquire lock for resource 'order_982341'. 
Lock held by client 'pod-a-7d8f' with remaining TTL 28800ms.

Redis Redlock Algoritması ve Saat 03:00’te Gelen On-Call Telefonu

Redis üzerinde dağıtık kilit kurmak denince akla gelen ilk standart Redlock algoritmasıdır. Redlock, tek bir Redis düğümünün (node) çökme ihtimaline karşı, en az 5 farklı Redis düğümünden çoğunluk (quorum) onayı alarak kilit edinmeyi hedefler. Algoritma kağıt üzerinde harika çalışır: 5 düğümden 3’ü onay verirse kilit sizindir. Ancak Martin Kleppmann’ın da zamanında belirttiği gibi, Redlock fiziksel saatlerin (wall-clock time) doğruluğuna ve ağ gecikmelerine aşırı bağımlıdır.

Bir ERP sisteminde, malzeme ihtiyaç planlaması (MRP) çalıştıran bir arka plan işinde Redlock kullanmıştık. Sürecin tipik çalışma süresine göre kilidin TTL süresini cömert bir payla belirlemiştik. Ancak arka planda çalışan sanal makinenin (VM) vCPU hırsızlığı (CPU steal time) ve o esnada tetiklenen uzun bir Garbage Collection (GC) duraksaması yüzünden, uygulama sunucusu uzunca bir süre tamamen dondu. Bu donma esnasında, uygulama zamanın akmadığını sanırken, Redis düğümlerindeki TTL sayacı akmaya devam etti.

Bu olayı debug ederken çıkardığımız CPU ve ağ metrikleri, bize Redlock’un operasyonel olarak ne kadar kırılgan olduğunu gösterdi. Düğümler arasındaki saat senkronizasyonu (NTP) saniyelik de olsa saptığında, çoğunluk hesabı tamamen şaşıyordu. Redlock kullanıyorsanız, sadece Redis’i değil, tüm sunucuların NTP durumunu da izlemek zorundasınız. Bu da on-call listesine yeni bir alarm kalemi eklemekten başka bir işe yaramıyor.

Veritabanı Düzeyinde Kilitlenme (Advisory Locks) Neden Çoğu Zaman Yeterlidir?

Eğer sisteminizde zaten PostgreSQL gibi güçlü bir ilişkisel veritabanı varsa, dağıtık kilit yönetimi için Redis veya Consul gibi ek bir katmanı mimariye dahil etmek çoğu zaman gereksiz bir operasyonel yüktür. PostgreSQL, uygulama mantığından bağımsız, tamamen bellekte çalışan ve fiziksel tabloları kilitlemeyen “Danışman Kilitleri” (Advisory Locks) sunar.

Daha önce ele aldığım PostgreSQL index stratejileri yazımda da belirttiğim gibi, veritabanının kendi iç mekanizmaları ACID garantilerine sahip olduğu için, tutarlılık konusunda Redis’ten çok daha güvenilirdir. PostgreSQL’de bir anahtar (key) üzerinden kilit almak sadece tek bir SQL sorgusuna bakar:

-- 64-bitlik bir anahtar üzerinde transaction seviyesinde kilit al
SELECT pg_advisory_xact_lock(8472910472);

Bu yöntemin en büyük avantajı, kilit işleminin doğrudan veritabanı bağlantısına (session/connection) bağlı olmasıdır. Eğer uygulamanız çökerse, ağ bağlantısı koparsa veya sunucu fiziksel olarak kapanırsa, PostgreSQL bu durumu anında fark eder ve o bağlantıya ait tüm advisory kilitleri otomatik olarak serbest bırakır (cleanup). Redis’teki gibi “kilit havada kaldı, TTL süresini bekleyelim” derdi yoktur.

Ancak bu yöntemin de kendi trade-off’ları mevcuttur. Eğer uygulamanız çok yüksek trafik alıyorsa ve saniyede on binlerce kilit isteği yapıyorsa, PostgreSQL connection pooler (örneğin PgBouncer) limitlerine takılabilirsiniz. Transaction seviyesinde kilitler session-pooling modunda PgBouncer ile her zaman düzgün çalışmayabilir. Bu sınırı bilerek tasarım yapmak gerekir.

Kriter Redis (Redlock) PostgreSQL (Advisory Locks)
Bağlantı Kaybı Davranışı TTL süresince kilit kilitli kalır Bağlantı koptuğu an kilit açılır
Operasyonel Karmaşıklık Yüksek (5 bağımsız düğüm yönetimi) Düşük (Mevcut DB kullanılır)
Performans (Throughput) Çok Yüksek (>50k ops/sec) Orta (~5k-10k ops/sec)
Güvenilirlik (Consistency) Saat senkronizasyonuna (NTP) bağlı ACID garantili, saatten bağımsız

Ağ Gecikmeleri ve GC Pause (Garbage Collection) Yarattığı Kilit Sorunları

Dağıtık kilitlerin pratikte patlamasının en sinsi sebebi, yazılım dünyasının fiziksel gerçeklikleri göz ardı etmesidir. Ağ asenkrondur; bir paketin A noktasından B noktasına gitmesinin ne kadar süreceğini asla kesin olarak bilemezsiniz. İşin içine sanallaştırma, hypervisor katmanları ve runtime düzeyindeki Garbage Collection duraksamaları girdiğinde, kilitlerin geçerlilik süreleri tamamen birer tahminden ibaret hale gelir.

JVM tabanlı dillerde (Java, Kotlin, Scala) veya .NET runtime üzerinde çalışan büyük uygulamalarda karşılaşılan “Stop-the-World” GC duraksamaları, dağıtık kilitlerin en büyük düşmanıdır. Uygulama sunucunuz 5 saniyeliğine durduğunda, işletim sistemi seviyesindeki TCP soketleri buffer’lamaya devam edebilir ancak uygulama kodunuz donar.

Uygulama Akışı:
[00:00.000] -> Kilit Edinildi (TTL: 5 saniye)
[00:00.100] -> GC Duraksaması Başladı (Stop-the-World)
[00:04.800] -> GC Duraksaması Bitti (Geçen süre: 4.7sn)
[00:04.900] -> Veritabanına Yazma İsteği Gönderildi (Kilit aslında 0.1 saniye sonra düşecek!)
[00:05.100] -> İstek DB'ye ulaştı, ancak kilit çoktan düştü ve başka pod tarafından alındı.

Bunu çözmek için kilit kontrolü sonrasına eskrim jetonları (fencing tokens) koymak gerekir. Fencing token, her kilit alındığında artan bir sayaçtır (monotonically increasing counter). Veritabanına yazma yaparken bu sayacı da gönderirsiniz. Eğer veritabanındaki güncel sayaç sizin gönderdiğinizden büyükse, yazma işlemi reddedilir. RFC 7230 ve benzeri dağıtık sistem prensiplerinde de önerilen bu yöntem, kilitlerin arkasına ikinci bir güvenlik duvarı örer ancak yazılım mimarisini de bir hayli karmaşıklaştırır.

Idempotency ve Sınırlandırılmış Tasarımlar: Kilide Hiç İhtiyaç Duymamak

Peki, bu kadar operasyonel dertle uğraşmak yerine, kilide olan ihtiyacı tamamen ortadan kaldırabilir miyiz? Çoğu zaman evet. Yazılım mimarisi, kod yazmaktan ziyade organizasyonel ve veri akışsal bir tasarımdır. Kilit kullanmak yerine, işlemleri doğal olarak mükerrer çalışmaya dayanıklı (idempotent) hale getirmek, on-call maliyetini sıfıra indirmenin en temiz yoludur.

Örneğin, bir kullanıcının bakiyesinden para düşme işlemini ele alalım. Dağıtık kilit kullanarak bakiye kontrolü yapmak ve ardından güncellemek yerine, veritabanının kendi atomik operasyonlarını ve “optimistic locking” yöntemini kullanabiliriz.

# Kilit kullanmayan, optimistic concurrency kontrolü içeren FastAPI endpoint örneği
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
import psycopg2

app = FastAPI()

class PaymentRequest(BaseModel):
    account_id: int
    amount: float
    version: int  # Eskrim ve optimistik kontrol için sürüm numarası

@app.post("/withdraw")
def withdraw_money(req: PaymentRequest):
    conn = psycopg2.connect("dbname=finance user=postgres")
    cursor = conn.cursor()
    
    # Sadece versiyon eşleşiyorsa ve bakiye yetiyorsa güncelle
    cursor.execute(
        """
        UPDATE accounts 
        SET balance = balance - %s, version = version + 1
        WHERE id = %s AND version = %s AND balance >= %s
        """,
        (req.amount, req.account_id, req.version, req.amount)
    )
    
    if cursor.rowcount == 0:
        conn.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Bakiye yetersiz veya veri sürümü güncel değil. Lütfen tekrar deneyin."
        )
    
    conn.commit()
    return {"status": "success"}

Bu tasarımda hiçbir dağıtık kilit sunucusuna ihtiyaç yoktur. Eğer aynı anda iki istek gelirse, biri versiyon numarasını güncelleyeceği için diğeri otomatik olarak başarısız olur (rowcount == 0 döner). Uygulama katmanında bu hatayı yakalayıp kullanıcıya “Lütfen tekrar deneyin” diyebilir veya arka planda güvenli bir şekilde yeniden deneme (retry) mekanizması işletebiliriz. Bu yaklaşım, sistemin ölçeklenmesini (scaling) engelleyen tüm darboğazları ortadan kaldırır.

PostgreSQL index stratejileri yazımda da bahsettiğim üzere, bu tarz tasarımlarda doğru indekslerin bulunması sorgu hızını doğrudan etkiler. id ve version alanları üzerinde benzersiz bir indeks (unique index) olması, veritabanı motorunun bu güncellemeyi milisaniyeler içinde tamamlamasını sağlar.

On-Call Maliyetini Düşürmek İçin Pratik Runbook ve İzleme Stratejileri

Eğer mevcut mimaride dağıtık kilit kullanmak zorundaysanız ve bunu değiştiremiyorsanız, en azından on-call mühendislerinin hayatını kolaylaştıracak operasyonel önlemler almalısınız. Bir kilit sistemi izlenmiyorsa, kör uçuş yapıyorsunuz demektir.

İlk olarak, kilit edinme sürelerini ve kilit bekleme sürelerini (lock wait time) mutlaka metrikleştirin. Prometheus ve Grafana kullanarak bu metrikleri görselleştirmek, potansiyel bir deadlock durumunu saatler öncesinden fark etmenizi sağlar.

# Prometheus metrik formatı örneği
distributed_lock_acquisition_duration_seconds_bucket{resource="order_payout", le="0.1"} 1240
distributed_lock_acquisition_duration_seconds_bucket{resource="order_payout", le="0.5"} 145
distributed_lock_acquisition_duration_seconds_bucket{resource="order_payout", le="1.0"} 12
distributed_lock_acquisition_duration_seconds_bucket{resource="order_payout", le="+Inf"} 3

Yukarıdaki metriklerde, +Inf (sonsuz) veya 1 saniyenin üzerindeki kilit edinme süreleri artıyorsa, bu durum arka planda bir thread havuzunun (thread pool) tükendiğine veya kilit sunucusunun darboğaza girdiğine işarettir. On-call alarm eşiklerinizi bu metriklerin 99. yüzdelik dilimine (p99 latency) göre ayarlayın.

Ayrıca, on-call runbook belgenize şu acil durum adımlarını ekleyin:

  1. Zombi Kilitleri Temizleme: Hangi podun hangi kilidi tuttuğunu bulmak için Redis üzerinde SCAN ve TTL komutlarını çalıştırın.
  2. Bağlantı Kesme: Eğer PostgreSQL advisory lock kullanılıyorsa, kilitlenen session’ı pg_terminate_backend(pid) ile sonlandırın.
  3. Graceful Shutdown: Uygulama podları kapatılırken (SIGTERM sinyali alındığında) ellerindeki kilitleri serbest bırakacak temizlik fonksiyonlarının (shutdown hooks) çalıştığından emin olun.

Sonuç

Net pozisyonum şudur: Dağıtık kilitler, sistem mimarisindeki tasarım eksikliklerini ve veri akışlarındaki plansızlığı yamamak için kullanılan son çaredir; ilk tercih olmamalıdır. Bir sisteme dağıtık kilit eklemeden önce kendinize şu soruyu sorun: “Ben bu tutarsızlığı veritabanı seviyesinde optimistik kilitlerle, benzersiz kısıtlamalarla (unique constraints) veya event-driven bir mimariyle çözebilir miyim?”

Eğer cevap evet ise, dağıtık kilit seçeneğini masadan kaldırın. Gece saat 03:00’te telefon sesiyle uyanmayan, stabil çalışan ve ağ dalgalanmalarından etkilenmeyen bir sistem kurmanın yolu, karmaşıklığı yönetmekten değil, karmaşıklığı hiç var etmemekten geçer. Operasyonel mükemmellik, en gelişmiş araçları kullanmak değil, en basit tasarımlarla en yüksek dayanıklılığı (resilience) elde etmektir.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

Dağıtık kilit kullanımının operasyonel maliyetini azaltmak için hangi alternatif tasarım kalıplarını kullanmalıyım?
Benim deneyimim, dağıtık kilitlerin yerine daha basit ve ölçeklenebilir çözümler kullanmayı öneriyor. Örneğin, idempotent işlemler veya event-driven mimari gibi yaklaşımlar, sistemdeki karmaşıklığı azaltabilir ve on-call yükünü hafifletebilir.
Dağıtık kilitleri uygularken nelere dikkat etmeliyim?
Benim deneyimim, dağıtık kilitleri uygularken ağ dalgalanmaları, veritabanı kilitlenmeleri ve sunucu hataları gibi durumlara karşı hazırlıklı olmak gerektiğini gösteriyor. Bu nedenle, dağıtık kilitlerin uygulanması sırasında dikkatli bir şekilde planlama ve test yapmalı, ayrıca alternatif çözümler için de bir yol haritası belirlemeliyim.
Redlock gibi algoritmaların sınırları nelerdir?
Benim deneyimim, Redlock gibi algoritmaların dağıtık kilitlerin uygulanması sırasında faydalı olabileceğini, ancak sınırlarının da olduğunu gösteriyor. Örneğin, ağ dalgalanmaları veya sunucu hataları gibi durumlarda, bu algoritmaların çalışması aksayabilir. Bu nedenle, bu algoritmaları kullanırken dikkatli bir şekilde değerlendirme yapmalı ve alternatif çözümler için de bir planı olmalı.
Dağıtık kilitlerin yerine idempotent işlemler kullanmanın avantajları nelerdir?
Benim deneyimim, idempotent işlemlerin dağıtık kilitlerin yerine kullanıldığında, sistemdeki karmaşıklığın azaltabileceğini ve on-call yükünün hafifletebileceğini gösteriyor. İdemponent işlemler, aynı işlemi birden fazla kez çalıştırılmasının sistemde herhangi bir olumsuz etkiye neden olmayacağı anlamına geliyor, bu da sistemdeki güvenilirliği artırabilir.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar