Teknoloji

VPS’te PostgreSQL’i Uçurmak: shared_buffers, work_mem, WAL ve PgBouncer’ı Ne Zaman, Nasıl Ayarlarız?

Bir Gün, Tek Bir Sorgu Her Şeyi Durdurdu: Giriş

Hiç küçük bir VPS’te her şey yolundayken bir anda uygulamanın ağır çekime geçtiğini gördünüz mü? Ben gördüm. Bir öğleden sonra, hiç aklıma gelmeyen bir rapor sorgusu, bütün ekibin çay molasını uzatan bir mini felakete dönüştü. CPU tavan, disk ışığı yanıp sönüyor, web tarafı yavaşlamış… Derken şunu fark ettim: veritabanı ayarları kurulum günü neyse hâlâ oydu. Varsayılanlar iyidir ama her zaman değil. Hele ki VPS üzerinde, RAM ve disk gibi kaynaklar sınırlıyken, küçük dokunuşlar bile oyunu değiştiriyor.

Bu yazıda, PostgreSQL’i VPS üzerinde daha akıcı hale getirmek için dört temel taşı konuşacağız: shared_buffers, work_mem, WAL ve checkpoint ayarları, bir de PgBouncer ile bağlantı havuzu. Her birini günlük hayattan örneklerle, “neden” ve “ne zaman” sorularına cevap arayarak ele alacağız. Arada ufak config parçaları paylaşacağım, ama asıl amacım kafanızda sade bir yol haritası çizmek. Mesela şöyle düşünün: RAM bir mutfak tezgâhı, disk bir kiler, bağlantılar da mutfağa giren çıkıp duran insanlar. Ne kadar düzenli olursa, o kadar hızlı hazırlarsınız yemeği.

shared_buffers: Mutfakta Tezgâh Gibi, Elinizin Altı Ne Kadar Geniş?

PostgreSQL’in kendi belleğinde tuttuğu sayfa önbelleği diyebileceğimiz shared_buffers, en kritik ayarlardan biri. Çok küçük olursa her adımda diske bakmak zorunda kalırsınız, çok büyük olursa bu kez sistemin genel dengesi bozulur ve işletim sistemi önbelleğini köreltirsiniz. VPS’teki RAM’iniz sınırlıyken, buraya dengeli yaklaşmak şart. Benim pratik yaklaşımım, önce uygulamanın davranışını izlemek ve okuma ağırlıklı mı, yazma ağırlıklı mı anlamak. Sonra küçük adımlarla büyütmek.

Mesela şöyle düşünün: gün içinde hep aynı ürün listesine bakılıyor, aynı kullanıcı profilleri çekiliyor, yani sıcak veri bir şekilde tekrar tekrar kullanılıyor. Bu durumda shared_buffers’ı artırmak yüz güldürür. Ama veri sürekli değişiyor, yoğun yazma yapılıyor, bir de raporlar diske abanıyorsa, çok agresif bir değer bazen geri teper. Birkaç deneme, gözlem ve ufak dokunuş burada en iyi öğretmen.

Başlangıç için sade bir örnek:

# /etc/postgresql/XX/main/postgresql.conf veya dağıtımınıza göre ilgili dosya
shared_buffers = 1GB    # VPS RAM durumunuza göre temkinli başlayın
effective_cache_size = 3GB  # OS önbelleğini kabaca tahmin etmek için rehber değer

Buradaki effective_cache_size doğrudan bellek ayırmaz, ama sorgu planlayıcısına “dışarıda şu kadar önbellek var” diye fısıldar. Plansız iş kalmasın diye bu tür değerleri, gerçek kullanımı izledikten sonra ayarlamak iyi olur. “Kaç GB yapayım?” derseniz, büyütüp küçülterek, diskten okuma oranınızı ve sorgu sürelerinizi izleyin. Bir noktadan sonra artışın faydası azalır, bunu hissettiğiniz anda durmak en sağlıklısı.

Resmi belgelere göz atmak isterseniz, resmi PostgreSQL belgelerindeki bellek ayarlarının açıklamaları sade bir referans olarak elinizin altında dursun. Orada çok teknik detaya girmeden, hangi ayarın neye dokunduğunu görmek moral veriyor.

work_mem: Sırayı Bekleyen Sorgulara Küçük Bir Masa Ayırmak

work_mem, sıralama ve hash gibi işlemler için sorgu başına ayrılan geçici çalışma alanı gibi düşünebilirsiniz. Küçücük olduğunda PostgreSQL diske dökülür, yani sıralama için dosyalar oluşturur ve yavaşlar. Çok büyük yaptığınızda ise eşzamanlı sorgular, her biri ayrı ayrı bu alanı kullanabildiği için belleği bir anda eritebilir. İşte denge tam burada başlıyor.

Bir keresinde tek bir rapor sorgusunun, altındaki iki üç alt-sorguyla birlikte belleği nasıl şişirdiğini görmüştüm. Sorguların sayısı arttıkça “bir anda niye swap’e geçtik?” sorusunun cevabı meğerse work_mem’in cömertliğindeymiş. Çözüm, tek bir devasa değer yerine, bilinçli bir orta yoldu. Önce eşzamanlı bağlantı sayısını ve tahmini ağır sorgu sayısını düşündüm, sonra work_mem’i makul bir seviyeye çektim. Gün bittiğinde, herkes daha hızlı rapor aldığını söyledi.

Ayarlarken şöyle bir akıl yürütme iş görüyor: aynı anda kaç büyük sıralama bekliyorsunuz? Uygulama yoğunluğunuz ne zaman artıyor? Örnek bir başlangıç:

work_mem = 32MB           # Yavaş sıralamalar varsa yavaş yavaş artırın
maintenance_work_mem = 256MB  # VACUUM/CREATE INDEX gibi işler için, boşta iken biraz cömert davranılabilir

İşin güzel yanı, work_mem’i oturum bazında da değiştirebilirsiniz. Diyelim ki sadece bir bakım işi yapacaksınız, o oturum için geçici artırıp iş bitince eski haline bırakabilirsiniz:

-- psql içinde
SET work_mem = '128MB';
-- ağır bir raporu çalıştırın, iş bittiğinde kapatın

Burada amaç; sırf bir kez hızlansın diye her oturuma lüks tanımak değil, bilinçli jestler yapmak. Sorgularınızın sıklığını, boyutunu ve eşzamanlılığını aklınızda tutarsanız, work_mem sizi tatlı bir dengeye taşır.

WAL, Checkpoint ve Disk Yazımı: Sessizce İşleyen Bir Sigorta

WAL (write-ahead log) kulağa karmaşık gelebilir ama basitçe şunu yapar: veritabanına bir değişiklik yapmadan önce o değişikliği güvenle bir günlük dosyasına yazar. Böylece elektrikler gidip geldiğinde veri tutarlılığı korunur. VPS’te disk genelde en yavaş kısımdır; o yüzden WAL ayarları, performansın nabzını tutar. Çok sık checkpoint olursa disk yorulur; çok seyrek olursa da crash sonrası toparlanma uzar. Burada kilit, sizin iş yükünüz ve risk iştahınız.

Ben genelde şu yoldan giderim: önce checkpoint aralıklarını biraz ferahlatır, WAL dosyalarının çok hızlı şişmemesine de dikkat ederim. Sonra disk grafikleriyle, yük altında davranışı izlerim. Sanki bir makinenin ritmini dinlemek gibi. Bir süre sonra, “tamam, burada tatlı bir akış yakaladık” diyebiliyorsunuz.

Uygulanabilir bir başlangıç seti şöyle olabilir:

wal_level = replica            # Mantıksal çoğaltma yoksa replica genelde yeter
max_wal_size = 2GB             # Uygulamaya göre artırılabilir
min_wal_size = 512MB
checkpoint_timeout = 15min     # Çok sık checkpoint yapmasın
checkpoint_completion_target = 0.9  # Kontrol noktalarını zamana yayarak disk tepe noktalarını azaltır
synchronous_commit = on        # Veri güvenliği öncelikli, bazı işlerde off/remote_write düşünülebilir
wal_compression = on           # WAL boyutunu azaltabilir, CPU bedeli olabilir

Bir de şu var: güvenlik ve hız arasında bir ip var. synchronous_commit gibi ayarlar, “önce güven mi, önce hız mı?” sorusunu fısıldar. Kritik veride kural bellidir; ama bazı günlük log benzeri tablolarda tolerans varsa seçici davranılabilir. Bunu rastgele değil, uygulamanın veri akışını ve kayıp toleransını bilerek yapmak en doğrusu.

Merak ederseniz WAL ve ilgili ayarların resmi dökümünü hızlıca karıştırmak, her bir parametrenin mizacını görmenize yardım eder. Ben bazen bir parametreye dokunmadan önce buradaki kısa açıklamalara bakarım; fazla sürpriz istemiyorsanız işe yarıyor.

PgBouncer: İnce Kapıdan Geçen Kalabalıklar İçin Sessiz Bir Düzenleyici

Uygulama sunucularının her istekte yeni bir veritabanı bağlantısı açıp kapattığı dünyada, veritabanının işi bitmiyor. İşte PgBouncer burada devreye giriyor ve bağlantı havuzu yönetimiyle veritabanını nefeslendiriyor. VPS’te CPU ve RAM zaten sınırlı; her bağlantı açılıp kapandıkça kaynaklar parça parça harcanıyor. Havuz, bu gidiş gelişleri azaltarak daha akıcı bir trafik sağlar.

Benim en çok fayda gördüğüm an, web uygulamasında anlık trafik sıçramaları yaşandığında oldu. Bağlantı havuzu, PostgreSQL’e bir anda yüzlerce kapı açmak yerine, sırayı kibarca düzenledi. Bir yandan sorgular bitiyor, bir yandan yeni gelenler bekliyor ama ortalık yıkılmıyor. Özellikle bekleyen sorgular kısa sürüyorsa, bu düzen mucizeler yaratıyor.

Basit bir PgBouncer yapılandırması şu kafayı anlatır:

; /etc/pgbouncer/pgbouncer.ini
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb user=appuser password=secret

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction    ; transaction genelde web iş yüklerinde iyi sonuç verir
max_client_conn = 500      ; uygulama tarafındaki bağlantı üst sınırı
default_pool_size = 50     ; veritabanına açılacak gerçek bağlantı sayısı
reserve_pool_size = 10     ; ani sıçramalar için küçük bir tampon
server_idle_timeout = 300

Uygulama tarafında veritabanı adresini PgBouncer’ın portuna çevirince, çoğu durumda sihir başlar. Yine de şunu unutmayın: pool_mode seçiminiz çok önemlidir. transaction modu, her işlem bittiğinde bağlantıyı havuza iade eder ve web uygulamaları genelde bunu sever. Session modu, bağlantıyı oturum boyunca tutar; bazı işlerde bu da gerekebilir. Her iş yükünün huyu farklıdır, o yüzden ufak testler iyi fikir.

Resmi ayar referansı için PgBouncer konfigürasyon sayfasına göz atmak, özellikle time-out ve havuz boyutu gibi parametreleri tanırken yol gösterir. Ben bazen bu sayfayı açık tutup loglarla birlikte, “kim nerede bekliyor” sorusunu izleyerek ufak rötuşlar yaparım.

Ayarladık, Peki Nasıl Ölçeriz? İzleme, Loglar ve Küçük Ritüeller

Tüm bu ayarlar, ölçmeyince havada kalıyor. Benim iş rutinimde, önce birkaç metrikle barışmak var: ortalama sorgu süresi, disk bekleme süreleri, bağlantı sayıları, checkpoint zamanlaması ve autovacuum davranışı. Bunu kurmanın yolları bol; önemli olan, bir kez bakıp bırakmamak. Zamana yayılmış grafikler, özellikle VPS’te “bu aralıkta ne olmuş?” sorusunu çok net yanıtlıyor.

İşin görsel tarafını seviyorsanız, daha önce paylaştığımız VPS izleme ve alarm kurulumunu Prometheus ve Grafana ile ayağa kaldırmak yazısına göz atabilirsiniz. PostgreSQL metriklerini katınca, hangi ayarın nasıl bir dalga yarattığını anlamak çok kolaylaşıyor. Hatta loglarınızı derli toplu tutmak için Loki ve Promtail ile merkezi loglama rehberi de işinizi hafifletir; bir hata anında, bağlantı havuzuyla veritabanı loglarını yan yana görmek bazen hayat kurtarıyor.

Bir diğer küçük ritüelim, konfigürasyon değişikliklerini sakin zamanlarda ve mümkünse kısa bir bakım penceresinde uygulamak. Örneğin, sıfır kesintiye yakın deneyimler için VPS’e sıfır kesinti CI/CD akışı kurmak, konfigürasyon dosyalarını versiyonlayıp kontrollü dağıtım yapmayı kolaylaştırıyor. Sadece uygulama değil; veritabanı ayar dosyaları da bu ritme dahil olunca, sürprizler azalıyor.

Ölçmek demişken, PostgreSQL içinde anlık bir bakış için şu ufak komutlara göz atmak keyifli olabilir:

-- psql içinde
SHOW shared_buffers;
SHOW work_mem;
SHOW checkpoint_timeout;
SELECT * FROM pg_stat_activity LIMIT 5;  -- örnek bir durum turu

Bir de temizlik işlerini unutmayalım. Autovacuum ayarları ihmal edildiğinde, tablolar şişer ve sorgular hantallaşır. Yoğun yazma yapan tablolarda autovacuum daha sık ve hızlı çalışsın isteyebilirsiniz; daha sakin tablolarda ise “yerini bilsin, sessizce” davranması yeter. Burada da yine küçük ayarlar ve ölçüm size yol gösterir.

Eğer veritabanı ayarlarını canlıya taşırken uygulama tarafında korkular baş gösteriyorsa, dağıtım adımlarını planlarken geliştirme, staging ve canlı ortam arasında sıfır kesintiye yakın bir yolculuk yazısından ilham alabilirsiniz. Oradaki yaklaşım, veritabanı tarafındaki küçük ama etkili değişiklikleri de yumuşatıyor.

Birlikte Tutan Harç: Uygulama Kalıpları, İndeksler ve Sorgu Kokusu

Şimdi bir adım geri çekilelim. shared_buffers ve work_mem’le bellek akışını, WAL’la disk ritmini, PgBouncer’la bağlantı trafiğini ele aldık. Peki ya sorgular? Çoğu zaman asıl farkı, iyi kokan sorgular ve yerinde indeksler yaratıyor. Birkaç kere “neden hâlâ yavaş?” diye sorduğumda cevap, eksik bir indeks ya da gereğinden fazla veri döndüren masum bir sorgu çıktı. Burada araç basit: önce yavaş sorguları yakalayın, sonra EXPLAIN ile planı görün, ardından küçük dokunuşlarla iyileştirin.

Mesela şöyle düşünün: ürünler tablonuzda en sık arama kategoriye ve yayında olma durumuna göre yapılıyor. Buna uygun bileşik bir indeks, mucize gibi gelir. Sorgu planı, diskte dolaşmak yerine doğrudan hedefe gider. Sonra paylaşılan önbellekler devreye girer, aynı sorgular daha da hızlanır. Yani ayarların verdiği fayda, iyi sorgularla katlanarak artıyor.

Burada detaylara dalmak isteyenler için, referans okumasını sakince yapmak güzel: WAL mantığının kısa bir tanıtımı ve bellek ayarlarının neye dokunduğunu anlatan sayfalar, terimleri insanileştiriyor. Teknik gibi görünen her şey, küçük küçük okuyunca anlamlı bir hikâyeye dönüşüyor.

Kapanış: Küçük Ayarlar, Büyük Nefes

Toparlayalım. VPS üzerinde PostgreSQL’i hızlandırmak için harikalar yaratmaya gerek yok. shared_buffers ile elinizin altındaki sıcak veriyi akıllıca büyütün. work_mem ile sorgularınıza küçük ama isabetli çalışma alanları verin. WAL ve checkpoint ayarlarıyla diske yazma ritmini sakinleştirin, ani tepe noktalarını dağıtın. PgBouncer ile bağlantıları usulca sıraya sokun, veritabanını gereksiz kapı açmalardan koruyun. Sonra da bütün bu hikâyeyi izleyin, ölçün, küçük adımlarla düzeltin.

Pratik bir tavsiye: ayarları bir anda değil, adım adım değiştirin. Her adımda önce gözlem yapın, sonra bir sonrakine geçin. Gerekirse bir akşamüstü trafiğin az olduğu saatleri seçin, değişikliği uygulayıp loglara ve grafiklere bakın. Eğer tüm bu adımların yanında ağ, TLS ve ters proxy gibi katmanları da ele almak isterseniz, hızlı ve güvenli HTTPS’nin sıcak rehberi gibi yazılar da yaklaşımınıza omuz verir.

Umarım bu rehber aklınızdaki düğümleri biraz gevşetmiştir. Sorularınız olursa not alın, ilk fırsatta test ederek cevaplayın. Küçük dokunuşlar, düzenli ölçümler ve sabırlı bir yaklaşım; genelde en güzel performansı getiriyor. Bir sonraki yazıda yeniden görüşmek üzere; veritabanınız hızlı, loglarınız berrak, geceleriniz huzurlu olsun.

Sıkça Sorulan Sorular

Tek bir sihirli değer yok. Küçük başlayın, sorgu sürelerini ve diskten okuma oranını izleyin, sonra yavaş yavaş artırın. Etki azalmaya başlayınca durun.

Her zaman değil. Çok artırırsanız eşzamanlı sorgular belleği tüketebilir. Sıralama yavaşsa kontrollü yükseltin, ardından sistemi izleyerek dengeyi bulun.

Her istek bağlantı açıp kapadığında veritabanı yorulur. PgBouncer, bu bağlantıları havuzlayıp yeniden kullanır; ani trafik artışlarında sistemi sakin tutar.