Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?
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?
Cevap
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.
-
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. -
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 = $1gibi parametreyle sorarsanız planner index’i seçemeyebilir; koşulu literal'pending'olarak yazın ki eşleşme kanıtlanabilsin. -
ORDER BY kolonunu index’e koyun. Worker’lar tipik olarak
WHERE status='pending' ORDER BY created_at FOR UPDATE SKIP LOCKEDyapar. 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 deSKIP LOCKEDile kilitli satırları atlayıp sıradaki işi anında çekersiniz. -
Churn ve bloat’a dikkat. Bir satır
pending’dendone’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. -
Status üzerine düz index’ten kesinlikle daha iyidir. Düşük kardinaliteli
statuskolonuna düz bir index’in büyük kısmı işe yaramazdonedeğerleridir; planner çoğu zaman onu es geçip seq scan yapar. Bu erişim deseni için partial index net üstündür. -
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 = falsegibi 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:
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.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.