-- Crearea unui index GIN standard (flexibil dar mai mare)
CREATE INDEX idx_produse_atribute ON produse USING GIN (atribute);
-- Crearea unui index optimizat pentru interogări de tip 'contains'
CREATE INDEX idx_produse_atribute_fast ON produse USING GIN (atribute jsonb_path_ops);
-- Query care folosește eficient indexul
SELECT nume
FROM produse
WHERE atribute @> '{"culoare": "albastru", "stoc": true}';Am avut acum vreo trei ani un proiect de e-commerce unde clientul voia atribute diferite pentru fiecare categorie de produs. Știi tu, la televizoare ai diagonală, la haine ai material, la SSD-uri ai viteză de scriere. Prima reacție a fost să facem tabele de tip Entity-Attribute-Value (EAV), dar cine a lucrat cu EAV știe că e iadul pe pământ când vine vorba de join-uri și rapoarte. Așa că am zis: „Hai cu JSONB, că Postgres e deștept”.
Problema e că mulți developeri folosesc JSONB ca pe o groapă de gunoi. Aruncă tot acolo pentru că e lene să scrie o migrare sau să gândească o schemă corectă. Am pățit-o și eu. Pe la 500.000 de înregistrări, interogările au început să scârțâie serios. Am învățat pe calea grea că JSONB nu e un înlocuitor pentru normalizare, ci un tool pentru date semi-structurate.
Când alegi JSONB și când rămâi pe coloane clasice
Regula mea e simplă acum: dacă ai date pe care le folosești în 90% din query-uri pentru filtrare sau join-uri (cum ar fi un user_id, un status sau un created_at), pune-le în coloane normale. Postgres e optimizat masiv pentru tipuri de date fixe. Un integer ocupă fix 4 octeți, pe când în JSONB, același număr vine la pachet cu cheia lui (string) și overhead-ul de structură.
JSONB strălucește când ai date care se schimbă des sau care vin de la integrări externe unde nu controlezi tu schema. De exemplu, răspunsurile de la un API de plăți sau setările de UI ale unui user. Am economisit cam 30% din timpul de development la partea de integrare cu furnizorii de marfă doar pentru că nu a mai trebuit să modific schema bazei de date de fiecare dată când un furnizor adăuga un câmp nou în XML-ul lor (transformat în JSON, evident).
Indexarea GIN – Salvarea performanței
Dacă faci un SELECT după o cheie dintr-un JSONB fără index, Postgres va face un full table scan. La un proiect cu 1 milion de rânduri, asta înseamnă secunde bune de așteptare. Aici intră în scenă indexul GIN (Generalized Inverted Index).
Există două moduri mari de a-l folosi. Cel default indexează toate cheile și valorile, ceea ce e super flexibil, dar indexul poate ajunge să fie uriaș pe disc. Dacă știi exact ce cauți, poți folosi jsonb_path_ops, care e mult mai mic și mai rapid, dar suportă doar operatorul @>.
Trade-off-ul sincer? Un index GIN pe o coloană JSONB grasă poate să dubleze timpul de INSERT sau UPDATE. Am avut un tabel unde scrierile durau cu 40% mai mult după ce am adăugat indexul, pentru că Postgres trebuia să desfacă tot JSON-ul și să actualizeze intrările în index pentru fiecare cheie găsită. Nu e magie, e matematică.
Cum interoghezi eficient
Folosește operatorul @> (contains) ori de câte ori poți. E cel mai bine optimizat pentru indexul GIN. Dacă începi să faci cast-uri la text în interiorul query-ului ca să folosești LIKE, ai pierdut startul. Postgres nu va folosi indexul GIN pentru așa ceva și te vei trezi cu un query plan care arată horror.
În concluzie, JSONB e excelent pentru flexibilitate, dar te costă spațiu pe disc și CPU la scriere. Dacă ai date rigide, rămâi pe coloane clasice. Voi cum ați decis unde trageți linia între structurat și semi-structurat?