-- Crearea unui index GIN eficient folosind jsonb_path_ops
CREATE INDEX idx_products_attributes_fast ON products
USING GIN (attributes jsonb_path_ops);
-- Query care profită de indexul de mai sus
SELECT name, attributes->>'model'
FROM products
WHERE attributes @> '{"brand": "Sony", "type": "Camera"}';
-- Exemplu de index pe o cheie specifică dacă știi că doar asta cauți
CREATE INDEX idx_specific_attr ON products ((attributes->>'serial_number'));Hai să fim serioși, tentația e mare. Arunci totul într-un câmp de tip JSONB și gata, ai scăpat de migrații, de certuri cu echipa pe schema bazei de date și de stresul că 'mai trebuie o coloană'. Am văzut abordarea asta la un startup unde toată tabela de users era formată dintr-un ID și un mare blob JSONB. Rezultatul? Când au ajuns la 200k useri, orice query de raportare care nu era gândit perfect făcea serverul de DB să gâfâie la 90% CPU.
JSONB-ul nu e un substitut pentru designul corect de bază de date, ci o unealtă pentru datele care sunt 'schemaless' prin natura lor. Am avut un proiect pentru un magazin de electronice cu vreo 15k produse. Fiecare categorie avea atribute complet diferite: televizoarele aveau diagonală și refresh rate, frigiderele aveau volum și clasă energetică. Să faci 200 de coloane, majoritatea cu NULL, e o mizerie. Acolo am folosit JSONB pentru atribute și am salvat cam 30% din spațiul de stocare pe care l-ar fi ocupat o structură clasică de tip EAV (Entity-Attribute-Value), care e și mai greu de interogat.
Când să rămâi la coloane normale
Dacă ai date pe care faci calcule (SUM, AVG), filtrări constante sau care sunt prezente în 95% din rânduri, fă-le coloane native. Tipul de date integer sau boolean ocupă mult mai puțin spațiu și e infinit mai rapid de procesat. Am pățit să văd query-uri pe JSONB unde se făcea cast de la text la numeric în interiorul WHERE-ului; e o metodă sigură să omori performanța.
Trade-off-ul e simplu: JSONB îți oferă flexibilitate la scriere, dar te penalizează la citire și la validarea datelor. Nu ai constrângeri de tip NOT NULL sau FOREIGN KEY în interiorul unui JSON fără să te complici cu trigger-e sau check constraints urâte.
Indexarea GIN: Magia din spate
Dacă ai decis că JSONB e calea, nu poți lăsa query-urile să facă full table scan. Un index B-Tree normal nu te ajută aici decât dacă indexezi o expresie specifică (ex: data->>'brand'). Dacă vrei să cauți în tot obiectul, ai nevoie de GIN (Generalized Inverted Index).
La un proiect cu 1.2 milioane de loguri stocate în format JSONB, un query de căutare dura cam 4 secunde fără index. După un GIN index, a scăzut sub 50ms. Dar atenție la dimensiune: un index GIN poate deveni uriaș, uneori mai mare decât datele în sine.
Un pont pe care l-am învățat pe pielea mea: folosește jsonb_path_ops dacă ai nevoie doar de operatorul @> (contains). Indexul rezultat e mult mai compact și mai rapid la căutare decât cel default. La tabela mea de loguri, am redus dimensiunea indexului de la 800MB la vreo 450MB doar prin switch-ul ăsta.
Concluzia mea
JSONB e excelent pentru metadate, atribute dinamice sau integrări cu API-uri externe unde nu controlezi tu schema. Totuși, dacă ajungi să ai 10 indexuri GIN pe aceeași tabelă, probabil e timpul să te întorci la SQL-ul clasic și să normalizezi datele.
Voi în ce situații ați regretat că ați folosit JSONB în loc de coloane normale?