eduardweb.
PostgreSQLIntermediar#performance#postgresql#backend#database-design

Postgres JSONB: Când merită să renunți la coloane și cum îl indexezi corect cu GIN

De Ioana Marinescu, 15 iun. 2026 · 15 vizualizări · 2 like-uri

Postat 15 iun. 2026
sql
-- Creăm tabela pentru produse
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    attributes JSONB NOT NULL
);

-- Creăm un index GIN optimizat folosind jsonb_path_ops
CREATE INDEX idx_products_attributes ON products USING gin (attributes jsonb_path_ops);

-- Query-ul care va folosi indexul de mai sus
SELECT name 
FROM products 
WHERE attributes @> '{"color": "red", "size": "XL"}';

Am văzut des dezbaterea asta pe forumuri: punem totul în JSONB sau rămânem la tabele clasice? Am trecut prin ambele extreme în ultimii ani și am învățat pe pielea mea unde e limita. Azi vă arăt când merită să spargi tiparele relaționale și cum să nu pui baza în genunchi când ai de căutat în documentele alea.

Când trecem pe JSONB și când e o idee proastă

Am avut acum doi ani un proiect cu vreo 120.000 de produse, unde fiecare categorie avea atribute complet diferite. Televizoarele aveau diagonală și rezoluție, pantofii aveau mărime și culoare, iar parfumurile aveau note de vârf. Dacă mergeam pe clasicul model EAV (Entity-Attribute-Value) cu tabele de legătură, ajungeam la query-uri horror cu câte 8-10 join-uri. Am decis să punem atributele specifice într-o singură coloană de tip jsonb.

Merge excelent pentru date schemaless sau când integrezi API-uri externe unde structura se schimbă des și nu vrei să rulezi migrări de schemă în fiecare săptămână.

Dar există un trade-off major pe care mulți îl ignoră: pierzi constrângerile de integritate referențială la nivel de motor de bază de date. Nu poți pune un Foreign Key pe o valoare aflată în interiorul unui JSONB. Am pățit ca ID-uri de categorii să fie șterse din tabela principală, dar să rămână orfane în JSON-ul produselor. A durat o noapte întreagă să scriem scripturi de curățare a datelor corupte. Dacă datele tale au o structură clară și fixă, folosește coloane normale. Nu fi leneș.

Indexarea GIN: De la secunde la milisecunde

Implicit, dacă vrei să cauți un produs care are {"color": "red"} în coloana de JSONB, Postgres va face un Sequential Scan. Adică va citi absolut fiecare rând de pe disc ca să vadă ce e în interiorul acelui câmp. La 10.000 de rânduri nu se simte, dar la un volum mai mare începe să doară rău de tot.

Soluția este un index GIN (Generalized Inverted Index). Acesta știe să parseze structura JSONB și să creeze intrări în index pentru fiecare cheie și valoare internă.

Există două moduri mari de indexare GIN:

  1. jsonb_ops (cel implicit) - este flexibil, indexează chei, valori și sub-documente, dar ocupă mult spațiu pe disc.
  2. jsonb_path_ops - este mult mai mic și mai rapid, dar funcționează doar cu operatorul de incluziune @>.

Pe proiectul nostru, după ce am adăugat un index GIN pe atribute, timpul de răspuns al căutărilor pe filtre a scăzut de la 450ms la doar 12ms. Am economisit cam 30% la încărcarea CPU pe serverul de producție doar din optimizarea asta.

Cum arată în practică

Mai jos aveți exemplul clasic de definire a tabelului, crearea indexului optimizat și modul corect de a scrie query-ul pentru a activa indexul. Observați operatorul @> (conține), care este esențial pentru ca Postgres să folosească indexul jsonb_path_ops.

Folosiți JSONB când structura chiar este dinamică, dar nu îl transformați în groapa de gunoi a bazei de date.

Voi cum gestionați datele dinamice? Mergeți pe model hibrid în Postgres sau preferați baze de date document-store dedicate când schema devine prea fluidă?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.