EAV pakeitimas JSONB „PostgreSQL“.

TL; DR: JSONB gali labai supaprastinti duomenų bazės schemos kūrimą neprarandant užklausos našumo.

įvedimas

Pateikiame klasikinį bene vieno seniausių reliacinės duomenų bazės (duomenų bazės) naudojimo atvejų pasaulyje pavyzdį: mes turime objektą, ir mums reikia išsaugoti tam tikras šio objekto savybes (atributus). Tačiau ne visi atvejai gali turėti tą patį ypatybių rinkinį ir ateityje gali būti pridėta daugiau ypatybių.

Paprasčiausias būdas išspręsti šią problemą yra sukurti stulpelį duomenų bazės lentelėje kiekvienai nuosavybės vertei ir tiesiog užpildyti tuos, kurie reikalingi konkrečiam objekto egzemplioriui. Puiku! Problema išspręsta... kol jūsų lentelėje bus milijonai įrašų ir turėsite pridėti naują įrašą.

Apsvarstykite EAV modelį (Esybė-atributas-vertė), tai pasitaiko gana dažnai. Vienoje lentelėje yra objektai (įrašai), kitoje – ypatybių pavadinimai (atributai), o trečioje – objektai susiejami su jų atributais ir yra tų atributų reikšmė dabartiniam objektui. Tai suteikia galimybę turėti skirtingus ypatybių rinkinius skirtingiems objektams, taip pat pridėti ypatybes, nekeičiant duomenų bazės struktūros.

Tačiau nerašyčiau šio įrašo, jei nebūtų EVA metodo minusų. Taigi, pavyzdžiui, norint gauti vieną ar daugiau objektų, turinčių po 1 atributą, užklausoje reikia 2 sujungimų: pirmasis yra sujungimas su atributų lentele, antrasis yra sujungimas su reikšmių lentele. Jei subjektas turi 2 atributus, reikia 4 sujungimų! Be to, visi atributai paprastai išsaugomi kaip eilutės, todėl rezultato ir WHERE sąlygos tipas perduodamas. Jei rašote daug užklausų, tai yra gana švaistoma išteklių naudojimo požiūriu.

Nepaisant šių akivaizdžių trūkumų, EAV jau seniai buvo naudojamas tokio tipo problemoms spręsti. Tai buvo neišvengiami trūkumai, o geresnės alternatyvos tiesiog nebuvo.
Bet tada PostgreSQL pasirodė nauja „technologija“...

Pradedant nuo PostgreSQL 9.4, JSONB duomenų tipas buvo pridėtas JSON dvejetainiams duomenims saugoti. Nors JSON saugojimas šiuo formatu paprastai užima šiek tiek daugiau vietos ir laiko nei paprasto teksto JSON, operacijos su juo atliekamos daug greičiau. JSONB taip pat palaiko indeksavimą, todėl užklausos tampa dar greitesnės.

JSONB duomenų tipas leidžia pakeisti sudėtingą EAV šabloną, į objektų lentelę įtraukiant tik vieną JSONB stulpelį, o tai labai supaprastina duomenų bazės dizainą. Tačiau daugelis ginčijasi, kad tai turėtų lydėti produktyvumo mažėjimas... Dėl to ir parašiau šį straipsnį.

Bandymų duomenų bazės nustatymas

Šiam palyginimui sukūriau duomenų bazę naujai įdiegtoje „PostgreSQL 9.5“ 80 USD versijoje DigitalOcean Ubuntu 14.04 Sukonfigūravus kai kuriuos parametrus faile postgresql.conf, paleidau tai scenarijus naudojant psql. Duomenims pateikti EAV forma buvo sukurtos šios lentelės:

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
);

Žemiau yra lentelė, kurioje bus saugomi tie patys duomenys, bet su atributais JSONB tipo stulpelyje – savybės.

CREATE TABLE entity_jsonb (
  id          SERIAL PRIMARY KEY, 
  name        TEXT, 
  description TEXT,
  properties  JSONB
);

Atrodo daug paprasčiau, ar ne? Tada jis buvo įtrauktas į objektų lenteles (subjektas & entity_jsonb) 10 milijonų įrašų, todėl lentelė buvo užpildyta tais pačiais duomenimis, naudojant EAV šabloną ir metodą su JSONB stulpeliu - entity_jsonb.properties. Taigi iš viso ypatybių rinkinio gavome kelis skirtingus duomenų tipus. Duomenų pavyzdžiai:

{
  id:          1
  name:        "Entity1"
  description: "Test entity no. 1"
  properties:  {
    color:        "red"
    lenght:       120
    width:        3.1882420
    hassomething: true
    country:      "Belgium"
  } 
}

Taigi dabar turime tuos pačius abiejų variantų duomenis. Pradėkime lyginti diegimus darbe!

Supaprastinkite savo dizainą

Anksčiau buvo teigiama, kad duomenų bazės dizainas buvo labai supaprastintas: viena lentelė, ypatybėms naudojant JSONB stulpelį, o ne trys lentelės EAV. Tačiau kaip tai atsispindi prašymuose? Vienos objekto nuosavybės atnaujinimas atrodo taip:

-- 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;

Kaip matote, paskutinis prašymas neatrodo paprastesnis. Norėdami atnaujinti JSONB objekto nuosavybės vertę, turime naudoti funkciją jsonb_set(), ir turėtų perduoti mūsų naują vertę kaip JSONB objektą. Tačiau mums nereikia iš anksto žinoti jokio identifikatoriaus. Žvelgiant į EAV pavyzdį, kad galėtume atnaujinti, turime žinoti ir entity_id, ir entity_attribute_id. Jei norite atnaujinti ypatybę JSONB stulpelyje pagal objekto pavadinimą, visa tai atliekama vienoje paprastoje eilutėje.

Dabar pasirinkite objektą, kurį ką tik atnaujinome pagal jo naują spalvą:

-- 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';

Manau, kad galime sutikti, kad antrasis yra trumpesnis (join!), todėl skaitomesnis. JSONB čia laimi! Naudojame JSON ->> operatorių, kad gautume spalvą kaip teksto reikšmę iš JSONB objekto. Taip pat yra antras būdas pasiekti tą patį rezultatą JSONB modelyje naudojant @> operatorių:

-- JSONB 
SELECT name 
FROM entity_jsonb 
WHERE properties @> '{"color": "blue"}';

Tai šiek tiek sudėtingiau: patikriname, ar JSON objekte jo savybių stulpelyje yra objektas, esantis dešinėje nuo @> operatoriaus. Mažiau skaitomas, produktyvesnis (žr. toliau).

Padėkime naudoti JSONB dar lengviau, kai vienu metu reikia pasirinkti kelias ypatybes. Čia iš tikrųjų atsiranda JSONB metodas: tiesiog pasirenkame ypatybes kaip papildomus stulpelius rezultatų rinkinyje, nereikalaujant sujungimų:

-- JSONB 
SELECT name
  , properties ->> 'color'
  , properties ->> 'country'
FROM entity_jsonb 
WHERE id = 120;

Su EAV jums reikės 2 sujungimų kiekvienai nuosavybei, dėl kurios norite pateikti užklausą. Mano nuomone, aukščiau pateiktos užklausos rodo didelį duomenų bazės dizaino supaprastinimą. Žr. daugiau pavyzdžių, kaip rašyti JSONB užklausas, taip pat tai paštu.
Dabar atėjo laikas pakalbėti apie pasirodymą.

Našumas

Kad palyginčiau našumą, aš naudoju PAAIŠKINTI ANALIZUOTI užklausose, skaičiuojant vykdymo laiką. Kiekviena užklausa buvo vykdoma mažiausiai tris kartus, nes pirmą kartą užklausų planavimo priemonė užtrunka ilgiau. Pirmiausia paleidau užklausas be jokių indeksų. Akivaizdu, kad tai buvo JSONB pranašumas, nes EAV reikalingos jungtys negalėjo naudoti indeksų (svetimų raktų laukai nebuvo indeksuojami). Po to sukūriau indeksą 2 EAV verčių lentelės išorinių raktų stulpeliuose, taip pat indeksą GIN JSONB stulpeliui.

Duomenų atnaujinimas parodė tokius rezultatus pagal laiką (ms). Atminkite, kad skalė yra logaritminė:

EAV pakeitimas JSONB „PostgreSQL“.

Matome, kad JSONB yra daug (> 50000-x) greitesnis nei EAV, jei nenaudojate indeksų dėl anksčiau nurodytos priežasties. Kai indeksuojame stulpelius su pirminiais raktais, skirtumas beveik išnyksta, tačiau JSONB vis tiek yra 1,3 karto greitesnis nei EAV. Atminkite, kad JSONB stulpelio indeksas čia neturi jokios įtakos, nes vertinimo kriterijais nenaudojame nuosavybės stulpelio.

Pasirinkdami duomenis pagal nuosavybės vertę, gauname šiuos rezultatus (įprasta skalė):

EAV pakeitimas JSONB „PostgreSQL“.

Galite pastebėti, kad JSONB vėl veikia greičiau nei EAV be indeksų, tačiau kai EAV su indeksais, jis vis tiek veikia greičiau nei JSONB. Bet tada pamačiau, kad JSONB užklausų laikai buvo vienodi, tai paskatino mane suprasti, kad GIN indeksai neveikia. Matyt, kai naudojate GIN indeksą stulpelyje su užpildytomis ypatybėmis, jis galioja tik naudojant įtraukimo operatorių @>. Naudojau tai naujame bandyme ir tai turėjo didžiulę įtaką laikui: tik 0,153 ms! Tai yra 15000 25000 kartų greičiau nei EAV ir XNUMX XNUMX kartų greičiau nei operatorius ->>.

Manau, kad tai buvo pakankamai greita!

Duomenų bazės lentelės dydis

Palyginkime abiejų metodų lentelių dydžius. Psql galime parodyti visų lentelių ir indeksų dydį naudodami komandą dti+

EAV pakeitimas JSONB „PostgreSQL“.

Taikant EAV metodą, lentelės dydis yra apie 3068 MB, o indeksai - iki 3427 MB, iš viso 6,43 GB. JSONB metodas naudoja 1817 MB lentelei ir 318 MB indeksams, tai yra 2,08 GB. Pasirodo, 3 kartus mažiau! Šis faktas mane šiek tiek nustebino, nes kiekviename JSONB objekte saugome nuosavybės pavadinimus.

Tačiau vis tiek skaičiai kalba patys už save: EAV saugome 2 sveikuosius išorinius raktus kiekvienai atributo vertei, todėl gauname 8 baitus papildomų duomenų. Be to, EAV saugo visas nuosavybės vertes kaip tekstą, o JSONB, kur įmanoma, viduje naudos skaitines ir logines vertes, todėl bus mažesnis plotas.

rezultatai

Apskritai manau, kad objekto ypatybių išsaugojimas JSONB formatu gali palengvinti duomenų bazės kūrimą ir priežiūrą. Jei vykdote daug užklausų, viską laikant toje pačioje lentelėje, kurioje yra objektas, iš tikrųjų veiks efektyviau. Ir tai, kad tai supaprastina duomenų sąveiką, jau yra pliusas, tačiau gauta duomenų bazė yra 3 kartus mažesnė.

Taip pat, remiantis atliktais bandymais, galime daryti išvadą, kad veiklos nuostoliai yra labai nežymūs. Kai kuriais atvejais JSONB yra net greitesnis nei EAV, todėl jis yra dar geresnis. Tačiau šis etalonas, žinoma, neapima visų aspektų (pvz., subjektai, turintys labai daug savybių, reikšmingas esamų duomenų savybių skaičiaus padidėjimas,...), todėl jei turite pasiūlymų, kaip juos patobulinti. , nedvejodami palikite komentaruose!

Šaltinis: www.habr.com

Pirkite patikimą prieglobą svetainėms su DDoS apsauga, VPS VDS serveriais 🔥 Įsigykite patikimą svetainių talpinimą su DDoS apsauga, VPS VDS serveriais | ProHoster