TL; DR: JSONB vÔib oluliselt lihtsustada andmebaasiskeemi arendust, ilma et see kahjustaks pÀringute jÔudlust.
Sissejuhatus
Vaatame klassikalist nĂ€idet, mis on ilmselt ĂŒks vanimaid rakendusi rikkaandmestike maailmas: meil on entiteet, mille omadusi (atribuutide) tuleb salvestada. Kuid kĂ”ik eksemplarid ei pruugi omada samu omadusi ning tulevikus vĂ”ib tekkida vajadus lisada veel uusi omadusi.
Lihtsaim lahendus sellele probleemile on luua andmebaasi tabelis iga atribuudivÀÀrtuse jaoks veerg ja lihtsalt tÀita need veerud, mis on vajalikud konkreetse entiteedi eksemplari jaoks. SuurepÀrane! Probleem on lahendatud⊠kuni teie tabelis on miljoneid sissekandeid ja teil tekib vajadus lisada uus rida.
Vaatame EAV mustrit (), see, it occurs quite often. One table contains entities (records), another table contains property names (attributes), and a third table links entities with their attributes and contains the value of these attributes for the current entity. This allows you to have different sets of properties for different objects and also to add properties 'on the fly' without changing the database structure.
However, I wouldnât be writing this note if there werenât any drawbacks to the EVA approach. For instance, to retrieve one or several entities that have one attribute each, two joins are required in the query: the first being a join with the attributes table, and the second a join with the values table. If an entity has two attributes, then four joins are needed! Additionally, all attributes are usually stored as strings, which leads to type conversions both for the result and for the WHERE condition. If you write many queries, it can be quite wasteful in terms of resource usage.
Hoolimata nendest ilmsestest puudustest on EAV juba pikka aega kasutusel selliste probleemide lahendamiseks. Need olid vÀltimatud puudused ja paremat alternatiivi lihtsalt ei olnud.
Aga siis ilmus PostgreSQL-i uus "tehnoloogia"âŠ
Alates PostgreSQL 9.4 on lisatud JSONB andmetĂŒĂŒbi tugi JSON-i binaarsete andmete salvestamiseks. Kuigi JSON-i salvestamine selles formaadis vĂ”tab tavaliselt veidi rohkem ruumi ja aega kui lihtne tekstiline JSON, toimub selle töötlemine palju kiiremini. Samuti toetab JSONB indekseerimist, mis muudab pĂ€ringud veelgi kiiremaks.
JSONB andmetĂŒĂŒp vĂ”imaldab meil asendada mahuka EAV mustri, lisades meie entsiti tabelisse vaid ĂŒhe JSONB veeru, mis lihtsustab andmebaasi kavandamist oluliselt. Kuid paljud vĂ€idavad, et see peaks kaasnema jĂ”udluse langusega⊠Selle pĂ€rast see artikkel ilmuski.
Testiva andmebaasi seadistamine
Selleks vÔrdluseks lÔin ma andmebaasi uue PostgreSQL 9.5 installatsiooni peal 80-dollarise komplekti jaoks Ubuntu 14.04. PÀrast mÔnede seadete seadistamist postgresql.conf-is kÀivitasin ma psql-i abil integreerimine. EAV kujul andmete esitamiseks on loodud jÀrgmised tabelid:
Loo tabel entity (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT
);
Loo tabel entity_attribute (
id SERIAL PRIMARY KEY,
name TEXT
);
Loo tabel entity_attribute_value (
id SERIAL PRIMARY KEY,
entity_id INT REFERENTS entity(id),
entity_attribute_id INT REFERENTS entity_attribute(id),
value TEXT
);
Allpool on tabel, kus hoitakse samu andmeid, kuid atribuudid on JSONB tĂŒĂŒpi veerus â properties.
Loo tabel entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Tundub palju lihtsam, eks? Siis lisati tabelitesse (entity & entity_jsonb) 10 miljonit kirjet ja vastavalt tĂ€ideti sama andmetega tabel, kus kasutatakse EAV mustrit ja JSONB veeru lĂ€henemist â entity_jsonb.properties. Seega saime mitmeid erinevaid andmetĂŒĂŒpe kogu omaduste kogumi hulgas. NĂ€idetest:
{
id: 1
name: "Entity1"
description: "Test entity nr. 1"
properties: {
color: "punane"
length: 120
width: 3.1882420
hassomething: true
country: "Belgia"
}
}NĂŒĂŒd on meil samad andmed kahes variandis. Alustame rakenduste vĂ”rdlemisega!
Disaini lihtsustamine
Varasemalt on juba öeldud, et andmebaasi disain on mĂ€rkimisvÀÀrselt lihtsustatud: ĂŒks tabel, kasutades JSONB veergu omaduste jaoks, selle asemel et kasutada kolme tabelit EAV jaoks. Kuidas see aga pĂ€ringutes kajastub? Ăhe omaduse vĂ€rskendamine entiteedi kohta nĂ€eb vĂ€lja jĂ€rgmine:
-- 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;
Nagu nĂ€eme, ei tundu viimane pĂ€ring lihtsam. Selleks, et vĂ€rskendada omaduse vÀÀrtust JSONB objekti sees, peame kasutama funktsiooni , ja peame edastama meie uue vÀÀrtuse kui JSONB objekti. Siiski ei pea me eelnevalt teadma mingit ID-d. Vaatame EAV nĂ€idet, kus peame vĂ€rskendamiseks teadma nii entity_id kui ka entity_attribute_id. Kui soovite omadust JSONB veerus objekti nime pĂ”hjal vĂ€rskendada, siis toimub see kĂ”ik ĂŒhe lihtsa reaga.
NĂŒĂŒd valime entiteedi, mida just vĂ€rskendasime, tema uue vĂ€rvi alusel:
-- 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';
Ma arvan, et vĂ”ime nĂ”ustuda, et teine on lĂŒhem (ilma joinita!) ja seega paremini loetav. Siin vĂ”idab JSONB! Kasutame JSON operaatorit ->>, et saada vĂ€rv tekstivÀÀrtusena JSONB objektist. Samuti on olemas teine viis sama tulemuse saavutamiseks JSONB mudelis, kasutades operaatorit @>:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
See on veidi keerulisem: kontrollime, kas JSON objekt veerus properties sisaldab objekti, mis asub operaatori @> paremal kĂŒljel. VĂ€hem loetav, kuid suurem tootlikkus (vt allpool).
Teeme JSONB kasutamise veelgi lihtsamaks, kui peate valima mitu omadust korraga. Just siin sobib JSONB lĂ€henemine tĂ”eliselt: me lihtsalt valime omadused tĂ€iendavate veergudena meie tulemuste komplektis ilma vajaduseta ĂŒhendada:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
EAV kasutamisel on igas omaduses, mida soovite pÀrida, vaja 2 liitumist. Minu arvates nÀitavad eeltoodud pÀringud andmebaasi disainis suurt lihtsustamist. Vaadake rohkem nÀiteid, kuidas kirjutada pÀringuid JSONB-le, vÔib-olla ka postitusest.
NĂŒĂŒd on aeg rÀÀkida tootlikkusest.
Tootlikkus
Tootlikkuse vÔrdlemiseks kasutasin pÀringutes, et mÔÔta tÀitmise aega. Iga pÀringut kÀidi lÀbi vÀhemalt kolm korda, kuna esmakordselt kulub pÀringute planeerijale rohkem aega. Alustasin pÀringute tÀitmisega ilma igasuguste indeksiteta. Ilmselt andis see JSONB-le eelise, kuna EAV jaoks vajalikud liitumised ei saanud kasutada indekseid (vÀlistest vÔtmevÀljadest ei olnud indekseid). PÀrast seda lÔin 2 vÀlist vÔtmetabeli EAV vÀÀrtuste jaoks indeksi, samuti indeksi JSONB vÀlja jaoks.
Andmete vÀrskendamine nÀitas jÀrgmisi tulemusi aja osas (ms). Pange tÀhele, et skaala on logaritmiline:

NÀeme, et JSONB on mÀrgatavalt (> 50000 korda) kiirem kui EAV, kui indekseid ei kasutata, nagu eelnevalt mainitud. Kui indekseerisime veerge peamiste vÔtmete alusel, peaaegu kaob erinevus, kuid JSONB on siiski 1,3 korda kiirem kui EAV. Oluline on mÀrkida, et JSONB veeru indeks ei mÔjuta siin midagi, kuna me ei kasuta omaduste veergu hindekriteeriumites.
Omaduse vÀÀrtuse pÔhjal andmete valimiseks saame jÀrgmised tulemused (tavaline skaalamine):

On mÀrgata, et JSONB töötab jÀlle kiiremini kui EAV ilma indekseid, kuid kui EAV-l on indeksid, töötab see siiski kiiremini kui JSONB. Siiski mÀrkasin, et JSONB pÀringute aeg oli sama, mis tÔukaski mind oletama, et GIN-indeks ei tööta. Ilmselt, kui kasutate GIN-indeksit veerus, kus on omadused, kehtib see ainult kaudse operaatori @> kasutamise korral. Kasutasin seda uues testis, mis avaldas suure mÔju ajale: vaid 0,153 ms! See on 15000 korda kiirem kui EAV ja 25000 korda kiirem kui operaator ->>.
Arvan, et see oli piisavalt kiire!
Andmebaasi tabelite suurus
VÔrdleme tabelite suurusi mÔlema lÀhenemisviisi korral. Psql-is saame nÀidata kÔigi tabelite ja indeksite suurust kÀsuga dti+

EAV lĂ€henemise korral on tabelite suurus umbes 3068 MB ja indeksite suurus kuni 3427 MB, kokku 6,43 GB. JSONB lĂ€henemise puhul kasutatakse tabeli jaoks 1817 MB ja indeksite jaoks 318 MB, kokku 2,08 GB. See on kolm korda vĂ€hem! See fakt ĂŒllatas mind natuke, sest hoiame omaduste nimesid igas JSONB objekti.
Aga numbrid rÀÀgivad enda eest: EAV-s hoiame 2 tÀisarvulist vÀlisvÔtit atribuudi vÀÀrtusele, mille tulemusena saame 8 baiti lisanduvaid andmeid. Lisaks sellele on EAV-s kÔik omaduste vÀÀrtused tekstina, samas kui JSONB kasutab seal, kus vÔimalik, numbrilisi ja loogilisi vÀÀrtusi, mille tÔttu saadakse vÀiksem maht.
KokkuvÔte
KokkuvĂ”ttes arvan, et entsiti omaduste sĂ€ilitamine JSONB formaadis vĂ”ib teie andmebaasi projekteerimist ja haldamist oluliselt lihtsustada. Kui teete palju pĂ€ringuid, siis kĂ”ik, mis on salvestatud ĂŒhte tabelisse koos entiteediga, töötab tĂ”epoolest efektiivsemalt. Ja see, et see lihtsustab andmete vahelisi seoseid, on juba pluss, aga ka tulemusena saadud andmebaasi maht on kolm korda vĂ€iksem.
Samuti on tehtud testide pÔhjal vÔimalik jÀreldada, et jÔudluse kaod on vÀga vÀhesed. MÔnel juhul töötab JSONB isegi kiiremini kui EAV, mis teeb selle veelgi paremaks. Siiski ei kata see referentstest kindlasti kÔiki aspekte (nÀiteks entiteedid, millel on vÀga palju omadusi, olemasolevate andmete omaduste arvu mÀrkimisvÀÀrne suurenemine jne), seega, kui teil on mingeid ettepanekuid, kuidas neid parandada, Àrge kartke jÀtta kommentaare!
Allikas: habr.com
