iToverDose/Yazılım· 7 TEMMUZ 2026 · 16:03

PostgreSQL'de deadlock algoriması nasıl çalışır ve üretimdeki yaygın hatalar

PostgreSQL'in deadlock algoriması çalışma mantığı, neden sadece belirli zaman aralıklarında devreye girdiği ve üretim ortamlarında karşılaşılan en yaygın hatalardan biri olan sonsuz retry senaryosu hakkında derinlemesine bir inceleme sunuyoruz.

DEV Community4 dk okuma0 Yorumlar

PostgreSQL'in transaction yönetiminde karşılaşılan en kafa karıştırıcı hatalardan biri olan deadlock (ölü kilitlenme), uygulamaların üretim ortamında beklenmedik şekilde çökmesine neden olabiliyor. Bu durum, özellikle aynı veriye erişen farklı işlemlerin, kaynakları farklı sıralarda kilitlemeye çalıştığı senaryolarda ortaya çıkıyor. Peki PostgreSQL, bir deadlock'ı nasıl tespit ediyor ve üretim ortamındaki yaygın hatalardan nasıl kaçınabilirsiniz?

PostgreSQL'in deadlock tespit mekanizması

PostgreSQL'in lock manager, sistemdeki kilit bekleyen ve kilitleri elinde tutan işlemleri izleyen bir yapıdır. Bir işlem bir kilit beklemeye başladığında, bu bekleme durumu kaydedilir ve işlem uyku moduna geçer. Ancak PostgreSQL, deadlock aramasını sadece `deadlock_timeout` süresi dolduğunda başlatır — varsayılan olarak 1 saniye. Bu tasarımın arkasındaki mantık, çoğu kilit beklemesinin normal çalışma yükünde milisaniyeler içinde çözülmesidir. Örneğin, kısa bir UPDATE işleminin tamamlanmasını beklemek, sürekli olarak karmaşık bir grafik algoritması çalıştırmaktan çok daha verimlidir.

Deadlock tespiti için gereken bek-for grafiği, kilit bekleyen ve kilitleri elinde tutan süreçleri temsil eden bir yönlendirilmiş grafik oluşturur. PostgreSQL'in kaynak kodundaki deadlock.c dosyasında yer alan algoritma, önce yumuşak çakışmaları (iki işlemin de aynı kilitleri beklediği durumlar) çözmeye çalışır. Eğer bu yöntemle bir çözüm bulunamazsa, sistem bir deadlock olduğunu varsayar. Bu durumda, deadlock tespitini başlatan arka plan işlemi otomatik olarak sonlandırılır ve hata mesajı olarak ERROR: deadlock detected (SQLSTATE 40P01) üretilir. Transaction geri alınır, kilitler serbest bırakılır ve diğer işlemler devam eder.

Aşağıda, PostgreSQL'in loglarında yer alan tipik bir deadlock durumunun örneği yer alıyor:

LOG: process 12345 still waiting for ShareLock on transaction 9876 after 1000.123 ms
DETAIL: Process holding the lock: 12399. Wait queue: 12345.
CONTEXT: while updating tuple (5,12) in relation "accounts"
STATEMENT: UPDATE accounts SET balance = balance + 100 WHERE id = 99

ERROR: deadlock detected
DETAIL: Process 12345 waits for ShareLock on transaction 9876; blocked by process 12399.
       Process 12399 waits for ShareLock on transaction 9877; blocked by process 12345.
HINT: See server log for query details.

Bu log, iki işlemin birbirini nasıl kilitlediğini net bir şekilde ortaya koyuyor. PostgreSQL'in pg_stat_database görünümünde deadlocks sayacı, her deadlock tespit edildiğinde otomatik olarak artar. Ancak, sistemde oluşan deadlock'ların geçmişini görmek için log'ları elle incelemek gerekiyor.

Üretim ortamında karşılaşılan yaygın hatalar

Sonsuz retry döngüsü ve root cause'un ihmal edilmesi

Birçok geliştirici, deadlock hatasıyla ilk kez karşılaştığında, yaygın bir çözüm olarak tüm veritabanı işlemlerini bir try-catch bloğuna alarak otomatik retry mekanizması ekliyor. Ancak bu yaklaşım, root cause'un çözülmeden bırakılması durumunda ciddi performans sorunlarına yol açabiliyor.

# Uygulama katmanında yapılan yaygın ama hatalı retry örneği
def transfer(src_id, dst_id, amount):
    for attempt in range(10):
        try:
            with conn.transaction():
                conn.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (amount, src_id))
                conn.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (amount, dst_id))
            return
        except DeadlockDetected:
            time.sleep(0.01)  # Geri çekilme süresi yetersiz ve root cause düzeltilmiyor
    raise

Bu durumda, özellikle aynı veriye erişen farklı işlemler aynı sırayla kilitleri almaya çalışıyorsa, retry sadece aynı deadlock döngüsünü yeniden oluşturuyor. Her retry denemesi, kilit bekleme süresini artırarak, yanıt sürelerinin milisaniyelerden saniyelere çıkmasına neden olabiliyor. Dahası, eğer retry sayısında bir sınırlama yoksa ve trafik ani artışlar gösteriyorsa, veritabanı CPU'su sürekli olarak deadlock tespit algoritmalarını çalıştırmakla meşgul hale gelebiliyor.

Doğru çözüm: kilit alma sırasının standardize edilmesi

Deadlock'ların en yaygın nedeni, farklı işlemlerin aynı kaynaklara erişirken farklı sıralarda kilit almasıdır. Bu sorunu çözmenin en etkili yolu, tüm uygulamalarda kilit alma sırasını standartlaştırmaktır. Örneğin, tablolar arası işlemlerde her zaman aynı sıralama kullanılmalı — ya id değerlerine göre artan sırayla ya da tablo adlarına göre alfabetik sırayla.

# Doğru kilit alma sırası ve exponential backoff ile retry

def transfer(src_id, dst_id, amount):
    lo, hi = sorted([src_id, dst_id])
    for attempt in range(3):
        try:
            with conn.transaction():
                # Kilitleri standart sırayla al
                conn.execute(
                    "SELECT id FROM accounts WHERE id IN (%s,%s) ORDER BY id FOR UPDATE",
                    (lo, hi),
                )
                # İşlemleri gerçekleştir
                conn.execute("UPDATE accounts SET balance = balance - %s WHERE id = %s", (amount, src_id))
                conn.execute("UPDATE accounts SET balance = balance + %s WHERE id = %s", (amount, dst_id))
            return
        except DeadlockDetected:
            time.sleep(0.05 * (2 ** attempt))  # Üstel geri çekilme
    raise

Bu yaklaşımda, retry sadece gerçekten yarış koşullarını (race conditions) yönetmek için kullanılıyor. Deadlock'ların oluşmasını engellemek içinse, uygulama katmanında kilit alma sırasının standardize edilmesi gerekiyor. Retry sayısı sınırlandırılmalı, exponential backoff uygulanmalı ve belirli bir eşiğin aşılması durumunda ekip uyarılmalıdır.

Üretim ortamında dikkat edilmesi gereken diğer senaryolar

Foreign key kilitlenmeleri

PostgreSQL, foreign key ilişkilerinde otomatik olarak FOR KEY SHARE kilitleri alır. Bu, eğer bir transaction bir foreign key ilişkisi üzerinden bir tabloya veri eklemeye çalışırken, ilişkili tabloda değişiklik yapmaya çalışırsa, deadlock oluşabilir. Örneğin:

  • Transaction A: INSERT INTO child_table (fk_id) VALUES (1) alır FOR KEY SHARE kilidini parent_table.id=1 üzerinde.
  • Transaction B: UPDATE parent_table SET id = 2 WHERE id = 1 alır daha güçlü bir kilit.
  • Transaction A: Daha sonra UPDATE parent_table SET id = 1 WHERE id = 2 çalıştırmaya çalışır.

Bu durumda, Transaction A ve B birbirini kilitleyebilir. Bu senaryoya karşı, foreign key ilişkisi üzerinden yapılan işlemlerde de kilit alma sırasını standartlaştırmak önemlidir.

Sonuç ve öneriler

PostgreSQL'in deadlock tespit mekanizması, sistemdeki kaynak yönetimini optimize etmek için tasarlanmış olsa da, üretim ortamında karşılaşılan deadlock'ların çoğu, uygulama katmanındaki kilit alma stratejilerinin eksikliğinden kaynaklanıyor. Geliştiricilerin, farklı kod yollarının aynı kaynaklara erişirken standart bir kilit alma sırasına uyması, retry mekanizmalarını kontrollü bir şekilde uygulaması ve log'ları yakından izlemesi gerekiyor. Bu sayede, sistem performansı kaybı yaşanmadan, deadlock'lar minimize edilebilir ve kullanıcı deneyimi sorunsuz hale getirilebilir.

Yapay zeka özeti

PostgreSQL'in deadlock tespit mekanizması, üretim ortamındaki yaygın hatalardan biri olan sonsuz retry döngüsünden kaçınma yolları ve kilit alma stratejileri hakkında detaylı bir rehber.

Yorumlar

00
YORUM BIRAK
ID #6WHV1R

0 / 1200 KARAKTER

İnsan doğrulaması

3 + 9 = ?

Editör onayı sonrası yayına girer

Moderasyon · Spam koruması aktif

Henüz onaylı yorum yok. İlk yorumu sen bırak.