İçeriğe geç
Muhammet Şafak
en

Postgres partial index'i generic plana çevirmedi: kırk çalıştırma, kırk custom plan

Hazırlanmış bir deyimde Postgres partial index'li sorguyu kendiliğinden generic plana çeviriyor mu — yoksa bin altı yüz yetmiş üç katlık uçuruma ancak elle mi düşülüyor?

Bulgu

Postgres geçişi reddediyor. Partial index'te kırk çalıştırmanın kırkı da custom plan — sayaç 40/0. Reddetmesinin sebebi tam olarak felaketin kendisi: generic plan partial index'i kullanamaz, bu yüzden tahmini maliyeti yüksek çıkar ve planner onu seçmez. Composite index ise ders kitabındaki gibi altıncı çalıştırmada geçiyor (5/35) ve hiçbir şey kaybetmiyor. Yani bin altı yüz yetmiş üç katlık uçurum gerçek ama çitli: ona düşmek için `plan_cache_mode = force_generic_plan` yazmak gerekiyor.

Partial index, generic plan
0 / 40 çalıştırma
Composite index, geçiş
6. çalıştırma
Partial, ilk 5 → son 5
0,36 → 0,20 ms
Uçuruma düşmek için gereken
force_generic_plan

Yöntem

Aynı tezgâh, aynı 10 milyon ölü + 5.000 canlı satırlık tablo, üç index stratejisi. Her strateji için tek bir psql oturumunda tek bir hazırlanmış deyim kırk kez çalıştırıldı ve her çalıştırmadan sonra `pg_prepared_statements` sorgulandı — sayaçlar oturuma özgü olduğu için bu ölçüm pgbench'ten yapılamaz. Karar zamanlamadan çıkarsanmıyor, sayaçtan okunuyor: `custom_plans` ve `generic_plans` alanları planner'ın o çalıştırmada ne seçtiğini doğrudan söylüyor. Zamanlamalar çift geliyor (deyimin kendisi, sonra sayaç sorgusu) ve ayrıştırıcı yalnız ilkini alıyor. psql'in hizalı çıktısı sayaç değerini boşlukla dolduruyor; çıktı bu yüzden hizasız moda alındı — ilk koşu bu yüzden boş seri üretmişti.

Yüksek güven Tekrarlı ölçüm, denetimli ortam, ham veri paylaşıldı.
Ölçüm tarihi

dün ölçüldü

Yayın

Ortam

Postgres
17-alpine · plan_cache_mode varsayılan (auto)
Tablo
10M ölü + 5.000 canlı satır
Ölçüm
tek oturum · tek hazırlanmış deyim · 40 çalıştırma
Karar kaynağı
pg_prepared_statements.custom_plans / generic_plans
Donanım
Apple M4 Pro · 12 çekirdek · 24 GB · Docker Desktop 29.7.2

Teknolojiler

PostgreSQL SQL Docker

Tekrarlamak için

EXECUTIONS=40 ./bench/plan-switch.sh

Partial index ölçümü plan_cache_mode’u iki uca sabitledi ve aralarında bin altı yüz yetmiş üç kat buldu: custom planda 11.752 tps, generic planda 7. Ölçmediği şey herkesin gerçekten koştuğu ayardı — auto.

O kayıt orada bir cümle kurdu: “profil ısındıkça bozulur… staging’de görünmez.” Makul bir çıkarımdı ve yanlış çıktı.

Karar tahmin edilmedi, okundu

Postgres hazırlanmış bir deyimi ilk beş çalıştırmada custom planla koşar, sonra generic planın tahmini maliyetini custom planların ortalamasıyla karşılaştırır ve ancak generic daha ucuz görünüyorsa ona geçer. pg_prepared_statements bu kararı sayaç olarak tutuyor, yani zamanlamadan çıkarsamaya gerek yok.

Strateji İlk generic plan Son sayaç (custom/generic) İlk 5 (ms) Son 5 (ms)
partial geçiş reddedildi hiç 40 / 0 0,36 0,20
composite 6. çalıştırma 5 / 35 0,40 0,21
(status) hiç 40 / 0 0,98 0,78
Tek oturum, tek hazırlanmış deyim, kırk çalıştırma. Sayaçlar her çalıştırmadan sonra okundu.

Composite’in geçişi belgelendiği gibi, tam beşinciden sonra:

#5  0,266 ms   custom=5  generic=0
#6  0,275 ms   custom=5  generic=1   ← geçti
#7  0,185 ms   custom=5  generic=2

Partial index’te bu hiç olmuyor. Kırk çalıştırma, kırk custom plan.

Reddetmesinin sebebi felaketin kendisi

Planner generic planı seçmiyor çünkü maliyetini doğru tahmin ediyor. Generic plan $1’in ne olduğunu bilmez; partial index’i kullanabilmek için $1 = 'pending' olduğunu kanıtlaması gerekir, kanıtlayamaz, dolayısıyla o plan sequential scan’e düşer ve tahmini maliyeti tavan yapar. Custom planların ortalaması bunun çok altında kalınca karşılaştırma her seferinde custom lehine sonuçlanıyor.

Gerçek takas daha küçük ve başka yerde

Korumanın bir bedeli var: partial index’li sorgu her çalıştırmada yeniden planlanıyor. Kırk çalıştırma, kırk plan üretimi. Composite altıncıdan sonra aynı planı tekrar kullanıyor.

Bu ölçekte fark ölçülemiyor — 0,20 ms’ye karşı 0,21 ms. Ama plan üretimi ucuz bir sorguda ucuz; çok tablolu bir join’de ya da uzun bir IN listesinde değil. Partial index’in gizli maliyeti bloat (bkz. dayanıklılık ölçümü) ve sürekli yeniden planlama; ikisi de bu iş yükünde küçük, ikisi de başka bir iş yükünde küçük kalmayabilir.

Önceki kaydı düzeltiyor

Partial index kaydının “ısındıkça bozulan profil” paragrafı fazla ileri gitmişti ve o kayda bu bulguya işaret eden bir not düşüldü. Ölçümün kendi yayınını düzeltmesi bu bölümün sözleşmesinin gereği: bir sayı yayınlanıyorsa, onu yanlışlayan sayı da yayınlanır.

İlgili yazılar

Paylaş:

Diğer Kayıtlar

Tüm kayıtlar

Partial index on beş dakikada üç yüz seksen kat şişti — ve autovacuum bir kez bile gelmedi

Sürekli churn altındaki bir kuyruk tablosunda partial index'in küçüklüğü kalıcı mı, ve varsayılan autovacuum ayarları ona yetişiyor mu?

Bulgu

Canlı küme beş bin satırda sabit dururken partial index 0,1 MB'dan 38,2 MB'a çıktı — üç yüz seksen kat. Küçüklüğü canlı satır sayısından geliyor, şişme hızı iş hacminden, ve ikisi arasında hiçbir bağ yok. Composite index oransal olarak daha az şişti (%42) ama mutlak olarak daha çok (+126 MB) ve şişerken belleğe sığmayı bıraktı: gecikmesi 0,52 ms'den 61 saniyeye çıktı, kuyruğu 126 bine tırmandı. On beş dakikada 1,75 milyon ölü satır birikti ve autovacuum **bir kez bile koşmadı** — varsayılan eşik tablonun tamamına göre ölçekleniyor (50 + 0,2 × 10 milyon ≈ 2 milyon), churn ise küçük canlı kümede oluyor.

dün ölçüldü

Yüksek güven

Partial index kuyruk tablosunu kırk bir kat küçültüyor — planner onu seçtiği sürece

Milyonlarca ölü satır biriken bir Postgres kuyruk tablosunda partial index ne kazandırıyor, ve planner onu ne zaman seçmiyor?

Bulgu

10 milyon ölü satırda partial index saniyede 11.537 iş çıkarıyor, index'siz tablo 7. Composite index'le hız farkı küçük (%6,9) ama boyut farkı değil: 7,6 MB'a karşı 310,4 MB, ve partial tabloyla birlikte büyümüyor çünkü yalnız 5.000 canlı satırı indeksliyor. Asıl bulgu bunların hiçbiri: planner hazırlanmış bir deyimde generic plana geçtiği anda partial index tamamen devre dışı kalıyor — 11.752 tps 7'ye, 0,68 ms 1,1 saniyeye düşüyor. Bin altı yüz yetmiş üç kat. Aynı koşulda composite index etkilenmiyor.

dün ölçüldü

Yüksek güven

Laravel'in preload eğrisi: 123 dosya, 1.912 dosyadan sekiz kat fazla kazandırıyor

Laravel için elle seçilmiş bir preload nereye kadar iner, ve her dilim ne kadar açılış bedeline mal olur?

Bulgu

Eğri hacimle orantılı değil. İlk 1.592 dosya (Laravel çekirdeği) 30 ms kazandırıyor ve açılışa 1,2 saniye ekliyor. Sonraki 1.094 Symfony dosyası 9,5 ms kazandırıyor, bedava. Ondan sonraki **123 dosya** (psr, carbon) 15,7 ms kazandırıyor — kendinden önceki 1.094 dosyadan fazla. Ve son 1.912 dosya yalnız 1,8 ms kazandırıp açılışa 1,2 saniye daha yazıyor. Yani önceki kaydın tavan olarak ölçtüğü "hepsini derle", eğrinin başlangıç noktası dışındaki en kötü fiyat/performans bölgesi: 2.809 dosyada durmak 12,77 ms ve 1.514 ms açılış verirken, 4.721 dosya 10,96 ms için 2.691 ms istiyor.

dün ölçüldü

Yüksek güven

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi