- Hatchet’in 2 yıl boyunca production’da yaşadığı sorunlara dayanarak, ilk şema ve sorgu tasarımından yoğun yazma işlemleri ile tablo migration’larına kadar aşamalı operasyon ilkelerini derliyor
- Hızlı okumalar için indekslerle
ORDER BY uyumlu olmalı; ancak sorgu planner’ı istatistik ve maliyetlere göre sequential scan seçebileceğinden, EXPLAIN ANALYZE ile tahminler gerçek yürütmeyle karşılaştırılmalı
- Yazma performansı ve kararlılığı kısa transaction’lara, yalnızca gerekli satırları kilitlemeye,
CREATE INDEX CONCURRENTLY kullanımına ve connection pooling’e bağlı; batch işleme, Hatchet ölçümlerinde throughput’u yaklaşık 10 kat artırdı
- Yüksek frekanslı yazma ortamlarında varsayılan autovacuum ayarları dead tuple’ları ve transaction ID’lerini zamanında temizleyemeyebilir; transaction ID wraparound’a ulaşıldığında ciddi downtime yaşanır
- Ölçek büyüdükçe
FOR UPDATE SKIP LOCKED tabanlı iş kuyruğu, partitioning, trigger’lar ve batch backfill kullanılmalı; ancak ORM soyutlamasının dışına çıkıp SQL’i doğrudan kontrol edebilmek gerekir
Hedef okur kitlesi ve ORM’nin sınırları
- SQL, satır, tablo ve indeksin temel kavramlarını bilen geliştiricilerin production Postgres sorunlarına yanıt verebilmesi için hazırlanmış bir rehberdir
- Postgres kılavuzu kapsamlı olsa da arıza anlarında hızlıca başvurmak zor olduğundan, Hatchet’in 2 yıl boyunca yaşadığı operasyon deneyimleri merkeze alınarak özetlenmiştir
- ORM kullansanız da ilkeler geçerlidir; ancak ölçek büyüdükçe soyutlama katmanının dışına çıkıp doğrudan SQL yazmayı gerektiren optimizasyonlar çoğalır
- Prisma TypedSQL gibi özelliklerle ORM ve doğrudan SQL birlikte kullanılabilir
- Go tabanlı Hatchet, benzer davranış sağlayan
sqlc kullanır
- Claude’un sorgu yazdığı ortamlar için supabase/agent-skills önerilir
Değiştirmesi zor şema tasarımı
- Dağıtımdan sonra şema değişikliği en zor işlerden biri olduğundan, tabloların ve primary key’lerin ilk taslağı oluşturulduktan sonra uygulamanın ihtiyaç duyduğu sorgular yazılarak tasarım yinelemeli biçimde geliştirilmelidir
- Tasarım sürecinde tablonun nasıl kullanılacağı şu sorularla doğrulanır
- Okuma mı yazma mı daha sık yapılacak
- Okurken en sık kullanılan filtreler nelerdir
- En sık güncellenen sütunlar nelerdir
- Veritabanı normalizasyonunun 1NF, 2NF ve 3NF kuralları uygulanabilir; ancak normal formlar, sorgu verimliliği veya hızlı geliştirme için gereken kullanım kolaylığıyla çatışabilir
- Bazı durumlarda veriyi
jsonb sütununa koymak daha basittir
- Şema tasarımında uygulanan pratik kurallar şunlardır
- Primary key için identity sütunu olan otomatik artan integer veya Postgres’in yerleşik UUID’si kullanılır
- Identity sütunları
bigserial’dan biraz daha hızlıdır
- Zaman için her zaman
timestamptz kullanılır
- Her tabloda primary key bulunur
- Tutarlılık ve doğruluğun önemli olduğu düşük hacimli tablolarda cascade delete içeren foreign key kullanılır; ancak yüksek hacimli ortamlarda dikkatli olunmalıdır
Okuma sorguları ve indeksler
- Hızlı
SELECT sorgularını anlamak için basit model şudur: Postgres bir satırı indeksle hızlıca bulur ya da sequential scan (seq scan) ile tablodaki tüm satırları okur
- Hızlı tek satır aramaları için şu yapılar kullanılır
- Açıkça tanımlanmış indeks
- İndeksin özel bir türü olan unique constraint
- Postgres’in otomatik olarak indekslediği primary key
- Varsayılan indeks
btree kullanır; bunu, veriyi aramalar için optimize edilmiş biçimde saklayan ayrı bir tablo gibi düşünebilirsiniz
- Satır arama süresi kabaca
log(n)’dir; burada n tablodaki satır sayısıdır
- İndeks kullanılamazsa sequential scan çalışır; ancak modern veritabanları satırları belleğe hızlıca alabildiğinden 20 bin satırdan küçük tablolarda bu işlem neredeyse anında bitebilir
Join’ler ve bileşik indeksler
- Inner join hedeflerinde genellikle primary key kullanılmalıdır; aksi halde şema tasarımında veya normalizasyonda sorun olabilir
ON koşulu da WHERE koşulu gibi ele alınmalı ve join koşulunda uygun indeks kullanılmalıdır
- Büyük tablolarda listeleme sorguları, uygulamada ilk yavaş hissedilen sorgular olmaya yatkındır
- Organizasyon ve oluşturulma zamanı birlikte filtrelenip sıralanıyorsa bileşik indeks kullanılabilir
CREATE INDEX CONCURRENTLY idx_documents_org_created
ON documents (organization_id, created_at DESC);
- Karmaşık sorgularda
ORDER BY sütununu indeksin sonuna yerleştirmek ve sıralama yönünü de eşleştirmek pratik bir kuraldır
- Postgres
btree’yi iki yönde de tarayabildiğinden tek sütunda DESC anlamsız olabilir; ancak bileşik indekslerde eşleştirmek daha iyidir
- Descending indekslerin ayrıntılı davranışı ilgili kaynakta görülebilir
Yazma, kilitler ve migration
- Başarılı yazmanın ilk koşulu transaction’ları kısa tutmaktır
- Özel bir neden yoksa transaction sırasında harici servisler sorgulanmaz
- İkinci koşul yalnızca gerekli satırları kilitlemektir
- Bir satır güncellendiğinde, transaction commit edilene kadar o satır kilitli kalır
- Sistem yükü arttıkça kilitlerin etkisi de belirginleşir
- Mevcut büyük bir tabloda normal
CREATE INDEX çalıştırmak tabloyu kilitleyip insert ve update işlemlerini engellediğinden, her zaman CREATE INDEX CONCURRENTLY kullanılır
- İyi şema migration yetkinliği, yinelemeli geliştirme hızını artırır ve uptime’ı yükseltir
- Mümkün olduğunca sütun silmekten veya kaldırmaktan kaçınıp ekleme odaklı değişiklik yapılır
- Mümkünse rollback ve kısmi uygulamalara karşı transaction içinde çalıştırılır
- Daha gelişmiş bir yöntem olarak expand and contract migration kullanılabilir
- Migration önce tüm yazmaları engelleyip engellemediğine göre değerlendirilmelidir
CONCURRENTLY olmadan indeks oluşturmak tüm yazmaları durdurup downtime’a yol açabilir
ALTER TABLE işlemleri yeniden gözden geçirilmelidir; büyük tablolara check constraint eklemek de yazmaları engelleyebilir
- Check constraint’i
NOT VALID olarak eklemek bu engellemeyi önleyebilir
Bağlantı yönetimi
- Tüm sorgular ve transaction’lar veritabanı bağlantısı kullanır; bağlantılar CPU ve bellek açısından maliyetli olduğundan uzun süre tutulmalıdır
- Bağlantıları sık sık oluşturup kaldırmak kaynak israfıdır
- Aynı anda çok sayıda yeni bağlantının oluştuğu connection storm, Postgres iç kilitleriyle ilişkili ve debug etmesi zor sorunlara yol açabilir
- Öncelikle harici connection pooler olan
pgbouncer değerlendirilmeli; kullanılamıyorsa alternatif olarak bellek içi connection pool kullanılmalıdır
- Hatchet, kullanıcıların veritabanının harici pooler kullandığını varsayamadığından Go için pgxpool kullanır
Sorgu planner’ı ve istatistikler
- Çok sayıda join içeren veya birden fazla join yöntemini karıştıran karmaşık sorgular, yalnızca indeks ekleyerek çözülmez
- İndekslerin kendisinin de overhead’i olduğundan sınırsız eklenmemelidir
- Sorgu planner’ı, SQL’i dahili veritabanı işlemlerine dönüştürür ve indeks kullanılıp kullanılmayacağı gibi kararları verir; ancak sınırlı bilgi nedeniyle en iyi planı seçemeyebilir
- Planner’ın kullandığı bilgi tablo istatistikleridir ve
pg_stats üzerinden sorgulanabilir
SELECT *
FROM pg_stats
WHERE tablename = 'mytable';
- İstatistikler
ANALYZE sırasında toplanır ve autovacuum çalıştığında da güncellenir
- Autovacuum sıklığı artırılırsa sorgu istatistikleri de güncel kalır
- Sorguların yanlış davranmasının yaygın nedenlerinden biri analiz sıklığının yetersiz olmasıdır
- Sorguları sequential scan yapıp yapmadığına göre basitçe değerlendirmek, planner’ın öngörülemezliğini mikro optimizasyonlarla artırmayı azaltabilir
- Primary key ve indeks merkezli sorgulama, planner’ın plan seçmesini kolaylaştırır
Yürütme planı analizi ve sequential scan
- Google CloudSQL gibi bazı sağlayıcılar sorguları örnekleyip yavaş sorguları saklar; ancak tüm servisler bunu desteklemez
EXPLAIN ANALYZE sorguyu gerçekten çalıştırır ve tablo istatistiklerine göre beklenen satır sayısını gerçek taranan satır sayısıyla karşılaştırır
- Production’da gerçek sorgu çalışacağı için dikkatli olunmalıdır
- Çalıştırmadan yalnızca planı görmek için
ANALYZE olmadan EXPLAIN kullanılır
- Ayrıntılı plan JSON olarak kaydedilip explain.dalibo.com üzerinde görselleştirilebilir
psql -XqAt -f explain.sql -d $DATABASE_URL > analyze.json
- İstatistikler ve indeksler normal olduğu halde sequential scan yapılıyorsa, planner sequential scan maliyetinin daha düşük olduğunu hesaplamış olabilir
- İndeksler gerçek tablo verisinin bulunduğu heap’ten ayrı saklandığından, indekste bulunan birden fazla satırı heap’ten tekrar okumanın maliyeti oluşur
- Sorguyu büyük ölçüde yeniden yapılandıramıyorsanız sequential scan’i kabul etmek veya partitioning’i değerlendirmek gerekir
Yoğun yazma ve batch işleme
- Her sorguda veritabanı round-trip süresi, uygulama connection pool’undan bağlantı alma süresi ve Postgres işleme süresi gibi overhead’ler vardır
- Postgres iç kilitleri de yüksek throughput ortamlarında darboğaz olabilir
- Bir sorguda birden fazla satırı gruplamak bu maliyetleri azaltabilir
- En basit yöntem, implicit transaction ile birden fazla sorguyu sunucuya tek seferde göndermektir
- Go’da
pgx’in SendBatch özelliği kullanılabilir
- Hatchet’te batch işleme throughput’u yaklaşık 10 kat artırdı; ek insert optimizasyonları hızlı Postgres insert rehberinde derlenmiştir
autovacuum ve transaction ID wraparound
- autovacuum, dead tuple temizliği ve transaction ID yönetiminden sorumludur; yüksek frekanslı yazma ortamlarında ayarların düzenlenmesi gerekebilir
- Tuple, dosya sisteminde saklanan bir satırın bir sürümüdür
- Bir satır güncellense veya silinse bile, ondan önce başlamış tüm transaction’lar commit ya da rollback olana kadar eski sürüm kalır
- Artık hiçbir transaction tarafından okunamayan sürüm dead tuple’dır
- Yazma hızı çok yüksekse autovacuum dead tuple oluşum hızına yetişemeyebilir ve veritabanı durumu hızla kötüleşebilir
pg_stat_activity üzerinden aktif süreçler kontrol edildiğinde autovacuum sorgusu yaklaşık 1 saatten uzun süredir çalışıyorsa ayar değişikliği değerlendirilmelidir
- Autovacuum temizlemeden önce tüm transaction ID’leri tükenirse transaction ID wraparound oluşur ve ciddi downtime’a yol açar
Tablo ve indeks şişmesi
- Postgres satırları diskteki 8KB page’lerde saklar; mevcut page’e yeni satır sığmazsa yeni page oluşturur
- Dead tuple’lar temizlendikten sonra page kısmen boş kalırsa table bloat oluşur ve disk kullanımı ciddi biçimde artabilir
- En iyi önleme yöntemi, şişme oluşmadan autovacuum’u ayarlamaktır
- Zaten şişmiş tablolar için
pg_repack gibi extension’lar kullanılabilir
- Yerleşik
VACUUM FULL neredeyse hiçbir zaman iyi bir seçenek değildir
- Postgres 19’a eşzamanlı tablo repack için
REPACK...CONCURRENTLY eklenecek, ancak Hatchet bunu henüz test etmedi
- Index bloat da table bloat’un özel bir biçimidir ve uygun autovacuum ayarlarıyla azaltılabilir
- Zaten şişmiş indeksler için yerleşik
REINDEX INDEX CONCURRENTLY komutu kullanılabilir
FOR UPDATE SKIP LOCKED tabanlı eşzamanlı işleme
FOR UPDATE SKIP LOCKED, seçilen satırları mevcut transaction için rezerve ederken diğer sorguları engellemez
- Hatchet bunu iş kuyruğu için kullanır; tek sorguda bekleyen işleri kilitleyip durumlarını
RUNNING olarak değiştirebilir
WITH eligible_tasks AS (
SELECT *
FROM tasks
WHERE status = 'QUEUED'
ORDER BY id ASC
FOR UPDATE SKIP LOCKED
LIMIT 100
)
UPDATE tasks
SET status = 'RUNNING'
FROM eligible_tasks
WHERE tasks.id = eligible_tasks.id
RETURNING tasks.*;
- Birbirinden bağımsız satırları eşzamanlı güncellerken veya birden fazla uygulama instance’ı nesnelerin lease’ini yönetirken de kullanışlıdır
- Hatchet, tenant lease’lerini birden fazla engine’e dağıtmak için kullanır
Partitioning
- Postgres’in yerleşik partitioning özelliği, timestamp veya hash gibi satır değerlerine göre tabloları böler
- Zaman serisi verilerinde ve Hatchet’in geçmiş iş verilerinde şu avantajları sağlar
- Her partition’da autovacuum bağımsız çalıştırılarak tablonun autovacuum işleme ölçeği büyütülebilir
- Eski veriler satır bazında silinmek yerine partition tablosu düşürülerek neredeyse anında kaldırılabilir
- Planlama aşamasında Postgres gereksiz partition’ları eleyemezse okuma sorgularında overhead oluşabilir
Büyük tablolar arasında veri taşıma
- Burada kastedilen büyük tablo migration’ı şema değişikliği değil, bir tablodan başka bir tabloya büyük miktarda veri taşıma işidir
- Çok büyük bir tabloyu tek transaction içinde kopyalamak saatler sürebilir
- Uzun süreli transaction’lar autovacuum’un normal çalışmasını engelleyerek dead tuple şişmesine yol açar
- Eski tabloya yazma devam ederse yeni tabloya bu veriler yansımaz
- Hatchet, transaction dışında büyük batch backfill çalıştırır ve migration başladıktan sonraki yeni yazmaları Postgres trigger’larıyla yeni tabloya kopyalar
- Primary key’in unique constraint’i kullanılarak yinelenen yazmalar önlenir
1 yorum
Hacker News yorumları
Operasyonel bir veritabanıysa bence ilk iş yedekleme ve kurtarma planı oluşturmak olmalı. Yüksek erişilebilirlik başlangıçta isteğe bağlı olabilir, ama bir hayatta kalma rehberinde yedekleme ve kurtarmanın eksik olması şaşırtıcı.
PostgreSQL yedeklemeleri için bugünlerde hâlâ Barman(https://pgbarman.org/) yaygın kullanılıyor mu merak ediyorum
pg_dump_allçalıştırıpzstdile sıkıştırdıktan sonra S3 veya FTP gibi yerlere kopyalamak yeterlidir. Veri büyüdükçe tam yedeklemenin süresi ve maliyeti yük olur, ama bu basit yöntemle bile oldukça uzun süre idare edebilirsinizAWS’de birkaç TB ölçeğinde MongoDB’yi EBS snapshot’larıyla yedekleyerek hızlı artımlı yedekleme ve kurtarma uygulamıştık. Belirli bir ana geri yükleme yok, ancak saatlik gibi sık aralıklarla alınabildiği için PostgreSQL’e özel araçlarla birlikte kullanılacak tamamlayıcı bir strateji olarak uygun
Eklenmesi gereken birkaç nokta var. Genel UUIDv4 yerine UUIDv7 kullanılmalı; kilitlenen satır sayısının yanı sıra tüm sorgularda kilit sırası
id ASCgibi deterministik biçimde aynı tutulmalı ki deadlock önlensinEXPLAIN (GENERIC_PLAN)kullanırsanız parametre yer tutucularını koruyarak sorguyu kopyalayabilir ve PostgreSQL’in gerçek değerleri bilmediğinde oluşturduğu optimizasyon planını da görebilirsiniz. Boş veya küçük tablolardaSET enable_seqscan = offile indeks kullanma olasılığını kontrol edebilirsinizHerkesin varsayılan olarak kullandığı B-tree indeksler ağırdır ve kolayca şişme eğilimindedir; sıralama ya da aralık araması olmadan yalnızca basit lookup yapıyorsanız hash indeksleri de değerlendirilebilir. Unique hash index oluşturulamaz, ama hash exclusion constraint ile benzer bir etki elde edilebilir; çok sütunlu unique index desteklemez
GIN ve GiST indekslerini de öğrenmek iyi olur. MySQL kullanıcılarına şaşırtıcı gelebilir ama full-text search’e geçmeden sıradan
LIKE '%foo%'sorgularını bile hızlandırabilirlerORDER BYolmaması dışında, tablo kilitleme sırası farklı olduğunda da deadlock oluşur. Bir transactiontable_a,table_bsırasıyla kilitlerken başka bir transaction ters sırayla kilitlerse, her tablonun içindeORDER BYveFOR UPDATEkullansanız bile deadlock olurTeoride açık bir konu, ama pratikte tüm yazma işlemlerinin dokunduğu tabloları küresel olarak kavramak gerektiği için debug etmek çok daha zor; belirli bir extension’da gerçekten başıma geldi. JSONB key-value lookup için GIN’i deniyorum; performans artışı çok büyüktü ve
ANDileORarasındaki performans farkı da epey belirgindiBu tavsiye de iyi, ancak birlikte çalıştığım startup’lar ölçeklenebilirlikten daha aşağı seviyedeki organizasyonel sorunlara önce çarptı. ORM kullanmamak; anlamlı alanlar yerine artan sıralı birincil anahtarlar kullanmak; JSONB’yi de yalnızca gerçekten gerektiğinde sınırlı biçimde kullanmak daha iyi
Kaynak veriler yalnızca ekleme yapılabilen, append-only yapıda tutulmalı; güncellenmemeli ve silinmemeli. Performans ve kolaylık için kullanılan denormalize yardımcı tablolar değiştirilebilir, ancak gerçeğin kaynağı olarak görülmemeli
Bağlantı havuzu kullanın ama bağlantı sayısına dikkat edin; sorun yoksa PgBouncer’a kadar gitmek gerekmeyebilir. Açık bir neden yoksa açık transaction’lardan kaçının; transaction açıkken RPC gibi uzun süren işler yapmayın;
SERIALIZABLEda neredeyse hiç kullanılmasa daha iyiSELECT FOR UPDATEgibi açık kilitlemeye ihtiyaç duyuyorsanız tasarımın hatalı olma ihtimali var.type intdeğerine göre tek bir tablodaki satırlara farklı anlamlar yükleyerek tip sistemini yeniden icat etmeyin; kendine referans verennodeveedgetablolarıyla grafik veritabanı taklidi yapmayın. Çoğu şey sıradan normalize tablolarla çözülebilirProjenin başında veritabanı şemasını gereğinden fazla dert edip erken optimizasyon yapmak yerine zamanı ürün geliştirmeye harcamayı tercih ederim
SELECT FOR UPDATE’u birçok yerde faydalı şekilde kullandım; sorunun ne olduğunu merak ediyorum. Append-only gerçeğin kaynağı kullanınca bu tür kilitlere gerek kalmıyor mu, onu da bilmek isterimTersine, geleneksel değiştirilebilir ilişkisel tabloları gerçeğin kaynağı olarak tutup trigger’larla değişiklik günlüğü yazmak nasıl olur, merak ediyorum
Cascade delete’den hoşlanmıyorum. Çoğu geliştirici veritabanından çok Python, Node, Go gibi uygulama katmanında yaşadığı için, A tablosundaki bir satırı silince B tablosundaki verilerin de kaybolması şeklindeki cascade delete kolayca sihir gibi görünebilir. Yanlış yapılandırılırsa daha da tehlikeli olur; bu yüzden uzun vadeli bakım için açık silme ifadeleri daha iyi, yabancı anahtarları doğru kullanmak bile tutarlılığı korumaya yeter
Büyük tablo migrasyonlarının tuzakları ve etrafından dolaşma yolları doğru, ama pg-osc gibi araçlar zaten var. Bir komut çalıştırıp verinin kopyalandığı 24 saat boyunca gergin biçimde izlemek kadar basit olmalı
Uygulama ve veritabanı dağıtımları erkenden ayrılmalı. Şema ve uygulama değişikliklerini tamamen aynı anda transaction içinde dağıtamayacağınız için, üretime girdikten sonra yeni sütunları nullable yapmak veya varsayılan değer koymak, tablo/sütun adlarını değiştirmemek gibi geriye uyumlu şema değişiklikleri yapma alışkanlığı gerekir
Şema yönetimi stratejisi de erken belirlenmeli. Kıdemli geliştiricinin kendi bilgisayarından production DB’ye elle DDL çalıştırdığı bir dağıtım sürecinden kaçınmak gerekir; Liquibase veya Flyway gibi aşina olunan araçlar kullanılabilir
Sorgu planlayıcı ortalama durumu optimize eder, ancak uygulamalar için en kötü durumu optimize etmek bazen daha faydalıdır. Ortalama kullanıcının satırı az olduğu için belirli bir indeksle sonuç 10 ms altında dönüyordu; ama yoğun kullanan kullanıcıda aynı sorgu, parametrelere bağlı olarak 1 saniyeden uzun sürüyordu
Daha karmaşık bir sorguyla farklı bir indeks yolunu zorladık; ortalama performans biraz yavaşladı ama en kötü durum da 100 ms altına indi. Şirket için ortalamada 10 ms tasarruftan çok timeout’ları önlemek önemliydi
SKIP LOCKED, uygulama çalışırken transaction’ı açık tutup satırı kilitleyen etkileşimli transaction tabanlı iş kuyrukları için faydalı. Yüksek performanslı uygulamalarda böyle transaction’lardan tamamen kaçınıp satırı hemenpendingolarak güncellemek yeterli olduğundanSKIP LOCKEDgerekmezÖlçek büyüdükçe veritabanı belleğinde tutulan durumu azaltmak gerekir; etkileşimli transaction’lar da bu durumun bir parçasıdır. Ölçekli ortamlarda idempotency, atomicity’den daha avantajlıdır
Uzun süren transaction’lar veritabanı durumuna zarar verebileceğinden yalnızca güçlü bir gerekçe varsa kullanılmalı.
idle_in_transaction_session_timeoutile boşta bekleyen transaction’ların kilitleri veya tuple’ları uzun süre tutması engellenmeli; migrasyonlardalock_timeoutayarlanarak tek bir DDL’in tüm sistemi durdurması önlenmeliPahalı bir sorgunun sistemi felç etmemesi için
statement_timeoutda ayarlanmalıStartup’ın ilk dönemlerinde PostgreSQL işletmiş biri olarak bu yazı izleme ve uyarıları yeterince vurgulamıyor. PostgreSQL’de mutlaka kaçınılması gereken birkaç temel arıza türü var ve uyarılar riski erken yakalayabilir
AWS, transaction ID wraparound’a yaklaşıldığını e-postayla bildirse bile startup’larda, özellikle Boxing Day gibi bir günde, bu kolayca gözden kaçabilir. AWS’nin izlediği sinyalleri e-posta yerine çağrı cihazına bağlamak gerekir
Bağlantı havuzu uygulamaları arasında pek bilinmeyen büyük farklar var. Çoğu uygulama bağlantı havuzu düşük gecikme ve bağlantı bulunurluğu için ilk giren ilk çıkar (FIFO) yaklaşımıyla optimize edilir; ancak bağlantıları sürekli sıcak tuttuğundan gereksiz bağlantıları azaltmak zorlaşır
PgBouncer ve bazı harici pooler’lar PostgreSQL’e ulaşan bağlantı sayısını ve throughput’u optimize etmek için son giren ilk çıkar (LIFO) kullanır. En yeni bağlantı önce yeniden kullanıldığında, artan bağlantılar doğal olarak soğur ve kapanır
Yeni uygulamalarda FIFO yeterlidir; ancak ölçek büyüdükçe PgBouncer gibi araçlarla yüzlerce bağlantıyı yaklaşık %90 azaltmak daha iyi olur. Her bağlantı için süreç oluşturan PostgreSQL mimarisi, bağlantı sayısı azaldıkça daha iyi çalışır
Çok belirli durumlarda uygulama belleğinde join yaparak iyi sonuçlar elde ettik. Veritabanına gidiş gelişleri azaltmaya çalışırken karmaşık
JOIN,UNION,CASEifadelerinin iç içe geçtiği tek bir sorgu oluşturulan durumlar olabiliyor.Bunun yerine birkaç basit sorguyu bağımsız olarak çalıştırıp sonuçlar üzerinde dolaşarak ilgili satırları bir map ile bağlarsanız, gidiş geliş ve iterasyon maliyeti eklense bile sorgu planı daha öngörülebilir hâle geldiği için avantajlı olabilir. Bunu yalnızca sınırlı biçimde kullanıyoruz; bazı ORM’lerin içeride bu şekilde çalışmasını da koşulsuz olarak önermiyoruz.
Ancak seçici inner join’ler orijinalden çok daha küçük sonuçlar ürettiğinden, tüm kayıtları alıp yerelde kesişim ve filtreleme yapmak çok daha pahalıdır. İndeks join’lerinde sorgu planlayıcı indeksi kullanarak kaba kuvvet tablo taramasından, sıralamadan ve filtrelemeden kaçınabilir.