-- 1. Creează indexul optimizat pentru căutări de conținere
CREATE INDEX idx_products_attributes_path
ON products USING gin (attributes jsonb_path_ops);
-- 2. Query greșit (NU folosește indexul GIN de mai sus, face Seq Scan):
-- SELECT * FROM products WHERE attributes->>'color' = 'red';
-- 3. Query corect și rapid (utilizează jsonb_path_ops și scade la sub 10ms):
SELECT id, name, attributes
FROM products
WHERE attributes @> '{"color": "red", "brand": "Logitech"}';Am văzut prea multe schemuri de Postgres distruse doar pentru că echipa n-a avut chef să scrie o migrare și a trântit o coloană data jsonb. JSONB e un instrument excelent, dar dacă îl folosești ca pe un substitut pentru un model relațional bine gândit, o să ajungi rapid la probleme grave de performanță.
M-am lovit de asta direct acum doi ani, la un magazin online cu peste 120.000 de produse unde specificațiile tehnice erau complet haotice: televizoarele aveau diagonală și rezoluție, hainele aveau mărime și material, iar anvelopele aveau indici de viteză.
Când are sens JSONB și când e o capcană
Regula mea e simplă: dacă datele au o structură dinamică, necunoscută în faza de design sau specifică doar pentru 1-2% din rânduri, JSONB e rege. Atribute dinamice de produs, payload-uri de webhook-uri externe, sau setări per utilizator care se schimbă des ca structură.
Dar dacă ai un câmp pe care îl folosești în JOIN-uri, pe care ai FK-uri, sau peste care faci WHERE la fiecare request principal, mută-l în coloană clasică. Coloanele normale consumă mai puțin spațiu, au statistici mult mai bune în query planner și permit constrângeri reale la nivel de DB.
Operatorii @> și ->> și de ce contează pentru indecși
Dacă vrei să cauți eficient în JSONB, trebuie să înțelegi cum procesează Postgres operațiile. Cel mai des văd cod de genul:
SELECT * FROM products WHERE attributes->>'color' = 'red';
Un coleg din echipă se plângea că un query similar dura 450ms pe 100k de rânduri. De ce? Pentru că ->> extrage valoarea ca text, iar un index GIN standard pe coloană nu va fi folosit pentru așa ceva fără o expresie dedicată.
Săbiuța de aur pentru JSONB este operatorul de conținere @>. Acesta verifică dacă documentul JSON din stânga include structura din dreapta.
Indexarea GIN – secretul jsonb_path_ops
Dacă pui un index GIN simplu (CREATE INDEX ON products USING gin (attributes)), Postgres va indexa fiecare cheie și valoare din document. E util dacă faci interogări complexe pe chei necunoscute, dar indexul devine uriaș.
În 90% din cazuri, vrei doar să cauți valori pentru chei cunoscute. Aici intervenim cu opțiunea jsonb_path_ops.
Optimizarea asta ne-a coborât timpul de execuție de la 450ms la doar 8ms. Indexul jsonb_path_ops folosește hash-uri pentru căile complete, ocupă aproape jumătate din spațiul unui GIN standard și e sesizabil mai rapid la citire.
Trade-off-ul dureros: Write Amplification
Toată lumea laudă JSONB pentru flexibilitate, dar puțini vorbesc despre costul la scriere. În Postgres, un document JSONB e imutabil. Dacă ai un JSONB de 30KB și faci un UPDATE să modifici un singur boolean din el, Postgres nu schimbă acel bit – scrie un rând complet nou cu toți cei 30KB.
La proiectul de e-commerce de care ziceam, aveam un job care făcea update la stocuri într-un JSONB o dată la 5 minute. Ne-a umflat baza de date cu bloat masiv și I/O-ul pe disk era constant peste 90%. Am mutat stocul într-o coloană INTEGER separată și am lăsat în JSONB doar atributele statice.
Voi cum folosiți JSONB în producție? Îl țineți doar pentru metadata strictă sau aveți baze întregi schemaless?