Beklenmedik Bir Hata
Rutin bir iş gününde olduğunuzu düşünün. Altyapınızda, PostgreSQL yapınızın önünde bağlantıları yöneten PgBouncer (Transaction modunda) Metrikleriniz normal, sistem sorunsuz işliyor.
Ta ki uygulamanızın yazma servislerinden hata logları gelmeye başlayana kadar…
Logları açtığınızda veritabanınızında uygulamadan gelen sorguların şu hatayı fırlattığını görüyorsunuz: ERROR: 25006: cannot execute INSERT in a read-only transaction
“Recovery modda olmayan sunucu nasıl read-only olabilir?” diye düşünüp hemen terminalden psql ile sunucuya manuel bir bağlantı kuruyorsunuz. Basit bir INSERT testi yapıyor ve ek olarak SELECT pg_is_in_recovery(); sorgusu ile sunucunun durumunu kontrol ediyorsunuz. İşlem anında başarılı oluyor ve sunucunun recovery modunda olmadığı, yani recovery modda olmadan olarak çalıştığı doğrulanıyor. Veritabanı yapılandırmanızda veya yetkilerde hiçbir sorun yok.
Peki ama veritabanı neden uygulamaya “Ben read-only durumdayım” diyerek işlemleri reddediyor? İşte bu noktada, altyapınızın değil, bağlantı havuzunuzun içinde gizlenen bir “Session Leak” vakasıyla karşı karşıyasınız demektir.

Asıl Sorun: Session Bazında Read-Only Parametresinin Değişmesi
Detaylara indiğimizde sorunun mimari yönlendirmelerle veya veritabanı yetkileriyle hiçbir ilgisi olmadığını görüyoruz. Sorun tamamen, uygulamanın veritabanı ile konuşurken session state‘i (oturum durumunu) değiştirmesi ve sonrasında temizlememesinden kaynaklanıyor.
Uygulama loglarını detaylıca incelediğinizde asıl nedeni şu satırda bulursunuz: LOG: statement: SET default_transaction_read_only TO ‘on’
Peki bu ne anlama geliyor? AI araçlarını kullanılarak geliştirilen uygulamalarda “Sadece Okunabilir” (readOnly: true) olarak işaretlemiş olabilir. Ancak bu işlem yanlışlıkla Write Connection String üzerinden gönderildiğinde zincirleme bir reaksiyon başlar:
- Uygulama, PgBouncer’ın havuzundan temiz bir bağlantı alır.
- Veritabanına kendi başına sorgu üreten bazı AI araçları, olası yıkıcı işlemleri engellemek ve veri güvenliğini sağlamak adına, oturum seviyesinde SET default_transaction_read_only TO ‘on’ parametresini kullanarak bağlantıları otomatik olarak ‘Read-Only’ moduna çeker.
- Okuma işlemi biter ve uygulama bağlantıyı PgBouncer’a iade eder.
- PgBouncer “Transaction Mode” ile çalıştığı için (server_reset_query_always = 0), bağlantı ayarlarını DISCARD ALL ile sıfırlamadan doğrudan havuza geri koyar.
- Havuzdaki o bağlantı artık “Read-Only” modunda takılı kalmış, kirli bir bağlantıdır. Birkaç saniye sonra veri yazmak (INSERT/UPDATE) için havuza gelen yepyeni bir işlem, rastgele bu kirli bağlantıyı alır ve 25006 (read_only_sql_transaction) hatasını vererek iptal olur.

Geçici ve Kalıcı Çözüm Adımları
Böyle bir durumda sistemi hızlıca toparlamak ve kalıcı olarak çözmek için şu adımları izlemelisiniz:
- Acil Müdahale: Kilitlenmiş bağlantıları temizlemek için PgBouncer havuzunu yeniden başlatmanız gerekir. systemctl restart pgbouncer komutu ile havuzu sıfırladığınız an trafik normale döner. Ancak hatalı kod çalışmaya devam ettiği sürece havuz yeniden kirlenecektir.
- Geçici Sistem Yaması: PgBouncer yapılandırmasında server_reset_query_always = 1 yaparak her işlemden sonra veritabanına zorla DISCARD ALL gönderebilirsiniz. Bu sızıntıyı çözer ancak prepared statement’ları (önceden derlenmiş sorguları) sileceği için veritabanı CPU kullanımını artırıp ciddi bir performans darboğazı yaratabilir. Bu yüzden kalıcı bir çözüm olarak tavsiye edilmez.
- Kod Tarafı Yönlendirmesi: Kod tarafında readOnly: true (veya AsNoTracking) içeren tüm metotların sadece Read Connection String üzerinden çalıştığından emin olunmalıdır. Yazma havuzuna hiçbir şekilde oturum seviyesinde bir durum değişikliği gönderilmemelidir.
Çıkarılması Gereken Dersler (Best Practices)
Bu tarz gizli sorunlar, sistem yöneticilerine ve yazılım ekiplerine önemli tecrübeler kazandırır. İşte dikkat etmeniz gereken 3 temel kural:
- Connection String İzolasyonu: AI aracı ile ana uygulamanın aynı production kullanıcısını paylaşması, olası bir havuz kirlenmesinde tüm sistemi kilitler. Bağlantı mekanizmasını güvenli yönetmek için uygulamaları işlevlerine göre farklı kullanıcı seviyelerine ayırın. AI araçları için read-only yetkilerine sahip, tamamen ayrı bir veritabanı kullanıcısı (ve izole bir connection pool) oluşturun. Bu yapısal izolasyon, AI tarafında yaşanabilecek bir sorunun diğer kritik uygulamalara ve ana sisteme sıçramasını kesin olarak engeller.
- Pooler Modunuzun Davranışını Tanıyın: PgBouncer’ı Transaction modunda kullanıyorsanız, varsayılan olarak DISCARD ALL komutunun çalışmadığını ve oturum seviyesindeki değişikliklerin diğer işlemleri etkileyebileceğini bilmelisiniz.
- ApplicationName Kullanımı: Bir hatayı ararken loglarda app=[unknown] görmek işleri oldukça zorlaştırır. Connection String’lerinizin sonuna mutlaka ApplicationName=FaturaServisi gibi tanımlayıcılar ekleyin. Böylece havuzu kirleten modülü loglar üzerinden hızlıca tespit edebilirsiniz.



