Zëvendësimi i EAV me JSONB në PostgreSQL

TL;DR: JSONB mund ta thjeshtojë shumë zhvillimin e skemës së bazës së të dhënave pa sakrifikuar performancën e pyetjeve.

Paraqitje

Le të marrim një shembull klasik, ndoshta një nga rastet më të vjetra të përdorimit në botën e bazave të të dhënave relacionale: kemi një entitet dhe duhet të ruajmë veti (atribute) të caktuara të këtij entiteti. Por jo të gjitha instancat mund të kenë të njëjtin grup vetish dhe mund të shtohen më shumë veti në të ardhmen.

Zgjidhja më e thjeshtë për këtë problem është të krijoni një kolonë në tabelën e bazës së të dhënave për secilën vlerë të vetisë dhe thjesht të plotësoni ato të nevojshme për një instancë specifike të entitetit. Shkëlqyeshëm! Problemi u zgjidh... derisa tabela juaj të përmbajë miliona regjistrime dhe ju duhet të shtoni një regjistrim të ri.

Le të shqyrtojmë modelin EAV (Vlera-e-Atributit-Entitetit), është mjaft e zakonshme. Një tabelë përmban entitete (regjistrime), një tabelë tjetër përmban emrat e vetive (atributet) dhe një tabelë e tretë lidh entitetet me atributet e tyre dhe përmban vlerat e këtyre atributeve për entitetin aktual. Kjo ju lejon të keni grupe të ndryshme vetish për objekte të ndryshme, si dhe të shtoni veti menjëherë, pa ndryshuar strukturën e bazës së të dhënave.

Megjithatë, nuk do ta shkruaja këtë postim nëse nuk do të kishte disa të meta në qasjen EVA. Për shembull, marrja e një ose më shumë entiteteve me nga një atribut secili kërkon dy bashkime në pyetje: bashkimi i parë me tabelën e atributeve, bashkimi i dytë me tabelën e vlerave. Nëse një entitet ka dy atribute, atëherë kërkohen katër bashkime! Për më tepër, të gjitha atributet zakonisht ruhen si vargje, gjë që rezulton në detyrim të tipit si për rezultatin ashtu edhe për klauzolën WHERE. Nëse shkruani shumë pyetje, kjo është mjaft e kotë për sa i përket përdorimit të burimeve.

Pavarësisht këtyre mangësive të dukshme, EAV është përdorur prej kohësh për të zgjidhur këto lloj problemesh. Këto ishin mangësi të pashmangshme dhe thjesht nuk kishte një alternativë më të mirë.
Por më pas u shfaq një "teknologji" e re në PostgreSQL


Duke filluar me PostgreSQL 9.4, u shtua lloji i tĂ« dhĂ«nave JSONB pĂ«r ruajtjen e tĂ« dhĂ«nave binare JSON. Edhe pse ruajtja e JSON nĂ« kĂ«tĂ« format zakonisht kĂ«rkon pak mĂ« shumĂ« hapĂ«sirĂ« ​​dhe kohĂ« sesa ruajtja e JSON me tekst tĂ« thjeshtĂ«, operacionet me tĂ« janĂ« shumĂ« mĂ« tĂ« shpejta. JSONB gjithashtu mbĂ«shtet indeksimin, duke i bĂ«rĂ« pyetjet edhe mĂ« tĂ« shpejta.

Lloji i tĂ« dhĂ«nave JSONB na lejon tĂ« zĂ«vendĂ«sojmĂ« modelin e rĂ«ndĂ« EAV duke shtuar vetĂ«m njĂ« kolonĂ« tĂ« vetme JSONB nĂ« tabelĂ«n tonĂ« tĂ« entitetit, duke thjeshtuar ndjeshĂ«m dizajnin e bazĂ«s sĂ« tĂ« dhĂ«nave. MegjithatĂ«, shumĂ« argumentojnĂ« se kjo vjen me njĂ« kosto nĂ« performancë  Kjo Ă«shtĂ« arsyeja pse shkrova kĂ«tĂ« artikull.

Vendosja e një baze të dhënash testimi

PĂ«r kĂ«tĂ« krahasim, krijova bazĂ«n e tĂ« dhĂ«nave nĂ« njĂ« instalim tĂ« ri tĂ« PostgreSQL 9.5 nĂ« versionin prej 80 dollarĂ«sh. DigitalOcean Ubuntu 14.04 Pasi konfigurova disa parametra nĂ« postgresql.conf, ekzekutova ŃŃ‚ĐŸŃ‚ skript duke pĂ«rdorur psql. PĂ«r tĂ« paraqitur tĂ« dhĂ«nat si EAV, u krijuan tabelat e mĂ«poshtme:

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

MĂ« poshtĂ« Ă«shtĂ« njĂ« tabelĂ« ku do tĂ« ruhen tĂ« njĂ«jtat tĂ« dhĂ«na, por me atribute nĂ« njĂ« kolonĂ« JSONB – Pronat.

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

Duket shumĂ« mĂ« e thjeshtĂ«, apo jo? Pastaj u shtua nĂ« tabelat e entiteteve (entitet & entity_jsonb) 10 milionĂ« regjistrime, dhe nĂ« pĂ«rputhje me rrethanat, tabela u mbush me tĂ« dhĂ«na identike ku u pĂ«rdorĂ«n modeli EAV dhe qasja me kolonĂ«n JSONB – entity_jsonb.propertiesKĂ«shtu, morĂ«m disa lloje tĂ« ndryshme tĂ« tĂ« dhĂ«nave nĂ« tĂ« gjithĂ« grupin e vetive. Shembull tĂ« dhĂ«nash:

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

Pra, tani kemi të dhëna identike për të dyja opsionet. Le të fillojmë të krahasojmë implementimet në jetën reale!

Thjeshtimi i dizajnit

Më parë u përmend se dizajni i bazës së të dhënave ishte thjeshtuar ndjeshëm: një tabelë, duke përdorur një kolonë JSONB për vetitë, në vend të tre tabelave për EAV. Por si përkthehet kjo në pyetje? Përditësimi i një vetie të vetme entiteti duket kështu:

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

Siç mund ta shihni, pyetja e fundit nuk duket më e thjeshtë. Për të përditësuar vlerën e një vetie në një objekt JSONB, duhet të përdorim funksionin jsonb_set(), dhe duhet ta kalojë vlerën tonë të re si një objekt JSONB. Megjithatë, nuk kemi nevojë të dimë ndonjë identifikues paraprakisht. Duke parë shembullin EAV, duhet të dimë si entity_id ashtu edhe entity_attribute_id për të kryer përditësimin. Nëse doni të përditësoni një veti në një kolonë JSONB bazuar në emrin e objektit, e gjitha bëhet në një rresht të thjeshtë.

Tani le të zgjedhim entitetin që sapo përditësuam bazuar në ngjyrën e tij të re:

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

Mendoj se mund të biem dakord që e dyta është më e shkurtër (pa bashkimin!) dhe për këtë arsye më e lexueshme. JSONB fiton këtu! Ne përdorim operatorin JSON ->> për të marrë ngjyrën si vlerë teksti nga objekti JSONB. Ekziston edhe një mënyrë e dytë për të arritur të njëjtin rezultat në modelin JSONB duke përdorur operatorin @>:

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

Kjo është pak më e komplikuar: ne kontrollojmë nëse objekti JSON në kolonën e vetive përmban objektin në anën e djathtë të operatorit @>. Më pak i lexueshëm, më shumë performancë (shih më poshtë).

Le ta thjeshtojmë edhe më shumë përdorimin e JSONB kur duhet të zgjidhni shumë veti njëkohësisht. Këtu shkëlqen vërtet qasja JSONB: ne thjesht zgjedhim vetitë si kolona shtesë në grupin tonë të rezultateve, pa pasur nevojë për bashkime:

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

Me EAV, do t'ju duhen dy bashkime për secilën veti që dëshironi të pyetni. Sipas mendimit tim, pyetjet e mësipërme demonstrojnë një thjeshtëzim të konsiderueshëm në dizajnin e bazës së të dhënave. Gjithashtu mund të shihni më shumë shembuj se si të shkruani pyetje JSONB në kjo postë.
Tani është koha të flasim për performancën.

prodhimtari

PĂ«r tĂ« krahasuar performancĂ«n qĂ« pĂ«rdora SHPJEGO ANALIZO nĂ« pyetje pĂ«r tĂ« llogaritur kohĂ«n e ekzekutimit. Çdo pyetje u ekzekutua tĂ« paktĂ«n tre herĂ«, sepse planifikuesi i pyetjeve zgjat mĂ« shumĂ« herĂ«n e parĂ«. SĂ« pari, unĂ« i ekzekutova pyetjet pa indekse. Kjo padyshim shĂ«rbeu si njĂ« avantazh i JSONB, pasi bashkimet e kĂ«rkuara pĂ«r EAV nuk mund tĂ« pĂ«rdornin indekse (fushat e çelĂ«sit tĂ« huaj nuk ishin tĂ« indeksuara). Pas kĂ«saj, krijova njĂ« indeks nĂ« dy kolonat e çelĂ«sit tĂ« huaj tĂ« tabelĂ«s sĂ« vlerave EAV, si dhe njĂ« indeks GIN pĂ«r kolonĂ«n JSONB.

Përditësimet e të dhënave treguan rezultatet e mëposhtme kohore (në ms). Vini re se shkalla është logaritmike:

Zëvendësimi i EAV me JSONB në PostgreSQL

Shohim që JSONB është dukshëm (>50000 herë) më i shpejtë se EAV pa indekse, për arsyen e përmendur më sipër. Kur indeksojmë kolonat e çelësit primar, ndryshimi pothuajse zhduket, por JSONB është ende 1,3 herë më i shpejtë se EAV. Vini re se indeksi në kolonën JSONB nuk ka efekt këtu, pasi nuk e përdorim kolonën e vetive në kriteret e vlerësimit.

Për përzgjedhjen e të dhënave bazuar në vlerën e pronës, marrim rezultatet e mëposhtme (shkalla normale):

Zëvendësimi i EAV me JSONB në PostgreSQL

Mund të shihni që JSONB është përsëri më i shpejtë se EAV pa indekse, por kur EAV indeksohet, është akoma më i shpejtë se JSONB. Por pastaj pashë që kohët për pyetjet JSONB ishin të njëjta, gjë që më çoi në faktin se indekset GIN nuk aktivizohen. Me sa duket, kur përdorni një indeks GIN në një kolonë me veti të populluara, ai hyn në fuqi vetëm kur përdorni operatorin e përfshirjes @>. E përdora këtë në një test të ri dhe pati një ndikim të madh në kohë: vetëm 0,153 ms! Kjo është 15000 herë më e shpejtë se EAV dhe 25000 herë më e shpejtë se operatori ->>.

Mendoj se ishte mjaft shpejt!

Madhësia e tabelave të bazës së të dhënave

Le të krahasojmë madhësitë e tabelave për të dyja qasjet. Në psql, ne mund të shfaqim madhësinë e të gjitha tabelave dhe indekseve duke përdorur komandën dti+

Zëvendësimi i EAV me JSONB në PostgreSQL

Me qasjen EAV, madhësitë e tabelave janë afërsisht 3068 MB dhe indekset janë deri në 3427 MB, për një total prej 6,43 GB. Duke përdorur qasjen JSONB, tabela përdor 1817 MB dhe indekset 318 MB, për një total prej 2,08 GB. Kjo është një e treta e madhësisë! Ky fakt më habiti pak, pasi ne ruajmë emrat e vetive në çdo objekt JSONB.

Por numrat flasin vetë: në EAV, ne ruajmë dy çelësa të huaj me numra të plotë për vlerën e atributit, duke rezultuar në 8 bajt të dhëna shtesë. Për më tepër, në EAV, të gjitha vlerat e vetive ruhen si tekst, ndërsa JSONB do të përdorë vlera numerike dhe logjike brenda vendit, aty ku është e mundur, duke rezultuar në një gjurmë më të vogël.

Rezultatet e

Në përgjithësi, mendoj se ruajtja e vetive të entitetit në formatin JSONB mund ta thjeshtojë ndjeshëm projektimin dhe mirëmbajtjen e bazës suaj të të dhënave. Nëse kryeni shumë pyetje, ruajtja e gjithçkaje në të njëjtën tabelë me entitetin do të jetë vërtet më efikase. Fakti që thjeshton ndërveprimet e të dhënave është tashmë një plus, por baza e të dhënave që rezulton është gjithashtu tre herë më e vogël në madhësi.

Gjithashtu, bazuar në rezultatet e testit benchmark, mund të konkludojmë se penalizimi i performancës është shumë i vogël. Në disa raste, JSONB madje performon më shpejt se EAV, duke e bërë atë edhe më të mirë. Megjithatë, ky test benchmark sigurisht që nuk i mbulon të gjitha aspektet (p.sh., entitete me një numër shumë të madh vetish, një rritje të konsiderueshme të numrit të vetive në të dhënat ekzistuese, etj.), kështu që nëse keni ndonjë sugjerim për përmirësim, ju lutemi mos ngurroni t'i lini ato në komente!

Burimi: www.habr.com

Bleni njĂ« host tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, serverĂ« VPS VDS đŸ”„ Bleni hosting tĂ« besueshĂ«m tĂ« faqeve tĂ« internetit me mbrojtje DDoS, servera VPS VDS | ProHoster