İçeriğe geç
Muhammet Şafak
en

Partial index on beş dakikada üç yüz beş 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 yaklaşık 845. saniyeye kadar beş bin satırda sabit dururken partial index 0,125 MB'dan 38,2 MB'a çıktı — üç yüz beş 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 çöktü — büyük olasılıkla belleğe sığmadığı için, ki bu ölçümde nedeni ölçülmedi: 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.

Partial index, 15 dakikada
0,125 → 38,2 MB 305×
Composite index, aynı sürede
301 → 427 MB +%42
Autovacuum koşma sayısı
0
Composite gecikmesi, 900. saniye
61.533 ms

Yöntem

Partial index ölçümüyle aynı tezgâh, aynı Postgres 17 ayarları, aynı 10 milyon ölü + 5.000 canlı satırlık tablo. Üç fark var, üçü de bu soruyu sorabilmek için zorunlu. Tüketici satırı `pending`'e geri koymuyor, `done` işaretliyor — sınanan iddia satırın index'ten DÜŞMESİYLE ilgili ve hiç düşmeyen satırda ölçülemez. Yanında sabit varış hızında (2.000/sn) bir üretici koşuyor, yani tablo önceki kaydın eksik ilan etmek zorunda kaldığı insert trafiğini de görüyor. Ve talep, `\gset` yerine alt sorgulu `UPDATE` biçiminde: kısıtsız bir tüketici kuyruğu bir saniyenin altında boşaltıyor ve `\gset` boş sonuçta istemciyi öldürüyor. Tüketici de üretici hızına bağlandı, böylece kuyruk derinliği sabit kalıyor — ve sabit kaldığı örneklenerek doğrulanıyor, varsayılmıyor. On beş saniyede bir tablo boyutu, index boyutu, kuyruk derinliği, ölü satır sayısı ve autovacuum sayaçları kaydedildi; strateji başına 60 örnek.

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

43 gün önce ölçüldü

Yayın

Ortam

Postgres
17-alpine · shared_buffers 1 GB · autovacuum varsayılan ayarlarda
Tablo
10M ölü + 5.000 canlı satır · 1.421 MB başlangıç
Yük
üretici 2.000/sn sabit · tüketici 8 istemci, aynı hızda
Süre
strateji başına 900 sn · 15 saniyede bir örnek · 60 örnek
Donanım
Apple M4 Pro · 12 çekirdek · 24 GB · Docker Desktop 29.7.2

Teknolojiler

PostgreSQL SQL pgbench Docker

Tekrarlamak için

DURATION=900 ./bench/endurance.sh

Partial index ölçümü otuz saniyelik pencerelerle çalıştı ve sınırlar kutusunda şunu yazdı: “Autovacuum açık bırakıldı; 30 saniyelik koşular onun uzun vadeli etkisini ölçmeye yetmez, bu ayrı bir kayıt konusu.” Bu o kayıt.

Sınanan iddia, Sor Bakalım cevabının tek ölçülmemiş maddesiydi: “Bir satır pending’den done’a döndüğünde partial index’ten düşer — bu iyi. Ama yoğun update trafiği bloat üretebilir; autovacuum’un sağlıklı çalışması önemli. İyi haber: index küçük olduğu için vacuum’u da ucuzdur.”

Bloat eğrisi

Index boyutu, sürekli churn altında

Kuyruk derinliği yaklaşık 845. saniyeye kadar beş bin satır civarında sabit. Büyüyen tek şey ölü index girdileri.

  • partial
  • composite

MB düşük olan iyi Kaynak: pg_relation_size, 15 saniyede bir örnek, bench/endurance.sh

Veri tablosu
pg_relation_size, 15 saniyede bir örnek, bench/endurance.sh
Seri 0 sn135286436587738888
partial 0,1 MB5,9 MB12,4 MB18,8 MB25,3 MB31,7 MB38,2 MB
composite 301 MB321 MB344 MB366 MB388 MB409 MB427 MB

Grafik tarayıcıda çizilir; aşağıdaki tablo aynı veriyi taşır.

Partial index üç yüz beş kat büyüdü. Composite %42. Mutlak artış composite’te daha fazla (+126 MB), oransal olarak partial’da karşılaştırılamaz.

Sebep tek cümlede duruyor: pending → done geçişi satırı partial index’ten düşürüyor, ama ölü girdi vacuum gelene kadar orada kalıyor. Yani index’in küçüklüğü canlı satır sayısından, şişme hızı iş hacminden geliyor — ve ikisi arasında hiçbir bağ yok. Beş bin satırlık bir kuyruk, saniyede iki bin iş işlerken, on beş dakikada otuz sekiz megabaytlık ölü girdi biriktiriyor.

Autovacuum hiç gelmedi

On beş dakikada 1.753.949 ölü satır birikti. Autovacuum sayacı: 0.

Aritmetik acımasız:

eşik = autovacuum_vacuum_threshold + scale_factor × canlı satır
     = 50 + 0,2 × 10.005.000
     ≈ 2.001.050 ölü satır

Eşiğin hemen altında kaldık. Varsayılan ayarlarla bu tablo autovacuum’u yaklaşık on yedi dakikada bir tetikleyecek ve arada index’ler serbestçe şişecek.

Asıl mesele oran değil, ölçekleme yönü: eşik tablonun tamamına göre büyüyor, churn ise küçük canlı kümede oluyor ve tablo büyüdükçe hızlanmıyor. Yani tablo ne kadar büyükse autovacuum o kadar geç geliyor.

Şişen index sürdürülebilir yükü taşımıyor

Her iki koşu da saniyede 2.000 iş hedefiyle başladı.

Strateji Başlangıç 900. saniye Kuyruk derinliği
partial 2.024 tps · 1,7 ms 2.016 tps · 4.906 ms 5.003 → 14.364
composite hedefi tutamadı 2.000 tps · 0,52 ms 1.204 tps · 61.533 ms 5.000 → 126.024
Gecikme pgbench'in zamanlama gecikmesini içeriyor: hedeflenen hıza yetişemeyen bir istemcinin borcu buraya yazılıyor.

Composite hedefi tutamadı — 2.000’den 1.204 tps’ye düştü, gecikmesi 61 saniyeye çıktı, kuyruğu 126 bine tırmandı. Partial hedef hızında bitirdi ama gecikmesini koruyamadı: 855. saniye civarında o da geriye düşmeye başladı, 4,9 saniyelik gecikme ve 14.364’lük kuyrukla bitirdi.

Bu, otuz saniyelik ölçümün tersi yönde bir sonuç. Orada ikisi neredeyse eşitti (10.795 vs 11.537 tps). Fark sürdürülebilir yükte açılıyor, büyük olasılıkla 427 megabayta şişmiş index artık belleğe sığmadığı ve taramalar diske gittiği için. Bu koşu cache isabetlerini kaydetmedi, yani neden bir çıkarım; fark ise ölçülmüş.

Cevaba dönüş

Sor Bakalım cevabının altı maddesinden beşincisi buydu ve tek sınanmayan oydu. Sonuç: madde doğru ama uyarısı zayıf. “Autovacuum’un sağlıklı çalışması önemli” cümlesi, varsayılan ayarlarla autovacuum’un bu tabloda sağlıklı çalışmadığını söylemiyor.

Partial index’i seçmenin bedeli iki kalemde: ölü index girdileri iş hacmiyle birikiyor, ve o birikimi temizleyecek mekanizma tablo bazında ayarlanmadıkça gelmiyor.

İlgili yazılar

Diğer Kayıtlar

Tüm kayıtlar

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 otuz beş kat. Aynı koşulda composite index etkilenmiyor.

43 gün önce ölçüldü

Yüksek güven

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 otuz beş 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 otuz beş katlık uçurum gerçek ama çitli: ona düşmek için `plan_cache_mode = force_generic_plan` yazmak gerekiyor.

43 gün önce ölçüldü

Yüksek güven

Bir çekirdek Go'da 14.330, PHP-FPM'de 5.152 OAuth2 isteği taşıyor

Her istekte RS256 bearer token doğrulayıp PostgreSQL'e bir satır yazan ya da okuyan aynı API, bir, iki ve dört çekirdekte Go, PHP-FPM ve FrankenPHP worker ile saniyede kaç karma istek taşıyor?

Bulgu

Dört çekirdekte Go 57.321, FrankenPHP 25.659, PHP-FPM 20.606 karma istek taşıdı; çekirdek başına 14.330, 6.415 ve 5.152. Kapasite planına giren sayı bu değil, istek başına uygulama CPU'su: Go 66,8, FrankenPHP 110,7, PHP-FPM 187,7 mikrosaniye. FrankenPHP doyduğunda dört çekirdeğin ancak 2,84'ünü kullanabiliyor — Go 3,83, PHP-FPM 3,87. Darboğaz veritabanı değil: aynı dört çekirdekte PostgreSQL tek başına saniyede 68.212 satır yazıyor, yani en hızlı adayın karma tavanının üstünde.

17 gün önce ölçüldü

Orta güven

Sitede Ara

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

Esc ile kapat Pagefind ile güçlendirildi