3 puan yazan GN⁺ 4 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • 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

 
GN⁺ 4 시간 전
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

    • PostgreSQL uzmanı değilseniz kendiniz işletmek yerine RDS gibi yönetilen bir veritabanı kullanmak daha iyi. Kendi barındırmanızla tasarruf edeceğiniz maliyet, kanıtlanmış yüksek erişilebilirlik, yedekleme-kurtarma, belirli bir ana geri yükleme ve okuma replikaları elde etmenin maliyetiyle karşılaştırıldığında önemsiz kalır
    • pgBackRest kullanıyorum. Eskiden kullandığımız gece yedeklemesi için kurum içi çözüme kıyasla daha iyi belirli bir ana geri yükleme sunuyor; Backblaze B2’ye yedekleme yapacak şekilde nispeten kolay kurduk ve pek sorun yaşamadık
    • Çoğu durumda cron’dan pg_dump_all çalıştırıp zstd ile 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 edebilirsiniz
    • Güç kesintisinde bile dayanıklılığı garanti eden bir veritabanıysa atomik volume snapshot ile yedeklenebilir. Kurtarma süresini azaltmak için önce checkpoint oluşturulmalı; veri bozulmasını önlemek için de snapshot’ın atomikliği mutlaka garanti edilmelidir
      AWS’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
    • Zaten Kubernetes işletiyorsanız CloudNativePG kullanabilirsiniz
  • 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 ASC gibi deterministik biçimde aynı tutulmalı ki deadlock önlensin
    EXPLAIN (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 tablolarda SET enable_seqscan = off ile indeks kullanma olasılığını kontrol edebilirsiniz
    Herkesin 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ırabilirler

    • Kilitlenecek satır kümesinde tutarlı bir ORDER BY olmaması dışında, tablo kilitleme sırası farklı olduğunda da deadlock oluşur. Bir transaction table_a, table_b sırasıyla kilitlerken başka bir transaction ters sırayla kilitlerse, her tablonun içinde ORDER BY ve FOR UPDATE kullansanız bile deadlock olur
      Teoride 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 AND ile OR arasındaki performans farkı da epey belirgindi
    • Hangi UUID olursa olsun primary key olarak kullanıldığında primary key join’leri sık olduğu için maliyeti yüksektir ve genelde getirisi azdır. Varsayılan olarak sıralı artan primary key kullanıp dışarıya açmak gerekiyorsa secondary index’li bir UUIDv4 sütunu eklemek daha güvenli. UUIDv7’nin B-tree performansının UUIDv4’ten gerçekten daha iyi olup olmadığını merak ediyorum
    • Sequential scan’i kapatırsanız PostgreSQL, herhangi bir indeks varsa onu zorla kullanır gibi geliyor. Bu yüzden bunun doğru indeks olup olmadığını da söyleyebileceğini sanmıyorum
    • UUIDv7 ve UUIDv4 dönüştürme araçları olarak https://github.com/ali-master/uuidv47 ve https://github.com/stateless-me/uuidv47 birkaç kez tanıtılmıştı
  • Bu 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; SERIALIZABLE da neredeyse hiç kullanılmasa daha iyi
    SELECT FOR UPDATE gibi açık kilitlemeye ihtiyaç duyuyorsanız tasarımın hatalı olma ihtimali var. type int değerine göre tek bir tablodaki satırlara farklı anlamlar yükleyerek tip sistemini yeniden icat etmeyin; kendine referans veren node ve edge tablolarıyla grafik veritabanı taklidi yapmayın. Çoğu şey sıradan normalize tablolarla çözülebilir

    • Üzerinde çalıştığım PHP backend’de yetki kontrolleri vb. için nesneleri örneklemek gerektiğinden ORM çok kullanışlı. ORM olmadan uygulamak çok daha fazla iş gerektirecek gibi görünüyor; neden kötü bir tercih olduğunu merak ediyorum
    • Geliştirici maaşları en büyük maliyetse ORM kullanmayın ilkesi tartışmalı. Tablonun iş gereksinimleri, müşteri baskısı ve sıkı bütçe altında DBA ile doğru tasarımı uzun uzun tartışırken de maliyet işlemeye devam eder; bu yüzden tip sütunlarından veya grafik benzeri yapılardan kaçınmak da söylendiği kadar kolay değil
    • Ürünü hızlıca ayağa kaldırması gereken startup’lar için ORM yeterince iyi bir tercih. N+1 sorguları ve lazy loading tarzı tuzakları anlıyorsanız, sorgu yönetimini ve parametreleştirmeyi bir de elle yazmaktan daha iyi bir uzlaşma
      Projenin 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 isterim
    • Append-only kaynak veri çekici, ancak üzerinde çalıştığım birçok sistemde şüpheli faydalar uğruna pek çok tablonun depolama kullanımını patlatırdı. Faydalı bir teknik olsa da her yerde zorunlu kılınacak bir ilke mi, emin değilim
      Tersine, 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

    • Deklaratif şema yönetim aracı pgschema’yı yaptım
  • 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ı hemen pending olarak güncellemek yeterli olduğundan SKIP LOCKED gerekmez
    Ö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_timeout ile boşta bekleyen transaction’ların kilitleri veya tuple’ları uzun süre tutması engellenmeli; migrasyonlarda lock_timeout ayarlanarak tek bir DDL’in tüm sistemi durdurması önlenmeli
    Pahalı bir sorgunun sistemi felç etmemesi için statement_timeout da 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, CASE ifadelerinin 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.

    • Bu yöntemin etkisi büyük ölçüde duruma bağlıdır. Join, orijinalden çok daha büyük bir Kartezyen çarpım oluşturuyorsa yalnızca kaynak kümeleri alıp yerelde birleştirmek DB yükünü ve ağ trafiğini azaltabilir.
      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.
    • Karmaşık tek bir sorgu yerine iki view oluşturup sonra join etme yönteminin de kullanıldığını biliyorum