PostgreSQL Kilitlerini Okumak: Blocking, Deadlock ve Uzun Transaction'lar
Canlı Bekleme Grafı, pg_blocking_pids(), 40P01 Deadlock Tespiti ve L1–L5 Laboratuvar Kanıtı
PostgreSQL kilit bekleme grafını okuma, doğrusal bloklama ile döngüsel deadlock ayrımı, idle-in-transaction sızıntılarını teşhis etme ve L1–L5 laboratuvar kanıtlarıyla doğrulanmış müdahale adımları.

1. Belirti Triyajı: Kilit Beklemesini Kaynak Tüketiminden Ayırmak
İstemci uygulamalarında anlık gecikme (latency) artışları, bağlantı havuzu tükenmesi ve basamaklı HTTP 504 Gateway Timeout hataları yaşandığında, ekiplerin ilk refleksi genellikle donanım yetersizliğine—yüksek CPU, bellek darboğazı veya disk I/O doyumuna—odaklanmaktır. Oysa Çok Sürümlü Eşzamanlılık Denetimi (MVCC) mimarisiyle çalışan PostgreSQL sistemlerinde, en ağır verim düşüşleri çoğu zaman CPU kullanımı %15'in altındayken ve disk I/O tamamen rölantideyken gerçekleşir.
İstemci İstek Dalgası
│
▼
┌──────────────┐
│ Bağlantı │──► Havuz doldu (max_connections sınırına ulaşıldı)
│ Havuz Sınırı │
└──────────────┘
│
CPU > %85 mi?
├── EVET ──► Sorgu planı regresyonu, eksik indeks, hash join disk taşması
└── HAYIR ──► KİLİT BEKLEMESİ / BLOKLANAN OTURUMLAR
│
├── Doğrusal Bekleme Zinciri (Lock Wait / Blocking)
├── Döngüsel Bağımlılık (Deadlock -> SQLSTATE 40P01)
└── Unutulmuş Açık Transaction (idle in transaction)Üretim sistemlerinde aceleci ve yıkıcı komutlar çalıştırmadan önce üç temel arıza profilini birbirinden ayırmak şarttır:
- 1CPU / Disk Doyumu: Tablo taramaları (
Seq Scan), kartezyen döngüler veya diske dökülen sıralama işlemleri (temporary files) nedeniyle tüm çekirdekler hesaplama yapmaktadır. - 2Kilitsiz Bağlantı Doyumu: İstemciler veritabanının yeni backend süreci açma veya havuzlayıcının bağlantı atama hızından daha hızlı istek açmaktadır; sorgular hızlı biter ancak kapıda kuyruk oluşur.
- 3Kilit Bekleme Darboğazı: Backend süreçleri atanmıştır fakat işlem durumu çalışır halden
wait_event_type = 'Lock'durumuna geçmiştir. Oturumlar, başka bir oturum tarafından tutulan satır, tablo veya transaction kilidini bekleyerek uyumaktadır.
2. PostgreSQL Kilit Mekanizması ve MVCC'nin Önlemediği Çakışmalar
PostgreSQL'in MVCC mimarisinin temel sloganı genellikle *"Okuyucular yazarları engellemez, yazarlar da okuyucuları engellemez"* şeklinde özetlenir. Bu kural, standart SELECT okumaları ile eşzamanlı UPDATE, DELETE veya INSERT işlemleri arasında geçerli olsa da, standart izolasyon seviyelerinde (READ COMMITTED) kilitlenme yaşanmayacağı gibi yanıltıcı bir güven yaratır.
MVCC şunları önlemez:
- •Eşzamanlı Yazma-Yazma Çakışmaları: İki veya daha fazla eşzamanlı işlem aynı satırı
UPDATEveyaDELETEile değiştirmeye çalıştığında ya daSELECT ... FOR UPDATEile açıkça satır kilidi istediğinde, kilit yalnızca ilk işleme verilir. İkinci işlem, ilk işlemCOMMITveyaROLLBACKyapana kadar onun transaction ID'si üzerinde kilit kuyruğunda bekletilir. - •DDL ve DML Çakışmaları:
ALTER TABLE,CONCURRENTLYkullanılmadan açılanCREATE INDEX,DROP TABLEveyaVACUUM FULLişlemleriAccessExclusiveLocktalep eder. Bu kilit, masum birSELECTsorgusunun tuttuğuAccessShareLockdahil istisnasız tüm kilit modlarıyla çakışır. Arka planda unutulmuş tek bir uzun SELECT, bekleyen bir DDL'i bloke eder; o DDL de arkasından gelen binlerce yeni SELECT sorgusunu sıraya dizerek tüm veritabanını felç eder. - •Transaction Ömrü Kuralı: Bir transaction içinde alınan satır kilitleri, SQL ifadesi biter bitmez bırakılmaz. İlgili
UPDATEifadesi 1 milisaniyede bitmiş olsa bile, kilit o oturumCOMMITveyaROLLBACKgönderene kadar satır üzerinde asılı kalır.
3. Canlı Bekleme Grafını Okumak: pg_stat_activity ve pg_blocking_pids()
Kilit beklemesi başladığında doğrudan pg_locks tablosunu birden fazla self-join ile birleştirmeye çalışmak sıklıkla yanıltıcı sonuçlar üretir; çünkü pg_locks PostgreSQL'in kilit kuyruk önceliğini ve uyumluluk matrisini tek başına göstermez.
Modern PostgreSQL sürümlerinde (v14–18), motorun çekirdek bekleme kuyruğunu doğrudan sorgulayan yerleşik pg_blocking_pids(pid) fonksiyonu standart tanı aracıdır. Bu fonksiyon, hedef oturumu doğrudan veya dolaylı olarak engelleyen tüm süreçlerin PID dizisini döndürür.
▸Kök Bloklayıcıyı Saptayan Canlı Tanı Sorgusu
-- Teşhis Scripti: Kilit Zinciri ve Kök Bloklayıcı Analizi
SELECT
blocked.pid AS blocked_pid,
blocked.usename AS blocked_user,
blocked.application_name AS blocked_app,
blocked.state AS blocked_state,
blocked.wait_event_type,
blocked.wait_event,
ROUND(EXTRACT(EPOCH FROM (clock_timestamp() - blocked.state_change))::numeric, 2) AS wait_duration_sec,
pg_blocking_pids(blocked.pid) AS direct_blocker_pids,
blocker.pid AS blocker_pid,
blocker.usename AS blocker_user,
blocker.application_name AS blocker_app,
blocker.state AS blocker_state,
ROUND(EXTRACT(EPOCH FROM (clock_timestamp() - blocker.xact_start))::numeric, 2) AS blocker_xact_age_sec,
blocker.query AS blocker_current_query
FROM pg_stat_activity blocked
CROSS JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b_pid
JOIN pg_stat_activity blocker ON blocker.pid = b_pid
WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0
ORDER BY wait_duration_sec DESC;▸"En Yaşlı Sorgu" Yanılgısı
Kriz anında yapılan en yaygın hata, pg_stat_activity tablosunu salt query_start veya xact_start süresine göre sıralayıp en üstteki oturumu suçlu ilan etmektir.
- •
xact_start: Transaction bloğunun başladığı an (BEGIN). - •
query_start: Mevcut SQL cümlesinin sunucuya iletildiği an. - •
state_change: Oturumun durum değiştirdiği an (örneğin kilit beklemesine girdiği veyaidle in transactiondurumuna geçtiği an).
30 dakikadır açık olan bir analitik sorgu yalnızca paylaşımlı okuma kilidi tutuyor olabilir ve kimseyi engellemez. Buna karşılık, 10 saniye önce bir satırı güncelleyip ardından uygulama tarafında hata aldığı için idle in transaction durumunda kalan bir mikroservis, arkasından gelen yüzlerce işlemi kilitleyebilir. Teşhiste her zaman bekleyen oturumlardan (pg_blocking_pids) geriye doğru kök bloklayıcıya yürünmelidir.
4. Doğrusal Bloklama ile Döngüsel Deadlock Ayrımı
Bu iki olay arasındaki operasyonel fark kesin çizgilerle ayrılmalıdır:
- 1Doğrusal Bloklama (Linear Blocking): Oturum A Kilit 1'i tutar, Oturum B Kilit 1'i bekler. Oturum A sonlanmadığı sürece Oturum B sonsuza kadar bekler (bir
lock_timeouttanımlanmamışsa). Veritabanı motoru bu duruma kendiliğinden müdahale etmez. - 2Döngüsel Deadlock: Oturum A Satır 1'i kilitleyip Satır 2'yi ister; aynı anda Oturum B Satır 2'yi kilitleyip Satır 1'i ister. İki taraf da diğerinin kilidi bırakmasını beklediğinden sistem kilitlenir.
- •PostgreSQL, arka planda bir deadlock denetleyicisi çalıştırır. Bekleme süresi
deadlock_timeout(varsayılan:1000ms) değerini aştığında motor kilit grafını tarar. - •Döngü tespit edildiğinde PostgreSQL oturumlardan birini kurban (victim) seçer, işlemini SQLSTATE 40P01 koduyla anında iptal eder ve transaction'ını geri alır.
- •Önemli Gerçek: Kurban abort edildiği anda döngü çözülür ve canlı kilit grafı normale döner. Bu nedenle gerçekleşmiş bir deadlock'un canlı
pg_stat_activityüzerinden adli analizi yapılamaz; tek kanıt PostgreSQL sunucu loglarıdır.
5. Kontrollü Laboratuvar Kanıtı: L1–L5 Senaryoları
Bu rehberdeki tüm iddialar, sentetik ve izole Docker laboratuvarında gerçek PostgreSQL motoru üzerinde yürütülerek kanıtlanmıştır.
Test Ortamı:
- •Motor Sürümü: PostgreSQL 17.11 (Debian 17.11-1.pgdg13+2), 64-bit
- •İmaj Özeti (Digest):
sha256:67f41722b7a8cbdb868a44a4995c846eddfdc2973bccb291ce937dce88ad5675 - •Parametreler:
log_lock_waits = on,deadlock_timeout = 1000ms,log_line_prefix = '%m [%p] %q%u@%d app=%a ' - •Fixtür: İki hesap satırı:
id=1($1000.00),id=2($500.00).
▸Lab L1 — Basit Bloklama Kanıtı
- •Senaryo: Oturum A (
app_checkout_a, PID 113)id=1satırını güncelledi. Oturum B (app_batch_b, PID 120) aynı satırı güncellemek istedi. - •Örneklenen Telemetri:
blocked_pid : 120 (app_batch_b)
state : active
wait_event_type : Lock
wait_event : transactionid
blocking_pids : {113}
blocker_pid : 113 (app_checkout_a)
blocker_state : idle in transaction- •Sonuç: Oturum A
ROLLBACKgönderdiğinde Oturum B 0.04 saniyede kilidi aldı veCOMMITetti. Bakiye başarıyla $1050.00 olarak doğrulandı.
▸Lab L2 — Deadlock Tespiti ve Kurban İptali (SQLSTATE 40P01)
- •Senaryo: Oturum A (PID 127) 1'i kilitleyip 2'yi istedi; Oturum B (PID 134) 2'yi kilitleyip 1'i istedi.
- •Motor Tepkisi: Tam 1000ms sonra PostgreSQL döngüyü saptadı:
ERROR: deadlock detected
DETAIL: Process 127 waits for ShareLock on transaction 748; blocked by process 134.
Process 134 waits for ShareLock on transaction 747; blocked by process 127.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,6) in relation "accounts"
SQLSTATE: 40P01- •Bulgu: Oturum A kurban seçilip abort edildi, Oturum B hayatta kalarak işlemini tamamladı. Kurban önceden bilinemez; uygulamanın her iki tarafta da 40P01 yakalaması gerekir.
▸Lab L3 — SQLSTATE 55P03 Ayrımı (NOWAIT vs lock_timeout)
İki durum da aynı hata sınıfını (55P03, lock_not_available) üretir ancak mekanizmaları farklıdır:
- •NOWAIT (3a):
SELECT ... FOR UPDATE NOWAITanında (0.0507 saniye) şu hatayı döndü:
ERROR: could not obtain lock on row in relation "accounts".
- •lock_timeout (3b):
SET lock_timeout = '400ms'tam belirlenen süreyi bekleyerek (0.4048 saniye) şu hatayla durdu:
ERROR: canceling statement due to lock timeout.
- •Çıkarım:
NOWAITsıfır bekleme garantisi verir;lock_timeoutise kuyrukta birikmeyi üst sınıra bağlar.
▸Lab L4 — SQLSTATE 57014 Ayrımı (Statement Timeout vs Kullanıcı İptali)
57014 (query_canceled) kodu iki farklı senaryoda oluşur:
- •Statement Timeout (4a):
statement_timeout = '500ms'ile çalışan sorgu 0.5215 saniyede otomatik düştü:
ERROR: canceling statement due to statement timeout.
- •Kullanıcı İptali (4b):
pg_cancel_backendçalıştırıldığında oturum şu mesajla durdu:
ERROR: canceling statement due to user request.
- •Çıkarım: İzleme sistemleri yalnızca hata koduna bakarak operatör müdahalesi ile timeout aşımını ayıramaz; log metninin ayrıştırılması şarttır.
▸Lab L5 — Idle-in-Transaction Kilit Tutma ve Sonlandırma Sınırları
Bu deney, bağlantı sızıntılarının operasyonel tehlikesini ispatladı:
- •Oturum A (PID 162) satırı güncelleyip
idle in transactiondurumunda bekletildi. - •İptal Denemesi: Gözlemci
SELECT pg_cancel_backend(162);çalıştırdı. - •Dönen Değer:
cancel_returned_bool = true(başarılı döndü). - •Sistem Gerçeği: PID 162 hala
idle in transactiondurumundaydı ve tablodakiRowExclusiveLockkilidini bırakmadı.pg_cancel_backendyalnızca aktif koşan sorguyu iptal eder; boşta duran transaction'ı kapatamaz! - •Hedefli Sonlandırma: Gözlemci
SELECT pg_terminate_backend(162);çalıştırdı. - •Oturum sunucu seviyesinde tamamen kapatıldı.
- •PostgreSQL transaction'ı güvenle geri aldı ve kilitler anında serbest kalarak kuyruktaki işlemler çözüldü.
6. Hedefli Müdahale ve Triyaj Protokolü
Kilitlenme olaylarında şu adımlar disiplinle izlenmelidir:
- 1Kök Bloklayıcıyı Belirle:
pg_blocking_pidssorgusunu çalıştırın. Asla rastgele bekleyen oturumları sonlandırmayın. - 2Bloklayıcının Durumunu Değerlendir:
- •Eğer
state = 'active'ise: Bloklayıcı ağır bir hesaplama yapmaktadır. Önce güvenli iptal deneyin:
SELECT pg_cancel_backend(<blocker_pid>);- •Eğer
state = 'idle in transaction'ise:pg_cancel_backendkilitleri bırakmaz. İlgili servis sahibiyle görüşüp oturumu sonlandırın:
SELECT pg_terminate_backend(<blocker_pid>);- 1Kilitlerin Çözüldüğünü Doğrulayın: Bekleyen işlemlerin
state = 'active'durumuna geçip başarıyla tamamlandığını izleyin.
Operasyonel ayrıntılar için doğrulanmış runbook prosedürlerimizi kullanın:
- •Kilit Zinciri ve Kök Bloklayıcıyı Çözme
- •Uzun ve Boşta Kalan Transaction'ları Yönetme
- •Olay Sonrası Deadlock İnceleme Prosedürü
- •Canlı Teşhis Scripti: Kim Kimi Blokluyor?
7. Önleme Mimarisi: Kilit Çatışmalarını Tasarımda Engellemek
Kilit darboğazlarını kökten çözmek veritabanı ve uygulama katmanlarının ortak disiplinini gerektirir:
- 1Deterministik Erişim Sıralaması: Eğer Servis A Satır 1 ve ardından Satır 2'yi güncelliyorsa, Servis B ise Satır 2 ve ardından Satır 1'i güncelliyorsa deadlock matematiksel bir kaçınılmazlıktır. Uygulama kodunda satır erişimlerini mutlaka deterministik bir sıraya dizin (
SELECT ... FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE). - 2Transaction Sınırlarını Daraltmak: Veritabanı transaction bloğu içinde harici REST API çağrıları, e-posta gönderimi veya dosya yazımı yapmayın. Harici servis 5 saniye geciktiğinde veritabanınız 5 saniye boyunca kilit tutar.
- 3Kademeli Timeout Hiyerarşisi:
- •
idle_in_transaction_session_timeout = '15s': Unutulmuş bağlantı sızıntılarının tablo kilitlerini sonsuza kadar tutmasını engeller. - •
lock_timeout = '2s': Kilit kuyruğunda birikmeyi engelleyerek bağlantı havuzunun çökmesini önler. - •
statement_timeout = '15s': Kontrolden çıkmış sorguları sınırlar.
- 1Jitter Destekli Yeniden Deneme (Exponential Backoff): Kilit çakışması (SQLSTATE
40P01veya55P03) yaşandığında istemcilerin anında yeniden deneme yapmasını engelleyin. Onlarca istemcinin aynı anda yeniden denemesi bir *retry fırtınası* (retry storm) yaratarak kilitlenmeyi kalıcı hale getirir. Rastgele gecikmeli (jittered) geri çekilme kullanın.
8. Triad Kapsamındaki Diğer İncelemeler
Bu konunun diğer kritik boyutları için kardeş platformlarımızdaki analizleri inceleyin:
- •Araç Seçimi (dataxtools): PostgreSQL Kilit Beklemelerinde Araç Seçimi: Canlı Oturumdan Geçmiş Olay Kanıtına
- •Olay Yönetimi & SRE (dataopsphere): Kilit Beklemesi Olayını Yönetmek: Sahiplik, Müdahale Kararı ve Toparlanma Kanıtı
- •Çekirdek Mimari: PostgreSQL MVCC & Concurrency Derin Analizi
Tamamlayıcı Ekosistem İçerikleri: PostgreSQL Kilitleri, Deadlock ve Uzun Transaction'lar: Teşhis, Araç Seçimi ve Olay Yönetimi
Aynı mühendislik konusunu farklı açılardan (teşhis, araç seçimi, olay yönetimi) ele alan diğer dataiotech yayınları:
PostgreSQL Kilit Beklemelerinde Araç Seçimi: Canlı Oturumdan Geçmiş Olay Kanıtına
PostgreSQL kilit ve deadlock vakalarında pg_stat_activity/CLI, pg_activity/pgCenter ve pganalyze/Datadog/OTel karşılaştırması; saklama, yetki ve maliyet matrisi.
Kilit Beklemesi Olayını Yönetmek: Sahiplik, Müdahale Kararı ve Toparlanma Kanıtı
Kilit beklemesi krizlerinde panik yapmadan olay yönetimi: rol paylaşımı, kontrollü rollback/terminate kararı, retry fırtınası önleme protokolü ve kabul kanıtı.
PostgreSQL Diğer Üretim Rehberleri
Aynı veritabanı motoruna ait doğrulanmış diğer içerikler.
psql CLI Hızlı Referans
Bağlantı parametreleri, meta komutlar (\d, \l, \du), .psqlrc şablonu, COPY streaming ve anlık kilit/aktivite teşhis sorguları.
PostgreSQL Güvenlik Hardening
pg_hba.conf sıkılaştırma kuralları, SCRAM-SHA-256 parola politikaları, SSL/TLS verify-full, pgaudit denetim izi, public şema izolasyonu ve tek sorguda güvenlik kontrol listesi.
PostgreSQL Yedekleme & Kurtarma
pg_dump ve pg_dumpall mantıksal yedekler, pg_basebackup ve pgBackRest fiziksel yedekleme, WAL arşivleme ve Point-in-Time Recovery (PITR) komut kartı.