İçeriğe geç
Muhammet Şafak
en
Soran: Ekin Cevaplandı:

JSONB kolonundaki alanlarda filtrelemek için GIN index mi yoksa generated column mı tercih etmeliyim?


Soru

PostgreSQL'de bir tabloda `metadata` adlı bir JSONB kolonu tutuyorum ve sorgularımın çoğu bu blob içindeki birkaç sabit anahtara göre filtreleme yapıyor, örneğin `WHERE metadata->>'status' = 'active'` ve `WHERE metadata->>'region' = 'eu'`. Tablo büyüdükçe bu sorgular giderek yavaşlıyor. Bu alanlarda filtrelemeyi hızlandırmak için tüm JSONB'ye bir GIN index mi koymalıyım, yoksa bu anahtarları generated column'a çıkarıp mı index'lemeliyim? Hangisi daha doğru?

Cevap

Kısa cevap: Birkaç bilinen anahtara göre filtreliyorsanız cevap GIN değil.

Kısa cevap

Sabit anahtarlar için en hafif çözüm bir expression index’tir; değeri gerçek kolon olarak da istiyorsanız generated column. GIN’i dinamik anahtar ve containment sorguları için saklayın.

Neden

  1. İki farklı sorgu şekli, iki farklı araç. GIN, @> containment ve ? existence gibi operatörlerde ve önceden bilmediğiniz/çok sayıda anahtarda parlar. Generated column ve expression index ise bilinen, sabit birkaç anahtarda equality ve range için doğru araçtır. Sizin durumunuz ikincisi.

  2. Sizin durumunuz “birkaç sabit anahtar” → GIN overkill. GIN index büyüktür, yazma sırasında güncellemesi pahalıdır ve range/ordering’e (BETWEEN, ORDER BY) yaramaz. Sadece iki anahtar için tüm blob’u index’lemek, ihtiyacınızdan büyük ve yavaş bir yapı kurmaktır.

Ne yapmalı

  1. En hafif çözüm: expression index. Değeri ifade üzerinden doğrudan index’lersiniz — ne şema değişikliği ne de ekstra kolon gerekir. Sorgunuz index’teki ifadeyle birebir aynı ifadeyi kullandığı sürece planner onu kullanır.

    CREATE INDEX idx_meta_status ON items ((metadata->>'status'));
    CREATE INDEX idx_meta_region ON items ((metadata->>'region'));
    -- Sorgu ifadeyle birebir eşleşmeli:
    SELECT * FROM items WHERE metadata->>'status' = 'active';
  2. Generated column ne zaman: değeri gerçek kolon olarak da istiyorsanız. STORED bir generated column + b-tree index; planner istatistikleri (n_distinct, histogram) daha sağlıklı olur, sorgu daha okunur ve join’lerde kolona doğrudan başvurabilirsiniz. Bedeli: her anahtar için bir şema değişikliği ve storage tekrarı.

    ALTER TABLE items
      ADD COLUMN status text GENERATED ALWAYS AS (metadata->>'status') STORED;
    CREATE INDEX ON items (status);
  3. Cast tuzağına dikkat. ->>' her zaman text döner. Sayısal bir alanda range yapacaksanız ((metadata->>'age')::int > 18), cast’i hem index tanımında hem sorguda birebir aynı yazmalısınız — yoksa index kullanılmaz. NULL’lara ve boş değerlere de dikkat edin.

  4. GIN’i saklı tutun, silmeyin. Anahtarlar dinamikse, kullanıcı serbest metadata giriyorsa ya da metadata @> '{"tags":["x"]}' gibi containment yapıyorsanız GIN doğru araçtır — o zaman da yalnızca containment yapıyorsanız jsonb_path_ops varyantı daha küçük ve hızlıdır.

Sonuç: Ben olsam iki sabit anahtar için iki expression index açar, EXPLAIN (ANALYZE, BUFFERS) ile planın Seq Scan yerine Index Scan’e düştüğünü doğrulardım. Bu değerleri raporlarda ya da join’lerde de sık kullanıyorsam generated column’a terfi ettirirdim. GIN’i ancak anahtarlar öngörülemez hale gelirse ya da containment sorgularına geçersem devreye alırdım. Sabit anahtarda GIN, çekiçle ceviz kırmaktır.

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi