Kuyruk tablosu: 30 saniyede kazanan, 15 dakikada kaybeden index

Klavye: ← → ile gezinin, F tam ekran, O genel bakış.

Tan, dizüstü bilgisayarıyla

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

FOR UPDATE SKIP LOCKED ile claim; 8 istemci, 30 saniye, üç tekrarın medyanı. Canlı küme her kademede 5.000 satır.
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 · 100k 11,2
  • composite · 1M 40,3
  • composite · 10M 310,4
  • partial · 100k 6,5
  • partial · 1M 7,6
  • partial · 10M 7,6
pg_relation_size, bench/run.sh

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

Aynı tablo, aynı index, aynı sorgu. Partial index generic planda hiç taranmadı: Limit, LockRows, Sort, Seq Scan.
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

  • Not Generic plan $1'in ne olduğunu bilmeden kurulur. status = $1'in 'pending' olduğunu kanıtlayamaz, partial index'i eler.
  • İpucu Composite 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

  1. İlk beş çalıştırma: üçü de custom plan tamam
  2. Altıncı: composite generic'e geçer tamam
  3. Partial: 40'ın 40'ı custom tamam
  4. (status): 40'ın 40'ı custom tamam

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ı.
  • Neden En olası açıklama: partial index'i kullanamayan generic planın tahmini maliyeti yüksek çıkıyor. Plan maliyetleri kaydedilmedi.
  • Bedel Partial 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

Canlı küme sabit, index büyüyor (index boyutu, MB): 0 sn: 0,125, 135 sn: 5,9, 286 sn: 12,4, 436 sn: 18,8, 587 sn: 25,3, 738 sn: 31,7, 888 sn: 38,2.

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

Composite daha az oranla, daha çok MB şişti (index boyutu, MB): 0 sn: 301, 135 sn: 321, 286 sn: 344, 436 sn: 366, 587 sn: 388, 738 sn: 409, 888 sn: 427.

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

  • Not Gecikme pgbench'in zamanlama gecikmesini içeriyor: hedef hıza yetişemeyen istemcinin borcu buraya yazılıyor.
  • Not Fark 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,75 Milyon ölü satır 15 dakikada 1.753.949
  • 0 Autovacuum Bir kez bile koşmadı
  • ≈2 Eşik, milyon satır 50 + 0,2 × 10.005.000 ≈ 2.001.050
  • 17 Dakika, tahmini aralık Bu 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.

jobs.sql
ALTER TABLE jobs SET (  autovacuum_vacuum_scale_factor = 0,  autovacuum_vacuum_threshold    = 50000);CREATE INDEX CONCURRENTLY  jobs_pending_created_at  ON jobs (created_at)WHERE status = 'pending';
Tan, başparmağını kaldırmış onaylarken

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ı)
Tan, gülümserken

Koşulu bulun

github.com/muhammetsafak/pg-queue-bench

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.

Paylaş, göm, indir