Kuyruk tablosunda index'i canlı kümeye göre kurmak
Postgres 17 kuyruk tablosunda partial index: 41 kat küçük, generic planda uçurum, 15 dakikalık churn altında bloat ve hiç gelmeyen autovacuum.

PostgreSQL · Ölçüm
Kuyruk tablosunda index'i canlı kümeye göre kurmak
Tek makine, Postgres 17, sentetik tezgâh: 30 saniyelik ölçüm, planner ve 15 dakikalık churn.
41×
10 milyon ölü satırda
daha küçük index
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.
10M ölü satır, 5.000 canlı satır
Index'siz kuyruk tablosu yok oluyor
tps
- index yok7
- (status)6.426
- (status, created_at)10.795
- partial, pending11.537
Aynı tablo, aynı index, aynı sorgu
Partial index generic planda
- 11.752tps, custom planIndex Scan, 0,68 ms
- 7tps, generic planSort → Seq Scan; index hiç taranmadı
- 1.113ms, generic gecikmeYaklaşık 1.635 kat; force_generic_plan ile zorlandı
- 11.416Composite, genericKanıtlanacak predicate'i yok, etkilenmedi
Varsayılan auto mod, tek oturum
Kırk çalıştırma, kırk custom plan
- İlk beş çalıştırma: üçü de custom plantamam
- Altıncı: composite generic'e geçertamam
- Partial: 40'ın 40'ı customtamam
- (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.
15 dk · 2.000 iş/sn · pg_relation_size, 15 sn'de bir
Canlı küme sabit, index'ler büyüyor
| Süre | partial | composite |
|---|---|---|
| 0 sn | 0,125 MB | 301 MB |
| 286 sn | 12,4 MB | 344 MB |
| 587 sn | 25,3 MB | 388 MB |
| 888 sn | 38,2 MB | 427 MB |
| Artış | ~305 kat | %42, +126 MB |
Hedef 2.000 iş/sn · 900. saniye
Otuz saniyede neredeyse eşit olan iki index, on beş dakikada ayrıldı
| 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ı; 427 MB belleğe sığmıyor | Hedefi tuttu |
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

Ne yapmalı
Kuyruk tablosunu canlı kümeye göre kurun
- Index'i kuyruğa kurun: partial (created_at) WHERE status = 'pending' (tamamlandı)
- Predicate'li index'e parametreyle ulaşmayın (tamamlandı)
- Autovacuum eşiğini tablodan koparın; etkisi ölçülmedi, 50.000 bir başlangıç (tamamlandı)
- Önce pending/done oranına, index boyutuna, n_dead_tup'a ve eşiğe bakın (tamamlandı)
- Kalıbı bırakın: tamamlananı hemen siliyor ya da arşive taşıyorsanız, ORM status'u parametre bağlıyor ve generic plan zorlanıyorsa, tabloyu zamana göre partition'lıyorsanız (bekliyor)

Tablo boyutu bir gürültüdür
Index'i ve vacuum'u, işi yapan kümeye göre kurun. Sayılar tek makineden, Postgres 17'den ve tek erişim deseninden; gerçek bir kuyruğun aylarını temsil etmez.