-- Crearea indexului optimizat pe coloana JSONB folosind path_ops
CREATE INDEX idx_produse_atribute ON produse USING gin (atribute jsonb_path_ops);
-- Query rapid care folosește indexul (operatorul @> înseamnă "conține")
SELECT nume, pret
FROM produse
WHERE atribute @> '{"specificatii": {"culoare": "negru"}}';Am văzut prea des oameni care trec la Postgres și încep să arunce totul în JSONB doar pentru că e „flexibil”. Am făcut și eu prostia asta acum vreo 6 ani la un proiect și am regretat rapid. JSONB are un loc bine definit, dar nu e un înlocuitor pentru o schemă corect relațională.
Când merită să folosești JSONB?
Regula mea de aur e simplă: folosește JSONB doar când structura datelor tale este dictată de factori externi pe care nu-i poți controla sau când ai atribute extrem de dinamice.
Am avut un proiect cu vreo 12.000 de produse active în magazin. Fiecare categorie avea atribute complet diferite: hainele aveau mărimi și culori, laptopurile aveau RAM și procesor, iar loțiunile de plajă aveau factor de protecție. Dacă mergeam pe clasicul EAV (Entity-Attribute-Value), aveam nevoie de join-uri complexe care puneau baza de date în cap la query-uri mai grele.
Aruncând aceste specificații într-o coloană atribute de tip JSONB, am simplificat masiv codul și am salvat timp de dezvoltare. A mers brici pentru că datele erau doar de citit și afișat, fără validări complexe de integritate.
Trade-off-ul sincer: unde te frigi cu JSONB
Nu există magie gratis. Înainte să pui jsonb peste tot, ține cont de problemele astea:
- Spațiu pe disc: JSONB stochează cheile pentru fiecare rând în parte. Dacă ai un milion de rânduri și cheia se numește
"specificatie_tehnica_detaliata", o să stochezi stringul ăsta de un milion de ori. Coloanele normale stochează metadatele o singură dată în definirea tabelei. - Update-uri scumpe: Postgres folosește MVCC. Când faci update la o singură valoare dintr-un document JSONB mare, Postgres scrie o copie nouă a întregului rând pe disc. Dacă ai JSON-uri mari și update-uri dese, scrii pe disc de te doare capul.
- Lipsa constrângerilor: N-ai foreign keys în interiorul JSON-ului. Dacă ai un ID de user acolo și userul e șters, rămâi cu date orfane.
Indexarea GIN: Cum evităm Sequential Scan
Dacă ai trecut de 100.000 de rânduri și cauți în JSONB fără index, baza de date va face un scan complet pe tabelă. Pentru query-uri rapide, ai nevoie de un index GIN (Generalized Inverted Index).
Sunt două moduri mari în care poți defini un index GIN pe JSONB:
jsonb_ops(default): Permite query-uri flexibile. Poți căuta după chei, după valori sau după existența unei chei. E destul de mare ca dimensiune.jsonb_path_ops: E mult mai optimizat. Indexul ocupă cu vreo 30-50% mai puțin spațiu pe disc și e mai rapid la căutări de tip cheie-valoare, dar nu te lasă să verifici dacă o cheie există independent de valoarea ei.
La proiectul menționat mai sus, trecerea de la jsonb_ops la jsonb_path_ops pe coloana de atribute ne-a redus dimensiunea indexului de la 4.2 GB la 2.5 GB și a scăzut timpul de răspuns la căutări sub 5ms.
Voi cum gestionați datele dinamice în proiecte? Ați mers pe JSONB sau ați rămas fani ai tabelelor pivot clasice?