PostgreSQL VPS uzerinde bağlantı havuzu kullanımı
Her PostgreSQL bağlantısı ayrı bir süreçtir ve RAM tüketimini hızla artırır. OOM Killer kaynaklı çökmeleri önlemek için PgBouncer gibi bir havuz yöneticisi neden zorunludur?
Küçük bir VPS'in max_connections değerine ulaşmadan RAM tüketimini bitirmesinin nedeni
VPS üzerinde PostgreSQL bağlantı havuzu (connection pooling) bir hız hilesi değildir. 4 GB belleğe sahip bir sunucunun ayakta kalmasını sağlayan temel mekanizmadır; çünkü her PostgreSQL bağlantısı, kendi özel belleğini tutan ayrı bir işletim sistemi sürecidir. Bir havuz yöneticisi (pooler), çok sayıda ucuz istemci bağlantısının arkasına, gerçek arka uç (backend) süreçlerinden oluşan küçük ve sabit bir sayı yerleştirir.
Varsayılan max_connections değeri 100'dür. Bu bir bütçe değil, bir sınırdır. PostgreSQL, makinenizin gerçek sorguları çalıştıran 100 arka uç sürecini gerçekten kaldırıp kaldıramayacağını asla kontrol etmez, bu nedenle makine önce çöker. Çekirdeğin bellek yetersizliği (OOM) sonlandırıcısı bir süreç seçer ve bu süreç bir arka uç olduğunda, PostgreSQL paylaşılan belleği tekrar güvenli hale getirmek için tüm kümeyi yeniden başlatır. Günlük kayıtlarında server process (PID 1234) was terminated by signal 9: Killed, ardından terminating any other active server processes görülür. Sağlıklı olanlar da dahil olmak üzere tüm açık bağlantılar kopar.
Sunucunun belleği tükenir çünkü her bağlantı bir süreçtir ve work_mem değeri bağlantı başına değil, sıralama veya karma (hash) işlemi başına tahsis edilir. Her ikisi de bu tüketimi katlar.
Her bağlantı bir süreçtir ve her sürecin bir bellek maliyeti vardır
PostgreSQL, her bağlantı için bir süreç (process) kullanır. Bir istemci bağlandığında postmaster bir backend çatallar (fork) ve bu backend, istemci bağlantıyı kesene kadar yaşamaya devam eder. Bu bir iş parçacığı (thread) değildir. Kendi sayfa tablolarına, kendi katalog önbelleklerine ve kendi önbelleğe alınmış sorgu planlarına sahiptir. Bağlantı daha fazla tabloya erişip daha fazla farklı sorgu çalıştırdıkça bu önbellekler büyür; bu nedenle yoğun bir ORM uygulamasındaki uzun ömürlü bir bağlantı, yeni bir bağlantıdan daha fazla maliyetlidir.
Paylaşılan bellek gerçekten paylaşılır. shared_buffers, tüm küme için tek bir tahsisattır ve her backend içine eşlenir. Özel bellek paylaşılmaz; top değerinin sizi burada yanıltmasının nedeni budur: bir backend'in yerleşik küme boyutu (RSS), o backend'in eriştiği paylaşılan sayfaları da içerir. Bu nedenle 50 backend üzerindeki RSS değerlerini toplamak, shared_buffers değerini 50 kez saymak anlamına gelir.
Bunun yerine özel kısmı ölçün. PSS (orantılı küme boyutu), her paylaşılan sayfayı onu eşleyen süreç sayısına böler; USS (benzersiz küme boyutu) ise yalnızca o sürece ait olan sayfaları sayar.
sudo apt update
sudo apt install -y smem
sudo smem -k -P '^postgres'USS sütunu, ilgili backend sonlandığında serbest kalacak bellek miktarını gösterir. Bağlantı başına gerçek maliyetiniz budur. Yayınlanan rakamlar genellikle boşta duran bir backend'i tek haneli megabaytlarla, kapsamlı ORM sorguları çalıştırmış bir backend'i ise bunun birkaç katı büyüklükte gösterir. Bunları kendi verileriniz değil, tipik yayınlanmış rakamlar olarak değerlendirin. Planlama yaparken dikkate alınması gereken tek rakam, kendi sunucunuzda ve kendi iş yükünüz altında elde ettiğiniz değerdir.
Oturum başına yapılan bir tahsisatı gözden kaçırmak kolaydır. temp_buffers varsayılan olarak 8MB değerindedir ve oturum geçici bir tabloya ilk kez eriştiğinde oturum başına tahsis edilir. Oturum sona erene kadar bu bellek geri alınmaz.
work_mem bağlantı başına değil, işlem başına tahsis edilir
Hesaplamanın karmaşıklaştığı nokta burasıdır. work_mem varsayılan olarak 4MB değerindedir ve PostgreSQL dokümantasyonu bunun ne anlama geldiğini açıkça belirtir: "karmaşık bir sorgu aynı anda birkaç sıralama ve karma (hash) işlemi gerçekleştirebilir; her işlem, geçici dosyalara veri yazmaya başlamadan önce genellikle bu değerin belirttiği kadar bellek kullanabilir." Üç sıralama düğümü içeren bir plan, aynı anda tek bir backend içinde work_mem değerinin üç katını kullanabilir.
Karma (hash) işlemleri daha fazla bellek tüketir. hash_mem_multiplier varsayılan olarak 2.0 değerindedir; bu nedenle bir hash join veya hash aggregate işlemi, varsayılan ayarlarda work_mem değerinin iki katını, yani 8MB kullanabilir. Paralel sorgular bu miktarı daha da artırır, çünkü her bir paralel çalışan (worker), kendi bellek tahsisine sahip ayrı bir süreçtir.
4 GB RAM'e sahip bir VPS için hesaplama yapalım. shared_buffers değerini 1 GB olarak ayarlayıp, work_mem değerini 4MB'ta bıraktığınızı ve 100 bağlantının her birinin iki hash düğümü içeren bir sorgu çalıştırdığını varsayalım. Bu, 100 çarpı 16MB eder; yani 1 GB'lık paylaşılan tampon (shared buffers) belleğinin üzerine, sayfa önbelleği (page cache) ve sunucudaki diğer her şeyden önce 1.6 GB'lık özel bellek kullanımı demektir. Şimdi, sunucuda boş RAM olduğu düşüncesiyle work_mem değerini 64MB'a yükseltirseniz, aynı 100 bağlantı 100 çarpı 256MB'lık bir tüketime denk gelir. Sizi uyaran hiçbir mekanizma yoktur. Durumu ancak OOM killer devreye girdiğinde fark edersiniz.
Tahmin yürütmek yerine work_mem değerinin çok düşük olup olmadığını kontrol edebilirsiniz. postgresql.conf dosyasında log_temp_files = 0 ayarını yapılandırın ve yapılandırmayı yeniden yükleyin. Bu durumda diske yapılan her taşma (spill), dosya adını ve boyutunu içeren temporary file: path "base/pgsql_tmp/pgsql_tmp1234.0", size 20971520 gibi bir satır oluşturur. Sık yaşanan taşmalar, work_mem değerinin artırılmasının faydalı olacağını gösterir. Hiç taşma yoksa, artış yapmak hiçbir kazanç sağlamaz ve sahip olmadığınız belleği tüketir.
Sorun yaratan havuz aritmetiği
Kimse 240 bağlantı açmayı hedeflemez. 20'lik bir havuz yapılandırılır ve ardından uygulama birden fazla yerde çalıştırılır.
The data behind this chart
[
{
"config": "1 worker",
"backends": 20
},
{
"config": "4 web workers",
"backends": 80
},
{
"config": "4 web + 2 background",
"backends": 120
},
{
"config": "2 hosts x 4 workers",
"backends": 160
},
{
"config": "3 hosts x 4 workers",
"backends": 240
}
]Her biri 20'lik bir havuz tutan dört Gunicorn worker, 80 adet arka uç bağlantısı talep eder. İki arka plan işi worker'ı eklendiğinde bu sayı 120 olur. Sayı 3 hosts x 4 workers değerine ulaştığında, uygulama 100'lük bir max_connections sınırına karşı 240 adet arka uç bağlantısı talep ediyor demektir. Bu 5 kurulumun hiçbirinde tek bir noktada bile hatalı yapılandırma yoktur. Havuz süreç bazlıdır ve uygulamanın hiçbir parçası toplam sayıyı göremez.
Kütüphane varsayılanları da aynı yönde baskı yapar. SQLAlchemy'nin QueuePool ayarı, max_overflow=10 ile birlikte varsayılan olarak pool_size=5 değerindedir; yani süreç başına 15 bağlantı. HikariCP varsayılan olarak 10 bağlantı kullanır. Django'nun 5.1 sürümünden önce yerleşik bir havuz yapısı yoktu, süreç başına bir bağlantı kullanılırdı. Django uygulamalarının bu sorunla daha geç karşılaşmasının ve CONN_MAX_AGE ayarını yaptıklarında veya yeni "pool": True seçeneğini açtıklarında aniden bu sorunla yüzleşmelerinin nedeni budur. Eğer Gunicorn ve nginx arkasında bir Django uygulaması çalıştırıyorsanız, çarpmanız gereken sayı sunucu sayınız değil, Gunicorn worker sayınızdır.
VPS üzerinde Postgres bağlantı havuzlamasının (connection pooling) gerçekte değiştirdiği şeyler
Havuzlayıcı (pooler), bir tarafında uygulamanızla PostgreSQL protokolü üzerinden konuşan, diğer tarafında ise az sayıda gerçek sunucu bağlantısını hazır tutan bir süreçtir. Sorguları hızlandırmaz. Bağlantı maliyetini kimin üstlendiğini ve kaç adet gerçek arka uç (backend) bulunduğunu değiştirir.
İki şey iyileşir. Bağlantı kurma işlemi, bir fork oluşturma ve boş bir arka uç önbelleğini dolduran katalog aramaları yapma maliyetini ortadan kaldırır; çünkü havuzlayıcı, istemcinin bağlantı isteğini bizzat yanıtlar. Daha da önemlisi, gerçek arka uç sayısı artık uygulama bağlantı sayısına bağlı kalmaz; böylece 500 istemci 20 arka ucu paylaşabilir.
Bekleme süresi aslında bir özelliktir ve insanların direnç gösterdiği kısım da budur. Bir havuzlayıcı olmadan, 500 eşzamanlı sorgunun tamamı bir arka uç alır ve iki CPU çekirdeği üzerinde aynı anda çalışmaya başlar; bu da her birinin yavaşlamasına ve tüm belleğin aynı anda tüketilmesine neden olur. Bir havuzlayıcı ile 20 sorgu çalışır, geri kalanı ise birkaç milisaniye bekler; böylece çalışan her sorgu CPU'dan gerçek bir pay alır ve daha kısa sürede tamamlanır. Küçük bir havuzun önündeki kuyruk, büyük bir havuzun önünde kuyruk olmamasından daha iyidir.
Havuzlayıcının yapmadığı şey ise makinedeki diğer kaynakları sınırlamaktır. Eğer Postgres, bir uygulama sunucusuyla veya aynı VPS üzerindeki bir vektör veritabanı ile kaynak paylaşıyorsa, havuzlayıcı Postgres'i yalnızca uygulamanızdan korur, başka bir şeyden değil. Komşu servisler için de katı sınırlar koyun: bir sürecin kontrolden çıkıp veritabanını çökertmemesi için systemd ile bir servisin kullanabileceği bellek ve CPU miktarını sınırlayabilirsiniz. Veritabanının nerede çalıştığı, bu sınırları nasıl belirleyeceğinizi değiştirir; Postgres'i Docker içinde veya doğrudan ana makinede çalıştırmak arasındaki pratik fark budur.
Oturum havuzu (session pooling) ile işlem havuzu (transaction pooling) karşılaştırması
Her şeyi belirleyen tek bir ayar vardır, o da pool_mode ayarıdır.
Oturum havuzlamasında, bir sunucu bağlantısı istemci bağlantısının tüm ömrü boyunca o istemciye atanır ve istemci bağlantıyı kestiğinde serbest bırakılır. Havuzlayıcı basit bir proxy görevi gördüğü için her şey sorunsuz çalışır. Sadece bağlantı kurma maliyetinden tasarruf edersiniz, başka bir şeyden değil. Uygulama 200 bağlantı açarsa, yine 200 arka uç (backend) bağlantısına ihtiyaç duyarsınız.
İşlem havuzlamasında ise bir sunucu bağlantısı istemciye yalnızca bir işlem süresince atanır. COMMIT veya ROLLBACK komutunda bağlantı havuza geri döner ve bekleyen bir sonraki istemciye tahsis edilir. 500 istemciyi 20 arka uç bağlantısına dönüştüren yöntem budur. Ancak bu yöntem, tasarımı gereği bazı şeyleri de bozar: bir sonraki SQL ifadeniz, bir öncekinin çalıştığı arka uçtan farklı bir arka uç üzerinde çalışabilir.
PgBouncer varsayılan ayarı pool_mode = session şeklindedir. Kurulumu yapıp hiçbir ayarı değiştirmezseniz, havuzlamanın sağladığı avantajlardan yararlanamadan sadece bağlantı maliyeti tasarrufu elde edersiniz. Üçüncü bir mod olan statement ise bağlantıyı her bir SQL ifadesinden sonra havuza geri döndürür ve çok ifadeli işlemleri reddeder. Neden kullanmanız gerektiğini tam olarak bilmiyorsanız bu modu değiştirmeyin.
Hangi işlem modu bozulur ve neden
Aşağıdaki her şey tek bir nedenden dolayı başarısız olur. Bu durum, tek bir backend içinde yaşayan bir durumdur ve transaction pooling size aynı backend'i iki kez garanti etmez.
- Oturum düzeyinde
SETveRESET.SET search_path,SET statement_timeout,SET TIME ZONEveSET ROLE, o ifadeyi çalıştıran herhangi bir backend'e gider ve bir sonraki işleminizden önce kaybolur. İşlem kapsamına giren ve bu nedenle güvenli olan, açık bir işlem içindeSET LOCALkullanın. LISTEN. Bildirim teslimi,LISTENkomutunu çalıştıran backend'e aittir ve bu backend, işlem biter bitmez başka bir istemciye devredilir.NOTIFY, transaction modunda çalışmaya devam eder, bu da durumu kafa karıştırıcı bir hataya dönüştürür: gönderme başarılı olur, ancak alma işlemi asla gerçekleşmez.LISTENkullanmanız gerekiyorsa, pooler'ı atlayan ve doğrudan 5432 numaralı porta giden fazladan bir bağlantı açın.- Oturum düzeyinde advisory lock'lar.
pg_advisory_lock(), oturum tarafından tutulur ve oturum sona erdiğinde serbest bırakılır. Transaction pooling altında, kilit açma çağrınız farklı bir backend üzerinde çalışır, bu nedenle kilit, PgBouncer o sunucu bağlantısını emekli edene kadar (varsayılan olarakserver_lifetime, yani bir saat sonra) tutulmaya devam eder. Kilidi alan aynı backend tarafından işlemin sonunda serbest bırakılanpg_advisory_xact_lock()kullanın. PREPAREveDEALLOCATESQL ifadeleri. Transaction modunda asla kullanılamazlar.WITH HOLDimleçleri ve işleminden daha uzun yaşaması beklenen tüm sunucu tarafı imleçleri.- Bir commit işleminden sonra hayatta kalması amaçlanan geçici tablolar.
CREATE TEMP TABLE ... ON COMMIT PRESERVE ROWS, tabloyu bir backend'in geçici şemasına yerleştirir ve bir sonraki işleminiz o backend üzerinde gerçekleşmeyebilir. LOAD.
Protokol düzeyindeki hazırlanmış ifadeler (prepared statements), taşınmış olan tek öğedir. PgBouncer 1.21.0, transaction modunda bunlar için destek ekledi ve 1.24.0, max_prepared_statements değerini 200 olarak ayarlayarak bunu varsayılan hale getirdi. Daha eski sürümler bu değeri 0'da bırakır, bu da özelliğin kapalı olduğu anlamına gelir. Ubuntu 24.04, PgBouncer 1.22.0 sürümüyle gelir; bu nedenle özellik mevcuttur ancak max_prepared_statements ayarını kendiniz yapmanız gerekir. Derlemenizin ne yaptığından emin değilseniz, güvenli ayar istemci tarafındadır: prepare_threshold değerini None olarak ayarladığınızda, psycopg 3 sunucu tarafı hazırlanmış ifadeleri kullanmayı durdurur.
Django, bunun kendi versiyonunu farklı şekilde adlandırır. Dokümantasyon, "transaction pooling modunda bir bağlantı havuzu (örneğin PgBouncer) kullanmanın, o bağlantı için sunucu tarafı imleçlerini devre dışı bırakmayı gerektirdiğini" belirtir; çünkü "sunucu tarafı imleçlerine yalnızca oluşturuldukları bağlantı içinden erişilebilir". Veritabanı girişinde DISABLE_SERVER_SIDE_CURSORS değerini True olarak ayarlayın, aksi takdirde her .iterator() çağrısı yalnızca yük altında ortaya çıkan kesintili bir hataya dönüşür.
Transaction modu kullanmaya değerdir ve bir sözleşmedir. Listeyi okuyun, ORM'nizi ve arka plan iş kütüphanenizi buna göre kontrol edin ve ardından geçiş yapın.
PgBouncer kurulumu ve uygulamanın bu yapıya yönlendirilmesi
Aşağıdaki yapılandırma, PostgreSQL'in halihazırda 127.0.0.1 adresinde 5432 numaralı portu dinlediği Ubuntu 24.04 sunucunuzda çalıştırılmak üzere hazırlanmıştır.
sudo apt update
sudo apt install -y pgbouncer
pgbouncer --versionUbuntu 24.04, PgBouncer 1.22.0 sürümünü paketler. Ağustos 2026 itibarıyla güncel sürüm 1.25.2'dir. Yukarıda belirtilen prepared statement davranışı sürüme bağlı olduğundan, sisteminizdeki sürümü kontrol edin.
Yalnızca PgBouncer yönetici konsoluna giriş yapma yetkisine sahip bir rol oluşturun ve ardından parola dosyasını oluşturun. PgBouncer, pg_authid tablosundaki SCRAM (salted challenge response authentication mechanism) sırlarına ihtiyaç duyar ve bu tabloyu yalnızca bir süper kullanıcı okuyabilir.
sudo -u postgres psql -c "CREATE ROLE pgb_admin LOGIN PASSWORD 'change-this'"
sudo -u postgres psql -At -c \
'SELECT format($$"%s" "%s"$$, rolname, rolpassword) FROM pg_authid WHERE rolpassword IS NOT NULL' \
> /tmp/userlist.txt
sudo install -o postgres -g postgres -m 640 /tmp/userlist.txt /etc/pgbouncer/userlist.txt
rm /tmp/userlist.txtParolaları yeniden yazmak yerine sırları kopyalamak, bu yapının çalışmasını sağlar. PgBouncer'ın PostgreSQL'e giriş yapmak için bir SCRAM sırrını yeniden kullanabilmesi için şu şartlar gereklidir: istemcinin de SCRAM ile kimlik doğrulaması yapması, dosyadaki sırrın pg_authid içindeki değerle bayt bazında aynı olması (sadece aynı parola değil, aynı tuz ve yineleme sayısı) ve [databases] satırının bir user= değerini sabitlememiş olması. Bu satıra user=appuser eklerseniz, PgBouncer düz metin parola isteyecektir. Dosya sahibinin, servisi çalıştıran hesapla eşleştiğini systemctl show pgbouncer -p User ile doğrulayın. PostgreSQL'de bir parolayı değiştirmek, bu dosyanın yeniden oluşturulmasını gerektirir; aksi takdirde bir sonraki bağlantı girişimi password authentication failed hatası döndürür.
Şimdi /etc/pgbouncer/pgbouncer.ini dosyasını oluşturun.
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = pgb_admin
pool_mode = transaction
max_client_conn = 500
default_pool_size = 20
min_pool_size = 5
max_db_connections = 80
max_prepared_statements = 200
ignore_startup_parameters = extra_float_digitslisten_addr = 127.0.0.1, havuz yöneticisini (pooler) genel internete kapalı tutar; bu önemlidir çünkü dışarıdan erişilebilen bir havuz yöneticisi, yayınlamak istemediğiniz bir kimlik doğrulama uç noktası haline gelir. max_client_conn, PgBouncer'ın kabul edeceği uygulama bağlantısı sayısıdır; bu işlem düşük maliyetli olduğundan yüksek bir değer verilebilir. default_pool_size, bir veritabanı ve kullanıcı çiftinin tutabileceği gerçek arka uç (backend) sayısıdır ve maliyetli olan değer budur. max_db_connections, veritabanının tamamını 80 ile sınırlar ve max_connections altında psql, yedeklemeler ve izleme işlemleri için yer bırakır. ignore_startup_parameters = extra_float_digits, bağlantı sırasında bu parametreyi gönderen JDBC sürücüsü dahil olmak üzere sürücülerin PgBouncer tarafından reddedilmesini engeller.
sudo systemctl restart pgbouncer
sudo systemctl status pgbouncer --no-pager
sudo journalctl -u pgbouncer -n 20 --no-pagerSağlıklı bir başlangıç, PgBouncer'ın 127.0.0.1:6432 adresini dinlediğini belirten bir satırı günlüklere yazar. Parola dosyası nedeniyle başarısız olan bir başlangıç, okunamayan yolu günlüğe kaydeder; bu durum neredeyse her zaman sözdizimi hatasından ziyade bir izin veya sahiplik sorunudur. Ardından uygulamanın bağlantı dizgisini 5432 numaralı porttan 6432 numaralı porta değiştirin ve uygulamayı yeniden başlatın. Uygulamanın başka hiçbir yerinde değişiklik yapılması gerekmez.
Havuzun görevini yapıp yapmadığını kontrol etme
PgBouncer, pgbouncer adlı sanal bir veritabanı üzerinden erişilen bir yönetici konsoluna sahiptir.
psql -h 127.0.0.1 -p 6432 -U pgb_admin pgbouncerSHOW POOLS;
SHOW STATS;
SHOW CLIENTS;SHOW POOLS izlenmesi gereken temel komuttur. cl_active şu anda bir sunucu bağlantısına bağlı olan istemcileri, cl_waiting bir bağlantı için sırada bekleyen istemcileri, sv_active ve sv_idle kullanımda olan ve boşta duran gerçek arka uçları (backend), maxwait ise kuyruğun önündeki istemcinin saniye cinsinden bekleme süresini gösterir. Normal yük altında sağlıklı bir durum, cl_waiting değerinin 0 ve maxwait değerinin 0 olması anlamına gelir. Bir veya iki saniyenin üzerine çıkan maxwait değeri, havuzun çok küçük olduğunu veya sorguların çok yavaş çalıştığını gösterir; bu durumlar farklı çözüm yöntemleri gerektirir.
default_pool_size değerini artırmadan önce hangisinin sorun olduğunu kontrol edin.
SELECT state, count(*) FROM pg_stat_activity
WHERE backend_type = 'client backend' GROUP BY state;Arka uçların çoğu idle in transaction durumunda bekliyorsa, sorun havuz boyutu değildir. Uygulama bir işlem (transaction) başlatıyor ve ardından bu işlemin içinde HTTP çağrısı gibi yavaş bir işlem gerçekleştiriyor; bu nedenle her arka uç, bir sorgu çalıştırmadan meşgul tutuluyor demektir. idle_in_transaction_session_timeout bu bağlantıları kesecektir ancak asıl çözüm uygulama kodundadır. Eğer arka uçların tamamı active durumundaysa, havuz gerçekten doygunluğa ulaşmıştır ve daha fazla bağlantı vermeden önce sorguların EXPLAIN (ANALYZE, BUFFERS) ile optimize edilmesi gerekir.
Boyutlandırma konusunda en sık referans verilen başlangıç noktası, çekirdek sayısının yaklaşık iki katı artı bir şeklindeki HikariCP buluşsal yöntemidir; bu da 2 çekirdekli bir VPS üzerinde 5 bağlantıya denk gelir. Bunu yayınlanmış bir başlangıç değeri olarak kabul edin, default_pool_size değerini buna yakın ayarlayın ve maxwait verisine göre değişiklik yapın. Küçük havuzlar sezgisel olarak yanlış hissettirse de genellikle daha iyi performans ölçümleri verir; çünkü kuyrukta bekleyen bir arka ucun maliyeti yoktur, ancak çalışan bir arka uç CPU, bellek ve kilit çekişmesi (lock contention) maliyeti yaratır.
PgBouncer, PgDog ve Pgpool-II arasında seçim yapmak
PgBouncer, standart durumlar için en uygun çözümdür: tek bir PostgreSQL sunucusu, tek bir VPS ve sunucunun kapasitesinden daha fazla bağlantı açan bir uygulama. Tek bir işi yapar, yapılandırması tek bir ini dosyasından ibarettir ve Debian ile Ubuntu paket depolarında yer alır. Bağlantı yönetimini tek bir iş parçacığında (thread) gerçekleştirir; bu, VPS ölçeğindeki iş yükleri için fazlasıyla yeterlidir ve yalnızca çok daha büyük makinelerde bir darboğaz haline gelir.
Yönlendirme kararının, bağlantı havuzu (pooling) ile aynı ağ sekmesinde olması gerektiğinde PgDog değerlendirilebilir. Kendisini PostgreSQL ölçeklendirmesi için bir proxy olarak tanımlayan bu araç, Rust ile yazılmıştır. Sorgu ayrıştırma yoluyla işlem (transaction) ve oturum (session) havuzu oluşturmanın yanı sıra okuma/yazma ayrımı, çoklu shard yönlendirmesi ve iki aşamalı commit (two-phase commit) gibi özellikler sunar. Birincil (primary) sunucunuz ve bir veya daha fazla replikanız varsa, uygulamaya replikaların varlığını öğretmeden okuma işlemlerini replikalara yönlendirmek için bu aracı tercih edebilirsiniz. İki konuda dikkatli olunmalıdır. Yazılım AGPLv3 lisansına sahiptir; bu nedenle, üretim ortamına almadan önce ağ kullanımı maddesiyle ilgili şirketinizdeki yetkili birimle lisans konusunu netleştirmeniz gerekir. Projenin kendi görüşü, dahili kullanım ve özel değişikliklerin kaynak kodu paylaşma zorunluluğu doğurmadığı yönündedir. Ayrıca proje henüz yenidir, haftalık sürümler yayınlanmaktadır ve 0.x sürüm numaralarına sahiptir; bu nedenle main sürümünü takip etmek yerine belirli bir sürüm etiketini sabitleyin.
git clone https://github.com/pgdogdev/pgdog
cd pgdog
cargo build --release
./target/release/pgdog --config pgdog.toml --users users.tomlKaynaktan derleme yapmak için güncel ve kararlı bir Rust araç zinciri, CMake ve bir C/C++ derleyicisi gerekir. Ayrıca sürümler sayfasında önceden derlenmiş Linux ikili dosyaları ve Debian paketleri, ghcr.io/pgdogdev/pgdog adresinde ise bir container imajı mevcuttur. Yapılandırma iki dosyaya bölünmüştür. İlk dosya genel ayarları ve veritabanı başına bir girişi tutar; burada iki biçimin kolayca ayırt edilebilmesi için satır içi tablolardan oluşan bir TOML dizisi olarak yazılmıştır.
databases = [
{ name = "appdb", host = "127.0.0.1" },
]
[general]
port = 6432
default_pool_size = 10İkinci dosya, aynı dizi biçiminde kullanıcı başına bir girişi tutar.
users = [
{ name = "appuser", database = "appdb", password = "change-this" },
]PgDog varsayılan olarak 6432 numaralı portu dinler. Bu, PgBouncer ile aynı porttur; dolayısıyla her ikisi de aynı sunucuda varsayılan portu kullanamaz.
Haziran 2026 itibarıyla 4.7.2 sürümünde olan Pgpool-II, havuz oluşturma ve yük dengelemenin yanı sıra otomatik yük devretme (failover) için bir watchdog sunar. Ek özellikler beraberinde ek hata modları getirir; bu nedenle seçim yapmadan önce havuzlama modelini anlamak gerekir. Pgpool-II, num_init_children adet alt süreç (child process) oluşturur (pre-fork) ve her alt süreç max_pool adede kadar sunucu bağlantısını önbelleğe alır. Bu durumda arka uç (backend) sınırı, num_init_children ile max_pool değerinin çarpımıdır. Her alt süreç aynı anda tek bir istemciye hizmet verir; dolayısıyla kabul edebileceğiniz istemci sayısı num_init_children değerine eşittir ve bu sayı başlangıçta sabitlenir. Boşta duran bir istemci bile bir alt süreci meşgul eder. num_init_children değerini 100, max_pool değerini 4 olarak ayarlarsanız, 400 adet arka uç yetkilendirmiş olursunuz ki bu, tam olarak bir havuzlayıcı kurarak çözmeye çalıştığınız sorunun kendisidir. Pgpool-II'yi, yük devretme ve sorgu yönlendirme özelliklerine ihtiyaç duyduğunuzda seçin ve bu çarpım işlemini dikkatli yapın. Eğer tek istediğiniz daha az arka uç bağlantısı ise, bu araç işin gerektirdiğinden daha karmaşık bir yapı sunar.
Yönetilen proxy sorusu ve self-hosted karşılığı
Yönetilen platformlar bunu ayrı bir ürün olarak satar. AWS, RDS'in önüne RDS Proxy'yi koyar; Supabase ise kendi havuz yöneticisi olan Supavisor'ı Supabase Postgres'in önüne yerleştirir. Her ikisi de burada açıklanan işi yapar: istemci bağlantılarını düşük maliyetle tutar ve daha az sayıda gerçek arka uç bağlantısı dağıtır. Supavisor açık kaynaklıdır ve self-hosted olarak kurulabilir; dolayısıyla seçim, tescilli yazılım ile ücretsiz yazılım arasında değildir.
Yönetilen bir proxy'nin self-hosted karşılığı farklı bir fikir değildir. Yapılandırma dosyasının sizin elinizde olduğu aynı fikirdir: veritabanı ile aynı VPS üzerinde, 127.0.0.1 adresini dinleyen transaction modundaki PgBouncer. İki fark gerçektir. Yönetilen bir proxy bir ağ atlaması (network hop) uzakta durur; bu nedenle gecikme ekler ve veritabanı altında yeniden başlatılırken istemci bağlantılarını tutmaya devam eder. Veritabanı ana makinesindeki PgBouncer ise bir loopback atlaması ekler ki bu neredeyse ücretsizdir, ancak ana makine çöktüğünde o da ölür. Yeniden başlatma sırasında hayatta kalma davranışı istiyorsanız, Pgpool-II'nin watchdog mekanizması veya PgDog'un sağlık kontrollerinin karmaşıklığını hak etmeye başladığı noktada, failover (yük devretme) mekanizmalarına da ihtiyacınız olur.
Listeye bir seçenek daha eklenmelidir. Eğer bağlantı sayısı dağıtımınızı karmaşıklaştıran temel şeyse, gömülü bir veritabanının havuzlanacak bir bağlantı modeli yoktur; çünkü bu, bir port üzerindeki sunucudan ziyade sürecinizin içindeki bir kütüphanedir. Mütevazı yazma hacmine sahip tek bir uygulama sunucusu için VPS üzerinde SQLite çalıştırmak, bu sorunu yönetmek yerine tamamen ortadan kaldırır. Gerçek bir sunucuya ihtiyaç duyduğunuzda, makineyi boyutlandırmadan önce havuzu boyutlandırın.
FAQ
Uygulamamda zaten bir bağlantı havuzu varsa yine de PgBouncer kullanmalı mıyım?
Genellikle evet, çünkü uygulama havuzu süreç bazlıdır ve diğer süreçleri göremez. Her biri 20 istek havuzu tutan dört Gunicorn çalışanı 80 arka uç oluşturur; iki arka plan çalışanı daha eklendiğinde bu sayı 120 olur. Toplam sayıyı gören ve sınırlayabilen tek bileşen PgBouncer'dır. İdeal düzenleme her ikisini de kullanmaktır: isteklerin TCP bağlantı maliyetinden kaçınması için her çalışan içinde küçük bir havuz ve bunların arkasındaki gerçek arka uçları sınırlayan transaction modunda bir PgBouncer.
PgBouncer'ı transaction moduna aldığımda tam olarak ne bozulur?
İşlemler (transaction) boyunca bir arka uçta durum (state) tutan her şey bozulur. Oturum seviyesindeki SET ve RESET, LISTEN, WITH HOLD imleçleri, SQL PREPARE ve DEALLOCATE ifadeleri, oturum seviyesindeki danışma kilitleri (advisory locks), bir commit işleminden sonra yaşaması gereken geçici tablolar ve LOAD. NOTIFY çalışmaya devam eder, bu da bozuk LISTEN işlemlerinin bir havuzlama hatasından ziyade teslimat hatası gibi görünmesine neden olur. Django üzerinde DISABLE_SERVER_SIDE_CURSORS ayarını True olarak yapılandırın. psycopg 3 üzerinde ise prepare_threshold ayarını None yapın veya max_prepared_statements değeri 0'dan büyük olacak şekilde PgBouncer 1.22 veya daha yeni bir sürüm çalıştırın. pg_advisory_lock() yerine pg_advisory_xact_lock() kullanın.
2 çekirdekli bir VPS üzerinde default_pool_size ne kadar olmalıdır?
Düşündüğünüzden daha küçük olmalıdır. Yaygın olarak kabul edilen HikariCP buluşsal yöntemi, çekirdek sayısının iki katı artı bir şeklindedir; yani iki çekirdek için yaklaşık 5, bu bir cevap değil başlangıç noktasıdır. Değeri ayarlayın, ardından gerçek yük altında SHOW POOLS içindeki maxwait ve cl_waiting değerlerini okuyun. Her ikisinin de sıfır olması havuzun yeterince büyük olduğu anlamına gelir. Artan bir maxwait değeri istemcilerin kuyrukta beklediğini gösterir; sayıyı artırmadan önce pg_stat_activity değerini kontrol edin: idle in transaction durumunda takılı kalan arka uçlar, daha fazla bağlantı ile sadece gizlenebilecek bir uygulama hatasıdır.
PgBouncer mı, PgDog mu?
Çoğu kurulumda olduğu gibi, tek bir VPS üzerinde tek bir PostgreSQL sunucusu için PgBouncer kullanılmalıdır. Ubuntu paket depolarında mevcuttur, davranışları iyi belgelenmiştir ve tüm yapılandırması tek bir ini dosyasından ibarettir. Okuma/yazma ayrımı (read/write splitting) veya parçalama (sharding) gerektiren durumlarda, uygulamanın topolojiyi bilmemesi için PgDog, havuzlama ile aynı sekmede (hop) yer almalıdır. PgDog kullanmaya karar vermeden önce, çalıştığınız yerdeki lisanslama sorumluları ile AGPLv3 konusunu netleştirin ve belirli bir sürümü sabitleyin; çünkü proje hala 0.x sürüm numaralarındadır ve haftalık olarak yeni sürümler yayınlamaktadır.