Kuyruk tablosu: 30 saniyede kazanan, 15 dakikada kaybeden index
Klavye: ← → ile gezinin, F tam ekran, O genel bakış.

PostgreSQL · Kuyruk tablosu
30 saniyede kazanan, 15 dakikada kaybeden index
Aynı tablo, Postgres 17, üç ölçüm.
Muhammet Şafak
Tek makine, Postgres 17, sentetik tezgâh
30 saniyede neredeyse eşit. 15 dakikada ayrıldılar.
Partial ve composite index otuz saniyede 11.537'ye 10.795 tps. On beş dakikalık sürekli yükte composite hedefi tutamadı.
Postgres bu ayrımı bilmez
Bir tabloda iki küme
| Arşiv · done | Canlı küme · pending | |
|---|---|---|
| Kim bakar | Kimse sorgulamaz | Worker'lar arar, claim'ler kilitler |
| Boyut | Silinmezse sonsuza büyür | Kabaca sabit durur |
| Ölçülen | n_live_tup 10.005.726 | 5.000 satır |
| Postgres | Canlı tuple: index, istatistik ve vacuum eşiği onu sayar | Aynı muamele; ikisi ayırt edilmez |
Bölüm 01
Otuz saniye
Dört strateji, üç ölü satır kademesi. Canlı küme her kademede 5.000 satır.
SKIP LOCKED · 8 istemci · 30 sn · üç tekrarın medyanı
Üç kademe, dört strateji
| Strateji | 100k ölü satır | 1M | 10M | Index (10M) |
|---|---|---|---|---|
| index yok | 2.001 tps | 247 tps | 7 tps | — |
| (status) | 6.499 tps | 6.417 tps | 6.426 tps | 66,1 MB |
| (status, created_at) | 12.414 tps | 11.202 tps | 10.795 tps | 310,4 MB |
| partial (created_at) WHERE pending | 13.041 tps | 11.707 tps | 11.537 tps | 7,6 MB |
Index yok
Index'siz kuyruk yok oluyor.
Ölü satırla birlikte çökmüyor: 2.001'den 7 tps'ye. 10 milyon satırın arasından 5.000 satırı sequential scan saniyede yedi kez bulabiliyor.
41×
Asıl fark boyutta
daha küçük index
10 milyon ölü satırda partial 7,6 MB, composite 310,4 MB. Throughput farkı yalnız %6,9: seçme sebebi hız değil, index'in tabloyla birlikte büyümemesi.
Ölü satır arttıkça
Partial index tabloyla büyümüyor
index boyutu, MB
- composite · 100k11,2
- composite · 1M40,3
- composite · 10M310,4
- partial · 100k6,5
- partial · 1M7,6
- partial · 10M7,6
Bölüm 02
Planner
Aynı tablo, aynı index, aynı sorgu. Değişen tek şey planın türü.
10M ölü satır · aynı sorgu, parametreyle
Generic plan uçurumu
| Index · plan | tps | Gecikme | Plan |
|---|---|---|---|
| partial · custom | 11.752 | 0,68 ms | Index Scan |
| partial · generic | 7 | 1.113 ms | Sort → Seq Scan |
| composite · custom | 11.105 | 0,72 ms | Index Scan |
| composite · generic | 11.416 | 0,70 ms | Index Scan |
Neden
Yaklaşık 1.635 kat
- NotGeneric plan $1'in ne olduğunu bilmeden kurulur. status = $1'in 'pending' olduğunu kanıtlayamaz, partial index'i eler.
- İpucuComposite etkilenmez: kanıtlanacak bir predicate'i yok, status index'in içindeki bir kolon.
- UyarıUçuruma düşmek için force_generic_plan gerekti. Bu bir üretim ayarı değil.
Tek oturum, varsayılan auto mod
Kırk çalıştırma, kırk custom plan
- 00İlk beş çalıştırma: üçü de custom plantamam
- 01Altıncı: composite generic'e geçertamam
- 02Partial: 40'ın 40'ı customtamam
- 03(status): 40'ın 40'ı customtamam
Düzeltme
Bu koşullarda geçmedi
- DüzeltmeÖnceki kaydımda 'ısındıkça bozulur' demiştim. Ölçüldüğünde yanlış çıktı; kırk çalıştırmanın kırkı custom plan kaldı.
- NedenEn olası açıklama: partial index'i kullanamayan generic planın tahmini maliyeti yüksek çıkıyor. Plan maliyetleri kaydedilmedi.
- BedelPartial her çalıştırmada yeniden planlanır. Bu ölçekte fark ölçülemedi: 0,20 ms'ye 0,21 ms.
Bölüm 03
Şimdi 15 dakika
Sürekli churn: saniyede 2.000 iş, varsayılan autovacuum ayarları.
Partial · ~305 kat
Canlı küme sabit, index büyüyor
index boyutu, MB
pg_relation_size, 15 saniyede bir örnek, bench/endurance.sh. pending→done geçişi vacuum'a kadar ölü girdi bırakır.
Composite · %42, +126 MB
Composite daha az oranla, daha çok MB şişti
index boyutu, MB
pg_relation_size, 15 saniyede bir örnek, bench/endurance.sh
Hedef 2.000 iş/sn · 900. saniye
Şişen index sürdürülebilir yükü taşımıyor
| composite | partial | |
|---|---|---|
| Başlangıç | 2.000 tps · 0,52 ms | 2.024 tps · 1,7 ms |
| 900. saniye | 1.204 tps · 61.533 ms | 2.016 tps · 4.906 ms |
| Bekleyen iş | 5.000 → 126.024 | 5.003 → 14.364 |
| Sonuç | Hedefi tutamadı | Hedefi tuttu |
Okurken
İki not
- NotGecikme pgbench'in zamanlama gecikmesini içeriyor: hedef hıza yetişemeyen istemcinin borcu buraya yazılıyor.
- NotFark sürdürülebilir yükte açılıyor: 427 MB'a şişen index artık belleğe sığmıyor, her tarama diske gidiyor.
Eşik tabloyla büyür
Autovacuum hiç gelmedi
- 1,75Milyon ölü satır15 dakikada 1.753.949
- 0AutovacuumBir kez bile koşmadı
- ≈2Eşik, milyon satır50 + 0,2 × 10.005.000 ≈ 2.001.050
- 17Dakika, tahmini aralıkBu iş yüküne özgü; vacuum maliyeti ölçülmedi
Öneri
Eşiği tablodan koparın
Bu ayarı ölçmedim. 50.000 bir başlangıç noktası; kendi throughput'unuza göre ayarlayın.
1ALTER TABLE jobs SET (2 autovacuum_vacuum_scale_factor = 0,3 autovacuum_vacuum_threshold = 500004);56CREATE INDEX CONCURRENTLY7 jobs_pending_created_at8 ON jobs (created_at)9WHERE status = 'pending';
Sonuç
Sor Bakalım cevabındaki iddialar, ölçülünce
- Partial index küçük kalır: 10M'de 7,6 MB (tamamlandı)
- (status) index'inden üstün: 11.537'ye 6.426 tps (tamamlandı)
- ORDER BY kolonu index'e girmeli (tamamlandı)
- Parametreyle planner partial'ı seçemeyebilir: doğru ama dar (tamamlandı)
- Churn ve bloat: ilk kayıtta ölçülmedi, sonradan ölçüldü (tamamlandı)

Koşulu bulun
Bir tavsiyeyi ölçmenin değeri onu doğrulamak değil. Değeri, doğru bir cümlenin hangi koşulda tersine döndüğünü bulmakta.