PostgreSQL kurulup çalıştığı gün "veritabanı konusu kapandı" deriz; sonra yıllarca aynı üç özelliği kullanarak ilerleriz. Oysa en pahalı üç kaynak olan diski, belleği ve yazma maliyetini ciddi biçimde küçülten iki özellik çoğu ekibin radarına hiç girmez: kısmi indeks ve materialized view. İkisi de sihir değildir ama doğru senaryoda yüz binlerce satırlık tabloyu birkaç megabayta indirir, saniyeler süren raporu milisaniyeye sıkıştırır.

İkisinin ortak paydası da ders verdiği ilkedir: veritabanı kaynakları, verinin tamamına değil, verinin önemli kısmına harcanmalı. Hangi satırın önemli olduğu ise uygulamanın değil, işin kararınızdır; PostgreSQL bu kararı şemada ifade etmenin iki yolunu sunar.

Kısmi indeks: yalnızca önemli satırlar

Sipariş tablonuzdaki kayıtların yüzde doksan beşi "tamamlandı" durumunda olsun; uygulamanız ise gün boyunca yalnızca "beklemede" olanları sorguluyor. Klasik indeks, o dokuz yüz bin gereksiz satırı da belleğe taşır, her yeni kayıtta onları da günceller. Kısmi indeks yalnızca ilgilendiğiniz alt kümeyi saklar:

CREATE INDEX idx_siparis_bekleyen
    ON siparislar (olusturma_tarihi)
    WHERE durum = 'beklemede';

-- Bu sorgu indeksi kullanır:
SELECT id FROM siparislar
WHERE durum = 'beklemede'
ORDER BY olusturma_tarihi DESC;

İndeks on kat küçüldüğü için tamamen önbelleğe sığar; sorgular hızlanır, yazma maliyeti düşer. Aynı özellik iş kurallarını veritabanına yedirmek için de kullanılır: silinmemiş kullanıcının e-postasının benzersizliği, klasik unique indeksin yapamadığı bir kuraldır. CREATE UNIQUE INDEX komutunu WHERE silinme_tarihi IS NULL koşuluyla yazdığınızda silinmiş adresler yeniden kullanılabilir, aktif adresler ise tekillikten ödün vermez.

Materialized view: pahalı sorguyu bir kez ödeyin

Normal bir görünüm her çağrıda altındaki sorguyu yeniden çalıştırır. Materialized view ise sonucu diskte saklar: yüz binlerce satırın birleştirilmesi, gruplanması ve toplanması bir kez yapılır, sonra herkes hazır tabloyu okur. Gece raporları, yönetim paneli toplamları, anasayfadaki "en çok okunanlar" kutusu tam olarak bu profile sahiptir.

CREATE MATERIALIZED VIEW gunluk_satis AS
SELECT tarih, kategori, SUM(tutar) AS toplam
FROM satislar
GROUP BY tarih, kategori;

-- Kilitlenmeden yenileme için önce tekillik gerekir:
CREATE UNIQUE INDEX ON gunluk_satis (tarih, kategori);
REFRESH MATERIALIZED VIEW CONCURRENTLY gunluk_satis;

Bedeli tazeliktir: görünüm, onu yalnızca yenilediğiniz kadar günceldir. Bu yüzden saniyelik gecikmenin kabul edildiği raporlarda ve öneri kutularında parlar; stok ya da bakiye gibi kritik okumalarda klasik sorgu daha doğru tercihtir.

Bedelini bilerek kullanın

İki özellik de ücretsiz değil. Kısmi indeksin koşulu, planlayıcının sorgudaki koşulla eşleştirebileceği kadar basit olmalı; karmaşık bir ifade yazarsanız indeks sessizce yok sayılır. Materialized view tarafında ise yenilemeyi planlamanız gerekir: gece yarısı cron bağlantısı, pg_cron ya da uygulama tarafındaki bir zamanlayıcı. Yenilemede CONCURRENTLY kullanmazsanız okuyan sorgular kilitlenir; bunu önlemenin yolu da yukarıdaki unique indekstir.

PostgreSQL'in gücü egzotik eklentilerden değil, yerleşik ama az konuşulan özelliklerinden gelir. Yazma yolunu hafifleten kısmi indeks ve okuma yolunu önceden ödeyen materialized view, ikisi birlikte "donanım alalım" konuşmasını bir hafta erteleyebilir. Önce mevcut şemanızda deneyeceğiniz yer de bellidir: en kalabalık tablonuzda durumu belli olan satırları süzen sorgular ve gece yarısı öncesi süren raporlar. Sıradaki yavaş dashboard'da önce bu ikisini deneyin; sunucu büyütmek her zaman en pahalı iyileştirmedir.