# Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?

> Evet — bu partial index'in ders kitabı örneği. WHERE status='pending' yalnızca birkaç bin satırı indeksler; küçük, cache-dostu, milyonlarca ölü satırı atlar. ORDER BY kolonunu da koyup SKIP LOCKED ile eşleyin.

- Soruldu: 2026-08-08
- Yanıtlandı: 2026-08-11
- Soran: Yiğit
- Etiketler: postgresql, index, performans
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Postgres'te bir `jobs` kuyruk tablom var. İçinde milyonlarca `completed` satır birikiyor ama ben yalnızca birkaç bin `pending` satırı sorguluyorum: worker'lar `WHERE status = 'pending' ORDER BY created_at` ile sıradaki işi çekiyor.

`status` üzerine normal bir index mi, yoksa `WHERE status = 'pending'` koşullu bir partial index mi kullanmalıyım? Partial index'i kullanırken planner'ın onu gerçekten seçmesi için nelere dikkat etmeliyim?


Kısa cevap: Evet, partial index tam da bu senaryonun ders kitabı örneğidir. `WHERE status = 'pending'` koşullu bir index yalnızca birkaç bin satırı tutar; küçük olur, belleğe sığar, taraması hızlı ve güncellemesi ucuzdur — milyonlarca ölü satıra hiç dokunmadan geçer.

1. **Partial index yalnızca eşleşen satırları indeksler.** `CREATE INDEX ... WHERE status='pending'` ile index milyonlarca değil birkaç bin girdi tutar. Bu, onu neredeyse tamamen cache'te tutar ve her insert/update'te güncellenmesi gereken ağacı küçültür.

2. **Planner'ın predicate'i eşleştirmesi gerekir.** Sorgunuzun `WHERE`'i, index predicate'inin (`status='pending'`) mantıksal olarak kapsadığı bir koşul olmalı. `status = $1` gibi parametreyle sorarsanız planner index'i seçemeyebilir; koşulu literal `'pending'` olarak yazın ki eşleşme kanıtlanabilsin.

3. **ORDER BY kolonunu index'e koyun.** Worker'lar tipik olarak `WHERE status='pending' ORDER BY created_at FOR UPDATE SKIP LOCKED` yapar. Sıralama anahtarını index'e dahil edin: `(created_at) WHERE status='pending'`. Böylece hem index sırasında tarama alırsınız hem de `SKIP LOCKED` ile kilitli satırları atlayıp sıradaki işi anında çekersiniz.

4. **Churn ve bloat'a dikkat.** 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.

5. **Status üzerine düz index'ten kesinlikle daha iyidir.** Düşük kardinaliteli `status` kolonuna düz bir index'in büyük kısmı işe yaramaz `done` değerleridir; planner çoğu zaman onu es geçip seq scan yapar. Bu erişim deseni için partial index net üstündür.

6. **Aynı mantık her çarpık predicate için geçerlidir.** Ders yalnızca kuyruklara özgü değil: `WHERE deleted_at IS NULL` (soft-delete), `WHERE processed = false` gibi tablonun küçük bir azınlığını hedefleyen her sorguda partial index aynı kazancı verir. "Az sayıda satırı, çok sayıda ölü satırın arasından" çektiğiniz her yerde aklınıza gelsin.

SQL şöyle:

```sql
CREATE INDEX idx_jobs_pending
    ON jobs (created_at)
    WHERE status = 'pending';

-- worker'ın çekiş sorgusu:
SELECT id, payload
FROM jobs
WHERE status = 'pending'
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 1;
```

**Sonuç:** Ben olsam çekiş predicate'i üzerine, `ORDER BY` kolonunu da içerecek şekilde bir partial index kurar ve onu `FOR UPDATE SKIP LOCKED` ile eşlerdim. Tek dikkat noktası: sorgularınızın `'pending'`'i literal geçmesi ki planner index'i gerçekten seçsin. `EXPLAIN (ANALYZE, BUFFERS)` ile index scan aldığınızı bir kez doğrulayın; sonra milyonlarca `completed` satırın birikmesine rahatça aldırmazsınız.
