Bilgi Bankası
PostgreSQLPerformans ve kapasite 14 dk 14.06.2026

PostgreSQL too many clients ve yavaş sorgu: PgBouncer, indeks ve EXPLAIN planı

PostgreSQL “too many clients” dediğinde sorun sadece bağlantı sayısı değildir; çoğu zaman uygulama fazla bağlantı açıyor, bazı sorgular da o bağlantıları gereğinden uzun süre meşgul ediyordur. Bu yazı bağlantı havuzu ile yavaş sorguyu aynı hikâyenin iki parçası olarak ele alır.

Bu yazı sana şu durumda yardımcı olur
  • FATAL: sorry, too many clients already
  • API isteklerinde kuyruklanma ve yüksek gecikme
  • pg_stat_activity içinde idle in transaction oturumları
  • CPU düşükken bağlantı sayısı yüksek
  • aynı sorgunun çok uzun süre çalışması
Yazıyı bitirince

Kaç bağlantının gerçekten çalıştığını, kaçının beklediğini, hangi sorguların sistemi tuttuğunu göreceksiniz. Sonra PgBouncer, uygulama pool limiti ve indeks düzeltmesi arasında doğru sırayı kurabileceksiniz.

TürkDB açısından ürünleşme notu

Bu bölüm, TürkDB’nin ilgili problemi hangi ürün ilkeleri ve kontrol noktalarıyla ele alacağını anlatır. Kapsam, gerçek iş yükü ve ihtiyaçla birlikte netleşir.

TürkDB’nin buradaki değeri PostgreSQL’i sizin yerinize “hızlandırdığını” söylemek değil; PgBouncer, yavaş sorgu görünürlüğü, PITR ve bakım akışını aynı zeminde tutarak düzeltme işini dağınık bir operasyon projesine çevirmemesidir.

Bağlantı limiti genelde nasıl dolar?

PostgreSQL’de her bağlantının bir maliyeti vardır. Uygulama tarafında “biraz daha pool açalım” masum görünür; ama pod, worker ve pool sayısı çarpıldığında veritabanı gerçek iş yapmadan bağlantı doluluğu yaşayabilir.

İkinci yaygın neden idle in transaction oturumlarıdır. Uygulama transaction açıp commit veya rollback yapmadan beklerse tablo kilitleri, vacuum gecikmesi ve bağlantı tüketimi aynı anda büyür. Dışarıdan bakınca sadece “PostgreSQL yavaşladı” gibi görünür.

Bağlantı ve transaction teşhisi

postgres-diagnostics.sql
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state
ORDER BY count(*) DESC;

SELECT pid, usename, application_name, state, wait_event_type,
       now() - xact_start AS tx_age,
       left(query, 120) AS query
FROM pg_stat_activity
WHERE state IN ('active', 'idle in transaction')
ORDER BY tx_age DESC NULLS LAST
LIMIT 20;

PgBouncer ne zaman gerçekten işe yarar?

Web uygulamalarında istemci bağlantısı sayısı genellikle veritabanının aynı anda çalıştırması gereken sorgu sayısından çok daha fazladır. PgBouncer burada bir trafik düzenleyici gibi çalışır: kısa sorguları daha az backend bağlantısı üzerinden sıraya koyar.

Ama PgBouncer her derde ilaç değildir. Uzun transaction, session state, prepared statement davranışı veya yanlış pool modu varsa yeni sorunlar üretebilir. Bu yüzden önce uygulamanın bağlantı desenini anlamak, sonra pooler kararını vermek gerekir.

TürkDB’de PostgreSQL cluster oluştururken pooler etkinleştirildiğinde uygulama tek bağlantı dizesiyle bağlanır; PgBouncer yaşam döngüsü, TLS ve servis yönlendirmesi platform tarafından yönetilir.

Pooler ile PostgreSQL cluster oluşturma

turkdb-postgres.sh
turkdb cluster create --name app-pg \
  --type postgres \
  --version 16 \
  --plan standard \
  --region ist1 \
  --pooler \
  --wait

turkdb connect app-pg
Bu TürkDB bloğu kavramsal ürün akışını anlatır; mevcut çalışan komut, garanti edilen özellik veya teslim tarihi vaadi olarak okunmamalıdır.

Uygulama pool boyutu nasıl seçilir?

PostgreSQL’de “her uygulama podu 50 bağlantı açsın” yaklaşımı hız değil, bağlantı baskısı üretir. Toplam bağlantı sayısı uygulama podu, worker sayısı ve her workerın pool limiti çarpılarak hesaplanmalıdır.

Örneğin 8 pod, pod başına 4 worker ve worker başına 20 bağlantı varsa uygulama teorik olarak 640 bağlantı açabilir. Veritabanı bu kadar eş zamanlı sorgu çalıştırmayacaksa bu değer yalnızca memory ve context switching maliyeti yaratır.

  • Önce uygulamanın toplam maksimum bağlantı sayısını hesaplayın.
  • Kısa OLTP sorgularında PgBouncer transaction pooling tercih edin.
  • Uzun transaction veya session state kullanan işlerde pool modunu dikkatli seçin.
  • Pool zaman aşımı değerini sonsuza yakın bırakmayın; kuyruk büyüdükçe kullanıcı deneyimi bozulur.

Toplam bağlantı hesabı

pool-sizing.txt
toplam_baglanti = pod_sayisi * worker_sayisi * worker_pool_max
ornek = 8 pod * 4 worker * 20 pool = 640 potansiyel baglanti

Yavaş sorguyu plan üzerinden düzeltin

Yavaş sorgu çözümünde yalnızca süreye bakmak yetmez. Satır tahmini ile gerçek satır sayısı çok farklıysa istatistikler güncel olmayabilir; Sequential Scan beklenmeyen bir tabloda çıkıyorsa indeks eksik olabilir.

İndeks eklerken canlı ortam sistemlerde CREATE INDEX CONCURRENTLY tercih edilir. Bu işlem daha uzun sürebilir ama tabloya yazmayı kilitlemeden ilerler.

Sık çalışan pahalı sorguları bulma

pg-stat-statements.sql
SELECT calls,
       round(total_exec_time::numeric, 2) AS total_ms,
       round(mean_exec_time::numeric, 2) AS mean_ms,
       rows,
       left(query, 180) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

Plan inceleme ve güvenli indeks

postgres-index.sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, created_at
FROM orders
WHERE tenant_id = $1 AND status = 'paid'
ORDER BY created_at DESC
LIMIT 50;

CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_tenant_status_created
ON orders (tenant_id, status, created_at DESC);

TürkDB yavaş sorgu görünümü

turkdb-slow-queries.sh
turkdb cluster slow-queries app-pg --threshold 200 --limit 10
turkdb cluster slow-queries app-pg --reset
Bu TürkDB bloğu kavramsal ürün akışını anlatır; mevcut çalışan komut, garanti edilen özellik veya teslim tarihi vaadi olarak okunmamalıdır.

Düzeldi mi, yoksa sadece bugün mü rahatladı?

İndeks eklendikten veya pool ayarı değiştikten sonra yalnızca hata kayboldu mu diye bakmak yeterli değildir. Bir sistem bazen kısa süreliğine rahatlar ama aynı davranış ertesi gün tekrar limiti doldurur. Aktif bağlantı sayısı, idle in transaction oturumları, mean_exec_time ve p95 API gecikme birlikte düşmelidir.

Plan düzeldiği halde sorgu hâlâ yavaşsa problem indeks değil veri hacmi, yanlış join sırası, güncel olmayan istatistikler veya disk/IO baskısı olabilir.

Önce/sonra kontrolü

postgres-after-fix.sql
ANALYZE orders;

SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;

SELECT calls, mean_exec_time, rows, left(query, 140) AS query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
Devam etmek istersen

Sorunu motor bazında derinleştirin

Benzer belirtiler farklı motorlarda farklı nedenlere dayanır; yayınlanan tüm rehberleri ana sayfadan takip edebilirsiniz.

Tüm makaleler