TL; DR: JSONB môže výrazne zjednodušiť vývoj databázových schém bez obetovania výkonu dotazov.
Úvod
Uveďme klasický príklad pravdepodobne jedného z najstarších prípadov použitia vo svete relačnej databázy (databázy): máme entitu a potrebujeme uložiť určité vlastnosti (atribúty) tejto entity. Nie všetky inštancie však môžu mať rovnakú množinu vlastností a v budúcnosti môžu byť pridané ďalšie vlastnosti.
Najjednoduchší spôsob, ako vyriešiť tento problém, je vytvoriť stĺpec v tabuľke databázy pre každú hodnotu vlastnosti a jednoducho vyplniť tie, ktoré sú potrebné pre konkrétnu inštanciu entity. Skvelé! Problém vyriešený... kým vaša tabuľka nebude obsahovať milióny záznamov a nebudete musieť pridať nový záznam.
Zvážte vzor EAV (), vyskytuje sa pomerne často. Jedna tabuľka obsahuje entity (záznamy), ďalšia tabuľka obsahuje názvy vlastností (atribúty) a tretia tabuľka priraďuje entity k ich atribútom a obsahuje hodnotu týchto atribútov pre aktuálnu entitu. To vám dáva možnosť mať rôzne sady vlastností pre rôzne objekty a tiež pridávať vlastnosti za behu bez zmeny štruktúry databázy.
Nepísal by som však tento príspevok, ak by prístup EVA nemal nejaké nevýhody. Napríklad na získanie jednej alebo viacerých entít, z ktorých každá má 1 atribút, sú v dotaze potrebné 2 spojenia: prvé je spojenie s tabuľkou atribútov, druhé je spojenie s tabuľkou hodnôt. Ak má entita 2 atribúty, potom sú potrebné 4 spojenia! Okrem toho sú všetky atribúty zvyčajne uložené ako reťazce, čo vedie k pretypovaniu typu pre výsledok aj pre klauzulu WHERE. Ak píšete veľa otázok, potom je to z hľadiska využívania zdrojov dosť zbytočné.
Napriek týmto zjavným nedostatkom sa EAV už dlho používa na riešenie týchto typov problémov. Boli to nevyhnutné nedostatky a jednoducho neexistovala lepšia alternatíva.
Potom sa však v PostgreSQL objavila nová „technológia“...
Počnúc PostgreSQL 9.4 bol pridaný typ údajov JSONB na ukladanie binárnych údajov JSON. Aj keď ukladanie JSON v tomto formáte zvyčajne zaberie o niečo viac miesta a času ako obyčajný textový JSON, vykonávanie operácií na ňom je oveľa rýchlejšie. JSONB podporuje aj indexovanie, vďaka čomu sú dotazy ešte rýchlejšie.
Dátový typ JSONB nám umožňuje nahradiť ťažkopádny vzor EAV pridaním iba jedného stĺpca JSONB do našej tabuľky entít, čo výrazne zjednodušuje návrh databázy. Mnohí ale tvrdia, že by to malo byť sprevádzané poklesom produktivity... Preto som napísal tento článok.
Nastavenie testovacej databázy
Pre toto porovnanie som vytvoril databázu na čerstvej inštalácii PostgreSQL 9.5 na zostave za 80 dolárov Ubuntu 14.04 Po konfigurácii niektorých parametrov v postgresql.conf som spustil skript pomocou psql. Nasledujúce tabuľky boli vytvorené na prezentáciu údajov vo forme EAV:
CREATE TABLE entity (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT
);
CREATE TABLE entity_attribute (
id SERIAL PRIMARY KEY,
name TEXT
);
CREATE TABLE entity_attribute_value (
id SERIAL PRIMARY KEY,
entity_id INT REFERENCES entity(id),
entity_attribute_id INT REFERENCES entity_attribute(id),
value TEXT
);
Nižšie je tabuľka, kde budú uložené rovnaké údaje, ale s atribútmi v stĺpci typu JSONB – vlastnosti.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Vyzerá to oveľa jednoduchšie, však? Potom bol pridaný do tabuliek entít (bytosť & entity_jsonb) 10 miliónov záznamov a podľa toho bola tabuľka naplnená rovnakými údajmi pomocou vzoru EAV a prístupu so stĺpcom JSONB - entity_jsonb.properties. V rámci celého súboru vlastností sme teda dostali niekoľko rôznych typov údajov. Príklad údajov:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}Takže teraz máme rovnaké údaje pre obe možnosti. Začnime porovnávať implementácie v práci!
Zjednodušte svoj dizajn
Predtým bolo uvedené, že návrh databázy bol výrazne zjednodušený: jedna tabuľka s použitím stĺpca JSONB pre vlastnosti namiesto použitia troch tabuliek pre EAV. Ako sa to však odráža v žiadostiach? Aktualizácia jednej vlastnosti entity vyzerá takto:
-- EAV
UPDATE entity_attribute_value
SET value = 'blue'
WHERE entity_attribute_id = 1
AND entity_id = 120;
-- JSONB
UPDATE entity_jsonb
SET properties = jsonb_set(properties, '{"color"}', '"blue"')
WHERE id = 120;
Ako vidíte, posledná požiadavka nevyzerá jednoduchšie. Na aktualizáciu hodnoty vlastnosti v objekte JSONB musíme použiť funkciu a mali by odovzdať našu novú hodnotu ako objekt JSONB. Nepotrebujeme však vopred poznať žiadny identifikátor. Ak sa pozrieme na príklad EAV, na vykonanie aktualizácie potrebujeme poznať entity_id aj entity_attribute_id. Ak chcete aktualizovať vlastnosť v stĺpci JSONB na základe názvu objektu, všetko sa to deje v jednom jednoduchom riadku.
Teraz vyberme entitu, ktorú sme práve aktualizovali, na základe jej novej farby:
-- EAV
SELECT e.name
FROM entity e
INNER JOIN entity_attribute_value eav ON e.id = eav.entity_id
INNER JOIN entity_attribute ea ON eav.entity_attribute_id = ea.id
WHERE ea.name = 'color' AND eav.value = 'blue';
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties ->> 'color' = 'blue';
Myslím, že sa zhodneme na tom, že druhý je kratší (no join!), a teda aj čitateľnejší. JSONB tu vyhráva! Pomocou operátora JSON ->> získame farbu ako textovú hodnotu z objektu JSONB. Existuje aj druhý spôsob, ako dosiahnuť rovnaký výsledok v modeli JSONB pomocou operátora @>:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Toto je trochu komplikovanejšie: skontrolujeme, či objekt JSON v stĺpci vlastností obsahuje objekt, ktorý je napravo od operátora @>. Menej čitateľné, produktívnejšie (pozri nižšie).
Urobme si používanie JSONB ešte jednoduchšie, keď potrebujete vybrať viacero vlastností naraz. Tu skutočne prichádza prístup JSONB: jednoducho vyberieme vlastnosti ako ďalšie stĺpce v našej množine výsledkov bez potreby spojení:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
S EAV budete potrebovať 2 spojenia pre každú vlastnosť, ktorú chcete dotazovať. Podľa môjho názoru vyššie uvedené dotazy ukazujú veľké zjednodušenie návrhu databázy. Pozrite si ďalšie príklady, ako písať dopyty JSONB, aj v príspevok.
Teraz je čas hovoriť o výkone.
produktivita
Na porovnanie výkonu som použil v dopytoch na výpočet času vykonania. Každý dotaz bol vykonaný aspoň trikrát, pretože plánovač dotazov prvýkrát trvá dlhšie. Najprv som spustil dotazy bez akýchkoľvek indexov. Je zrejmé, že to bola výhoda JSONB, pretože spojenia požadované pre EAV nemohli používať indexy (polia cudzích kľúčov neboli indexované). Potom som vytvoril index na 2 stĺpcoch cudzieho kľúča tabuľky hodnôt EAV, ako aj index pre stĺpec JSONB.
Aktualizácia údajov ukázala nasledujúce výsledky z hľadiska času (v ms). Všimnite si, že mierka je logaritmická:

Vidíme, že JSONB je oveľa (> 50000-x) rýchlejší ako EAV, ak nepoužívate indexy z vyššie uvedeného dôvodu. Keď indexujeme stĺpce primárnymi kľúčmi, rozdiel takmer zmizne, ale JSONB je stále 1,3-krát rýchlejší ako EAV. Všimnite si, že index v stĺpci JSONB tu nemá žiadny vplyv, pretože v kritériách hodnotenia nepoužívame stĺpec vlastnosti.
Pri výbere údajov na základe hodnoty vlastnosti získame nasledujúce výsledky (normálna mierka):

Môžete si všimnúť, že JSONB opäť funguje rýchlejšie ako EAV bez indexov, ale keď EAV s indexmi, stále funguje rýchlejšie ako JSONB. Potom som však videl, že časy pre dopyty JSONB boli rovnaké, čo ma priviedlo k tomu, že indexy GIN nefungujú. Keď použijete index GIN na stĺpec s vyplnenými vlastnosťami, prejaví sa iba pri použití operátora zahrnutia @>. Použil som to v novom teste a malo to obrovský vplyv na čas: iba 0,153 ms! To je 15000 25000-krát rýchlejšie ako EAV a XNUMX XNUMX-krát rýchlejšie ako operátor ->>.
Myslím, že to bolo dosť rýchle!
Veľkosť tabuľky databázy
Porovnajme veľkosti tabuliek pre oba prístupy. V psql môžeme pomocou príkazu zobraziť veľkosť všetkých tabuliek a indexov dti+

V prípade prístupu EAV sú veľkosti tabuliek približne 3068 MB a indexy až 3427 MB, spolu 6,43 GB. Prístup JSONB používa 1817 MB pre tabuľku a 318 MB pre indexy, čo je 2,08 GB. Ukazuje sa to 3 krát menej! Táto skutočnosť ma trochu prekvapila, pretože názvy vlastností ukladáme do každého objektu JSONB.
Čísla však hovoria samy za seba: v EAV ukladáme 2 celočíselné cudzie kľúče na hodnotu atribútu, výsledkom čoho je 8 bajtov dodatočných údajov. Okrem toho EAV ukladá všetky hodnoty vlastností ako text, zatiaľ čo JSONB bude interne používať číselné a boolovské hodnoty, ak je to možné, čo vedie k menšej stope.
Výsledky
Celkovo si myslím, že ukladanie vlastností entity vo formáte JSONB môže výrazne zjednodušiť návrh a údržbu vašej databázy. Ak máte spustených veľa dotazov, ponechanie všetkého v rovnakej tabuľke ako entita bude skutočne fungovať efektívnejšie. A to, že to zjednodušuje interakciu medzi dátami je už plus, no výsledná databáza je objemovo 3x menšia.
Aj na základe vykonaných testov môžeme konštatovať, že straty výkonu sú veľmi nevýznamné. V niektorých prípadoch je JSONB dokonca rýchlejší ako EAV, vďaka čomu je ešte lepší. Tento benchmark však samozrejme nepokrýva všetky aspekty (napr. entity s veľmi veľkým počtom vlastností, výrazný nárast počtu vlastností existujúcich údajov,...), takže ak máte nejaké návrhy, ako ich zlepšiť , neváhajte a zanechajte v komentároch!
Zdroj: hab.com
