JSONB — GIN index, so'rovlarni tezlashtirish

~20 daq · PostgreSQL · PostgreSQL Maxsus Imkoniyatlari

Muammodan boshlaylik

Oldingi darsda JSONB ga ma'lumot qo'shib, @> operatori bilan qidirdik. Kichik jadvalda tez ishladi. Lekin jadvalda 1 million mahsulot bo'lsa nima bo'ladi?

PostgreSQL har bir qatorning xususiyatlar ustunini ochib, ichida "ranglar": ["qora"] bormi deb tekshiradi. 1 million qator — 1 million tekshiruv. Bu — Seq Scan (to'liq jadval skanerlash). Sekin.

Index qo'yilsa — PostgreSQL oldindan tayyorlangan "qidirish xaritasi" orqali bir zumda topadi. JSONB uchun bu xarita — GIN index.


GIN index nima?

GIN (Generalized Inverted Index) — teskari indeks. Har bir kalit va qiymat qaysi qatorlarda borligini alohida saqlaydi.

Hayotiy o'xshatma: kitob oxiridagi mavzular ko'rsatkichi. "PostgreSQL" so'zi kitobning 12, 45, 78-betlarida bor — bu ko'rsatkichda yozilgan. Kitobni sahifa-sahifa o'qimasdan, ko'rsatkichdan darhol topasiz. GIN index — shu ko'rsatkich.

Oddiy qidiruv (Seq Scan):       GIN index bilan:
──────────────────────────      ──────────────────────────
1-qator → JSONB ochib tekshir   "qora" → [1, 2, 3] ←
2-qator → JSONB ochib tekshir   darhol topildi!
3-qator → JSONB ochib tekshir
...
1 000 000-qator → ...

GIN index yaratish

-- Butun JSONB ustuniga GIN index
CREATE INDEX idx_mahsulotlar_xususiyatlar
ON mahsulotlar USING GIN (xususiyatlar);

Bu bitta index — xususiyatlar ustunidagi barcha kalit va qiymatlarni qamrab oladi.

Index yaratilgandan keyin quyidagi operatorlar avtomatik tezlashadi:

@>    ichida tuzilma bormi
?     kalit bormi
?|    kalitlardan biri bormi
?&    kalitlarning hammasi bormi

Index qancha tezlashtiradi?

-- Index yo'q — Seq Scan
EXPLAIN ANALYZE
SELECT * FROM mahsulotlar
WHERE xususiyatlar @> '{"brend": "Dell"}';

-- Seq Scan on mahsulotlar (cost=0.00..2540.00 rows=100)
-- Execution time: 1240 ms
-- GIN index bor — Bitmap Index Scan
EXPLAIN ANALYZE
SELECT * FROM mahsulotlar
WHERE xususiyatlar @> '{"brend": "Dell"}';

-- Bitmap Index Scan on idx_mahsulotlar_xususiyatlar (cost=0.00..12.50 rows=100)
-- Execution time: 3 ms

1240 ms → 3 ms. 400 barobar tez.


jsonb_path_ops — yengilroq index

Standart GIN index kalitlar, qiymatlar va ularning kombinatsiyasini saqlaydi. jsonb_path_ops esa faqat @> uchun optimallashtirilgan — kichikroq, tezroq:

CREATE INDEX idx_mahsulotlar_path
ON mahsulotlar USING GIN (xususiyatlar jsonb_path_ops);
Standart GIN:          jsonb_path_ops:
──────────────         ──────────────────
@> ✅                  @> ✅
?  ✅                  ?  ❌ (ishlamaydi)
?| ✅                  ?| ❌
?& ✅                  ?& ❌

Faqat @> ishlatilsa — jsonb_path_ops tanlang: hajmi kichik, tezroq. ? operatorlari ham kerak bo'lsa — standart GIN.


Muayyan kalit uchun index

Ba'zan butun JSONB emas, faqat bitta kalit bo'yicha qidirish kerak. Masalan, faqat brend bo'yicha. Ifodalangan indeks — expression index:

CREATE INDEX idx_mahsulotlar_brend
ON mahsulotlar ((xususiyatlar ->> 'brend'));

Bu B-tree index — oddiy ustun indeksiga o'xshash. Faqat brend kalit bo'yicha qidirishni tezlashtiradi:

-- Bu so'rov endi B-tree index ishlatadi
SELECT * FROM mahsulotlar
WHERE xususiyatlar ->> 'brend' = 'Dell';

GIN dan qachon afzal? GIN butun JSONB uchun katta index. Agar faqat bitta-ikkita kalit bo'yicha ko'p qidirilsa — expression index kichikroq va ko'pincha tezroq.


Partial index — faqat kerakli qatorlar uchun

Agar mahsulotlarning faqat bir qismi qidirilsa — masalan, faqat aktiv mahsulotlar — partial index qo'yish mumkin:

CREATE INDEX idx_aktiv_mahsulotlar
ON mahsulotlar USING GIN (xususiyatlar)
WHERE aktiv = true;  -- ← faqat aktiv qatorlar

Bu index hajmi kichik — chunki faqat aktiv qatorlar kiritilgan.


Amaliy misol — qidirish so'rovlari va ularning indexlari

Vaziyat: elektron do'kon qidiruvi

-- 1. Brend bo'yicha qidirish — expression index yaxshi
CREATE INDEX idx_brend ON mahsulotlar ((xususiyatlar ->> 'brend'));

SELECT * FROM mahsulotlar
WHERE xususiyatlar ->> 'brend' = 'Dell';

-- 2. Xususiyat mavjudligini tekshirish — GIN kerak
CREATE INDEX idx_gin ON mahsulotlar USING GIN (xususiyatlar);

SELECT * FROM mahsulotlar
WHERE xususiyatlar ? 'noise_cancelling';

-- 3. Rang bo'yicha qidirish — GIN @> bilan
SELECT * FROM mahsulotlar
WHERE xususiyatlar @> '{"ranglar": ["qora"]}';

-- 4. Narx oralig'i + JSONB kombinatsiyasi
SELECT * FROM mahsulotlar
WHERE narx BETWEEN 500000 AND 2000000
  AND xususiyatlar @> '{"brend": "Sony"}';
-- PostgreSQL ikki indexni birlashtiradi: narx (B-tree) + JSONB (GIN)

Index ishlatilayaptimi? — EXPLAIN bilan tekshirish

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM mahsulotlar
WHERE xususiyatlar @> '{"brend": "Dell"}';

Natijada qidirish:

Bitmap Index Scan on idx_gin  ← index ishlatildi ✅
  Index Cond: (xususiyatlar @> '{"brend": "Dell"}')

Seq Scan on mahsulotlar       ← index ishlatilmadi ❌
  Filter: (xususiyatlar @> '{"brend": "Dell"}')

Seq Scan ko'rsak — index yo'q yoki ishlatilmayapti. Index Scan yoki Bitmap Index Scan — index ishlayapti.


Tez-tez uchraydigan xatolar

->> bilan qidirishda GIN index ishlamasligini bilmaslik:

-- GIN index bor, lekin bu so'rovda ISHLAMAYDI
SELECT * FROM mahsulotlar
WHERE xususiyatlar ->> 'brend' = 'Dell';
-- Seq Scan!

-- GIN index ISHLAYDI
SELECT * FROM mahsulotlar
WHERE xususiyatlar @> '{"brend": "Dell"}';
-- Bitmap Index Scan ✅

GIN index faqat @>, ?, ?|, ?& operatorlari bilan ishlaydi. ->> bilan qidirish uchun expression index kerak.

Katta jadvalga index qo'shishni kechiktirish:

-- Katta jadvalga index qo'shish vaqt oladi va jadval lock bo'ladi
CREATE INDEX ON mahsulotlar USING GIN (xususiyatlar);
-- Production da bu bir necha daqiqa davom etishi mumkin

-- ✅ CONCURRENTLY — jadval lock bo'lmaydi, sekinroq lekin xavfsiz
CREATE INDEX CONCURRENTLY ON mahsulotlar USING GIN (xususiyatlar);

Production jadvalda index qo'shishda har doim CONCURRENTLY — jadval bloklashmasdan index quriladi.


Xulosa

GIN index — JSONB qidiruvini Seq Scan dan Index Scan ga o'tkazadi. Katta jadvalda yuzlab barobar tezlashtiradi.

Standart GIN          → @>, ?, ?|, ?& barchasi uchun
jsonb_path_ops GIN    → faqat @>, kichikroq va tezroq
Expression index      → bitta kalit bo'yicha qidirish uchun
Partial index         → faqat ma'lum qatorlar uchun, hajmi kichik

Qoida: @> bilan JSONB qidiruvingiz bor va jadval katta bo'lsa — GIN index shart. EXPLAIN ANALYZE bilan tekshiring — Seq Scan ko'rsangiz, index qo'shing.


Keyingi dars

24-dars: ARRAY — yaratish, operatorlar, ANY, ALL, unnest