eduardweb.
PostgreSQLIntermediar#performance#postgresql#database#jsonb#indexing

Când merită să arunci JSONB în Postgres și cum îl faci rachetă cu indecși GIN

De Cristian Barbu, 5 aug. 2026 · 6 vizualizări · 3 like-uri

Postat 5 aug. 2026
sql
-- Crearea tabelului cu atribute dinamice
CREATE TABLE products (
    id BIGSERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    price NUMERIC(10, 2) NOT NULL,
    attributes JSONB NOT NULL DEFAULT '{}'::jsonb
);

-- Option A: Index GIN standard (acoperă toți operatorii @>, ?, ?&, ?|)
CREATE INDEX idx_products_attributes_gin ON products USING gin (attributes);

-- Option B: Index mai mic și mai rapid dacă folosești DOAR operatorul @>
CREATE INDEX idx_products_attributes_path ON products USING gin (attributes jsonb_path_ops);

-- Interogare care folosește indexul GIN instant
SELECT id, title, price
FROM products
WHERE attributes @> '{"brand": "Logitech", "wireless": true}';

Mulți developeri pe care i-am mentorat au tentația să transforme Postgres-ul într-un document store mascat imediat ce dau de o structură mai flexibilă. Am făcut și eu greșeala asta prin 2017, la o aplicație de e-commerce cu vreo 140k produse în catalog. Rezultatul? Baza gâfâia și consuma memorie aiurea.

JSONB e o funcționalitate genială, dar trebuie folosită cu cap, nu ca scuză să nu mai faci diagramă ER.

Când are sens JSONB și când e o capcană

Să fim clari: dacă ai câmpuri fixe precum email, created_at sau status, le pui în coloane dedicate. SQL-ul clasic le optimizează perfect, iar constrângerile de tip te salvează de bug-uri stupide.

JSONB câștigă detașat în trei cazuri clare:

  • Atribute dinamice sau de nișă: un laptop are ram_gb și processor, o pereche de adidași are marime_eu. Să faci 50 de coloane nullable sau un tabel EAV (Entity-Attribute-Value) e un cosmar de mentenanță.
  • Payload-uri de la webhook-uri externe: când salvezi răspunsul brut de la Stripe sau PayPal pentru audit.
  • Setări și preferințe de UI: structuri adânci care se citesc aproape mereu împreună.

Am văzut un proiect unde echipa pusese tot modelul de Order într-un singur câmp JSONB. Când a trebuit să scoatem rapoarte lunare pe vânzări, interogările au scăzut de la 20ms la peste 4 secunde. De ce? Pentru că la fiecare update pe un singur atribut din JSONB, engine-ul rescrie practic tot documentul pe disc (efectul MVCC).

Cum funcționează magia cu indexul GIN

JSONB nu e un simplu text (cum e tipul json). La inserare e parsat în format binar, se elimină spațiile și cheile duplicate. Scrierea e marginal mai lentă, dar citirea e extrem de rapidă.

Problema apare când ai 500k rânduri și cauți un produs cu o anumită proprietate. Fără un index, Postgres face Sequential Scan, adică citește fiecare rând de pe disc.

Aici intervine indexul GIN (Generalized Inverted Index). Spre deosebire de un B-Tree clasic care indexează valoarea întreagă a coloanei, GIN desface documentul JSONB în fiecare cheie și valoare individuală și le pune într-un index inversat.

Dacă folosești operatorul de incluziune @>, cu un index GIN configurat corect, timpii de răspuns scad dramatic. În cazul meu, pe o masă de date cu 300k înregistrări, am scăzut de la 420ms la sub 8ms.

Trade-off-uri sincere de care nu-ți spune nimeni

Nimic nu e gratuit în ingineria software. Înainte să pui GIN peste tot, ține cont de chestiile astea:

  1. Dimensiunea pe disc: Un index GIN pe date JSONB mari poate ajunge la 50-80% din mărimea tabelului în sine. Am avut un caz în care tabelul avea 3GB și indexul mai adăuga încă 2.2GB.
  2. Penalizare la scriere: La fiecare INSERT sau UPDATE, Postgres trebuie să spargă noul JSONB și să actualizeze arborele GIN. Dacă ai scrieri intense (peste câțiva sute de ops/sec), scrierea va deveni un bottleneck.
  3. Lipsa Foreign Keys: Nu poți pune constrângeri de integritate referențială pe o cheie aflată în interiorul unui JSONB. Validarea trebuie mutată 100% în aplicație.

Dacă ai doar 2-3 câmpuri fixe pe care le cauți des în JSONB, mai bine faci un index B-Tree pe o expresie dedicată decat un GIN pe tot documentul.

Voi cum folosiți JSONB în proiectele curente? L-ați lăsat doar pentru setări și integrări sau aveți entități principale stocate așa?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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