eduardweb.
PostgreSQLIntermediar#performance#postgresql#database#jsonb

Postgres JSONB în producție: Când merită și cum îl accelerezi cu index GIN

De Bogdan Răducanu, 23 iul. 2026 · 7 vizualizări · 3 like-uri

Postat 23 iul. 2026
sql
-- Crearea tabelului cu un câmp flexibil de specificații
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    price NUMERIC(10, 2) NOT NULL, -- coloană normală (filtrăm/sortăm des)
    specs JSONB NOT NULL DEFAULT '{}'::jsonb
);

-- Opțiunea 1: Index GIN optimizat doar pentru căutări de tip containment (@>)
-- Este mai mic pe disc și mai rapid decât GIN-ul default
CREATE INDEX idx_products_specs_fast ON products USING gin (specs jsonb_path_ops);

-- Inserare exemplu de date imbricate
INSERT INTO products (title, price, specs)
VALUES ('Laptop Pro 15', 4500.00, '{"brand": "Dell", "hardware": {"ram": "16GB", "cpu": "i7"}}');

-- Query rapid care folosește indexul GIN cu operatorul @>
SELECT title, price 
FROM products 
WHERE specs @> '{"hardware": {"ram": "16GB"}}';

Am văzut de-a lungul timpului două tabere de devi: cei care aruncă totul într-o singură coloană JSONB ca să scape de migrații și cei care refuză pur și simplu să folosească date nestructurate într-o bază relațională. Adevărul, ca de obicei, e la mijloc. În 10 ani de backend am învățat că JSONB e o unealtă genială în Postgres, dar doar dacă înțelegi exact ce pierzi și ce câștigi când o folosești.

Când e ok JSONB și când îți furi singur căciula

Am avut cazul unui e-commerce cu vreo 25k de produse. Fiecare categorie avea specificații tehnice complet diferite: la laptopuri aveai RAM și procesor, la haine aveai mărime și material, iar la anvelope aveai diametru și indice de viteză. Dacă încercam să fac asta cu tabel clasic de atribute (modelul clasic EAV - Entity Attribute Value), ajungeam la query-uri de mărimea unui roman și JOIN-uri care îmi îngenuncheau baza de date.

Acolo JSONB a strălucit. Am pus un câmp attributes de tip JSONB și gata.

Când NU e ok să-l folosești? Pentru date pe care le filtrezi, sortezi sau le folosești în JOIN-uri în mod curent. Am văzut status sau user_id băgate în JSONB. Nu face asta. Pierzi tipizarea strictă, pierzi Foreign Keys reale, iar Postgres trebuie să muncească mult mai mult la scanare. Un alt trade-off sincer: scrierea e o idee mai scumpă, iar discul se umple mai repede din cauza metadatelor salvate de Postgres pentru structura JSON-ului.

Cum pui index GIN fără să îți distrugi discul

Dacă dai un query simplu cu WHERE attributes->>'ram' = '16GB', Postgres va face un Seq Scan pe tot tabelul. La 1.000 de rânduri nu simți, dar la un tabel cu jumătate de milion de înregistrări, procesorul o ia la fugă.

Pentru asta există indecșii GIN (Generalized Inverted Index). Ei stochează fiecare cheie și valoare din JSONB într-o structură separată de căutare.

Implicit, dacă creezi un index USING gin(attributes), Postgres indexează absolut tot din acel JSON. La un payload mare, indexul poate ajunge mai mare decât tabelul în sine! Aici intervine trucul: folosește operatorul de clasă jsonb_path_ops. Acesta face un hash doar pe combinația cheie-valoare, rezultând un index cu 50-60% mai mic și considerabil mai rapid la interogări cu operatorul @>.

La un proiect de analiză de date, prin trecerea la jsonb_path_ops am scăzut timpul unui query de filtrare pe date imbricate de la 480ms la 4ms.

Capcana de care lovesc toți în producție

Trebuie să ții minte un lucru critic: Postgres rescrie întregul rând de fiecare dată când faci UPDATE pe un câmp din JSONB. Dacă ai un document JSON de 50KB și tu actualizezi doar o bifă de boolean din el de 10 ori pe secundă, vei genera tone de "dead tuples" și îți vei îneca baza de date în IOPS din cauza procesului de VACUUM.

Regula mea e simplă: dacă câmpul se schimbă des, îl scot în coloană separată. Dacă e citit des și structura e flexibilă, rămâne în JSONB.

Voi unde ați lovit pragul de sus cu JSONB în aplicațiile voastre?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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