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

TL; DR: JSONB mund të thjeshtojë ndjeshëm zhvillimin e skemës së DB pa e sakrifikuar performancën në pyetje.

Hyrje

Le të kontribuojmë një shembull klasik, ndoshta një nga përdorimet më të vjetra në botën e DB-ve relacional (database): kemi një entitet dhe duhet të ruajmë disa veti (atribute) të këtij entiteti. Por jo të gjitha instancat mund të kenë të njëjtin set vetish, dhe gjithashtu në të ardhmen, mund të ketë shtim të vetive të tjera.

MĂ«nyra mĂ« e thjeshtĂ« pĂ«r tĂ« zgjidhur kĂ«tĂ« problem Ă«shtĂ« tĂ« krijoni njĂ« kolonĂ« nĂ« tabelĂ«n e DB-sĂ« pĂ«r çdo vlerĂ« tĂ« vetisĂ«, dhe thjesht tĂ« plotĂ«soni ato qĂ« nevojiten pĂ«r njĂ« instancĂ« tĂ« caktuar tĂ« entitetit. Super! Problemi Ă«shtĂ« zgjidhur
 deri nĂ« momentin kur tabela juaj pĂ«rmban miliona regjistrime dhe ju nevojitet tĂ« shtoni njĂ« regjistrim tĂ« ri.

Le tĂ« shqyrtojmĂ« modelin EAV (Entity-Attribute-Value), ai takon shpesh. NjĂ« tavolinĂ« pĂ«rmban entitete (shĂ«nime), njĂ« tavolinĂ« tjetĂ«r pĂ«rmban emrat e vetive (atributeve), dhe tavolina e tretĂ« lidh entitetet me atributet e tyre dhe mban vlerĂ«n e kĂ«tyre atributeve pĂ«r entitetin aktual. Kjo ju ofron mundĂ«sinĂ« pĂ«r tĂ« pasur grupe tĂ« ndryshme vetish pĂ«r objekte tĂ« ndryshme, si dhe pĂ«r tĂ« shtuar vetitĂ« “nĂ« flakĂ«â€, pa ndryshuar strukturat e DB-sĂ«.

MegjithatĂ«, nuk do tĂ« shkruaja kĂ«tĂ« shĂ«nim nĂ«se nuk do tĂ« ishin disa mangĂ«si nĂ« qasjen qĂ« pĂ«rdor EVA. PĂ«r shembull, pĂ«r tĂ« marrĂ« njĂ« ose mĂ« shumĂ« entitete qĂ« kanĂ« nga 1 atribut kĂ«rkohen 2 bashkime nĂ« kĂ«rkesĂ«: e para – bashkimi me tavolinĂ«n e atributeve, e dyta – bashkimi me tavolinĂ«n e vlerave. NĂ«se entitetet kanĂ« 2 atribute, atĂ«herĂ« nevojiten tashmĂ« 4 bashkime! PĂ«r mĂ« tepĂ«r, tĂ« gjitha atributet zakonisht ruhen si vargje, çka rezulton nĂ« konvertimin e tipeve pĂ«r si pĂ«r rezultatin ashtu edhe pĂ«r kushtin WHERE. NĂ«se shkruani shumĂ« kĂ«rkesa, kjo Ă«shtĂ« mjaft Shpenzuese, sa i pĂ«rket pĂ«rdorimit tĂ« burimeve.

Megjithatë, pavarësisht këtyre mangësive të dukshme, EAV ka qenë prej kohësh në përdorim për zgjidhjen e këtij lloji problemi. Këto ishin mangësi të pashmangshme, dhe nuk kishte një alternativë më të mirë.
Por pastaj në PostgreSQL u shfaq një 'teknologji' e re


Që nga PostgreSQL 9.4, u shtua një tip të dhënash JSONB për të ruajtur të dhënat njëshe në formatin JSON. Ndërsa ruajtja e JSON në këtë format zakonisht kërkon pak më shumë hapësirë dhe kohë se sa JSON-i i thjeshtë tekstor, operacionet me të ndodhin shumë më shpejt. Po ashtu, JSONB mbështet indeksimin, gjë që i bën kërkesat ndaj tij edhe më të shpejta.

Tipi i tĂ« dhĂ«nave JSONB na lejon tĂ« zĂ«vendĂ«sojmĂ« modelin e rĂ«ndĂ« EAV duke shtuar vetĂ«m njĂ« kolonĂ« JSONB nĂ« tabelĂ«n tonĂ« tĂ« entiteteve, çka e thjeshton ndjeshĂ«m projektimin e bazĂ«s sĂ« tĂ« dhĂ«nave. Por shumĂ« njerĂ«z pretendojnĂ« se kjo duhet tĂ« shoqĂ«rohet me njĂ« rĂ«nie tĂ« performancĂ«s
 PĂ«r kĂ«tĂ« arsye shkruhet ky artikull.

Konfigurimi i bazës së të dhënave për testim

Për këtë krahasim, kam krijuar një bazë të dhënash në një instalim të ri të PostgreSQL 9.5 në një konfigurim prej 80 dollarësh. DigitalOcean Ubuntu 14.04. Pasi kam vendosur disa parametra në postgresql.conf, kam nisur këtë skripti me psql. Për të paraqitur të dhënat në formatin EAV, u krijuan tabelat e mëposhtme:

KRIJO TABELË enti (
  id           SERIAL KRYESORE, 
  emri         TEKST, 
  përshkrimi   TEKST
);
KRIJO TABELË entity_attribute (
  id          SERIAL KRYESORE, 
  emri        TEKST
);
KRIJO TABELË entity_attribute_value (
  id                  SERIAL KRYESORE, 
  entity_id           INT    REFERENCAT entity(id), 
  entity_attribute_id INT    REFERENCAT entity_attribute(id), 
  vlera               TEKST
);

MĂ« poshtĂ« Ă«shtĂ« tabela ku do tĂ« ruhen tĂ« njĂ«jtat tĂ« dhĂ«na, por me atribute nĂ« kolonĂ«n e tipit JSONB – propertietet.

KRIJO TABELË entity_jsonb (
  id          SERIAL KRYESORE, 
  emri        TEKST, 
  përshkrimi TEKST,
  propertietet  JSONB
);

Duket shumĂ« mĂ« e thjeshtĂ«, apo jo? MĂ« pas u shtuan nĂ« tabelat e entiteteve (enti & entity_jsonb) 10 milijon regjistrime, dhe si pĂ«rfundim, u plotĂ«suan tĂ« dhĂ«nat e njĂ«jta tabelave qĂ« pĂ«rdorin modelin EAV dhe qasjen me kolonĂ«n JSONB – entity_jsonb.propertietet. KĂ«shtu, morĂ«m disa lloje tĂ« ndryshme tĂ« tĂ« dhĂ«nave nĂ« tĂ«rĂ« grupin e pronave. Shembuj tĂ« dhĂ«nash:

{
  id:          1
  emri:        "Entity1"
  përshkrimi: "Entitet testi nr. 1"
  propertietet:  {
    ngjyra:        "e kuqe"
    gjatësia:       120
    gjerësi:        3.1882420
    kaçkavall: true
    vendi:      "Belgjika"
  } 
}

Pra, tani kemi të dhëna të njëjta për dy variante. Le të fillojmë të krahasojmë realizimet në punë!

Thjeshtimi i dizajnit

Kemi thënë më parë se dizajni i DB është simplifikuar ndjeshëm: një tabelë, përmes përdorimit të kolonës JSONB për vetitë, në vend të përdorimit të tre tabelave për EAV. Por si reflektohet kjo në pyetje?

-- 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ç e shohim, kĂ«rkesa e fundit nuk duket mĂ« e thjeshtĂ«. QĂ« tĂ« pĂ«rditĂ«sojmĂ« vlerĂ«n e njĂ« vetie nĂ« objektin JSONB, ne duhet tĂ« pĂ«rdorim funksionin jsonb_set(), dhe duhet tĂ« kalojmĂ« vlerĂ«n tonĂ« tĂ« re si njĂ« objekt JSONB. MegjithatĂ«, nuk na nevojitet tĂ« dimĂ« ndonjĂ« identifikues paraprakisht. Duke parĂ« shembullin me EAV, na nevojitet tĂ« dimĂ« si entity_id, ashtu edhe entity_attribute_id pĂ«r tĂ« kryer pĂ«rditĂ«simin. NĂ«se dĂ«shiron tĂ« pĂ«rditĂ«sosh njĂ« vetie nĂ« kolonĂ«n JSONB bazuar nĂ« emrin e objektit, – kjo bĂ«het me njĂ« rresht tĂ« thjeshtĂ«.

Tani le të zgjedhim atë entitet që sapo e përditësuam, sipas kushtit të ngjyrës së 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 se mund të pajtohemi që e dyta është më e shkurtër (pa join!), dhe për rrjedhojë më e lexueshme. Këtu fiton JSONB! Ne e përdorim operatorin JSON ->>, për të marrë ngjyrën si vlerë tekstuale nga objekti JSONB. Ekziston gjithashtu një mënyrë tjetër 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 pronave përmban objektin që ndodhet në të djathtë të operatorit @>. Më pak e lexueshme, më e prodhueshme (shih më poshtë).

Le të thjeshtojmë përdorimin e JSONB edhe më shumë, kur ju nevojitet të zgjidhni disa prona në të njëjtën kohë. Këtu është ku i përshtatet vërtet qasja JSONB: ne thjesht zgjedhim pronat si kolona shtesë në grupin tonë të rezultateve pa nevojën për bashkime:

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

Me EAV nevojiten 2 bashkime për çdo pronë që dëshironi të kërkoni. Sipas mendimit tim, kërkesat e lartpërmendura tregojnë një thjeshtim të madh në dizajnin e bazës së të dhënave. Shihni më shumë shembuj se si të shkruani kërkesa për JSONB, gjithashtu në këtë post.
Tani është koha për të folur rreth performancës.

Performanca

PĂ«r tĂ« krahasuar performancĂ«n, pĂ«rdora EXPLAIN ANALYZE nĂ« kĂ«rkesat, pĂ«r tĂ« llogaritur kohĂ«n e ekzekutimit. Çdo kĂ«rkesĂ« u ekzekutua tĂ« paktĂ«n tre herĂ«, sepse hera e parĂ« planifikuesit e kĂ«rkesave kĂ«rkon mĂ« shumĂ« kohĂ«. E fillova duke ekzekutuar kĂ«rkesat pa ndonjĂ« indeks. Pa dyshim, kjo i shĂ«rbente JSONB, pasi bashkimet e nevojshme pĂ«r EAV nuk mund tĂ« pĂ«rdorin indekset (fushat e çelĂ«sit tĂ« jashtĂ«m nuk ishin indeksuar). Pas kĂ«saj, krijova njĂ« indeks pĂ«r 2 kolonat e çelĂ«save tĂ« jashtĂ«m nĂ« tabelĂ«n EAV si dhe njĂ« indeks GIN pĂ«r kolonĂ«n JSONB.

Rritjet e të dhënave treguan rezultatet e mëposhtme në kohë (në ms). Kushtojini vëmendje se shkalla është logarithmike:

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

Shohim se se JSONB është shumë më i shpejtë (> 50000-x) se EAV, nëse nuk përdoren indekset, për arsye të përmendura më sipër. Kur indeksojmë kolonat me çelësa primarë, diferenca pothuajse zhduket, por JSONB është akoma 1,3 herë më i shpejtë se EAV. Vini re se indeksi në kolonën JSONB këtu nuk ka asnjë ndikim, pasi nuk e përdorim kolonën e pronave në kriteret e vlerësimit.

Për të zgjedhur të dhënat mbi bazën e një vlere të pronës, kemi rezultatet e mëposhtme (shkallë normale):

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

VĂ«rehet se JSONB pĂ«rsĂ«ri funksionon mĂ« shpejt se EAV pa indekse, por kur EAV ka indekse – akoma funksionon mĂ« shpejt se JSONB. Por mĂ« pas pashĂ« se koha pĂ«r kĂ«rkesat JSONB ishte e njĂ«jtĂ«, kjo mĂ« shtyu nĂ« faktin se indekset GIN nuk punonin. Duket se kur pĂ«rdorni indeksin GIN pĂ«r njĂ« kolonĂ« me pronat e mbushura, ai vepron vetĂ«m kur pĂ«rdoret operatori i pĂ«rfshirjes @>. E pĂ«rdora kĂ«tĂ« nĂ« njĂ« test tĂ« ri, i cili kishte njĂ« ndikim tĂ« madh nĂ« kohĂ«n: 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ë BD

Le të krahasojmë përmasat e tabelave në të dy qasjet. Në psql mund të tregojmë madhësinë e të gjitha tabelave dhe indekseve përmes komandës dti+

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

Për qasjen EAV, përmasat e tabelave arrijnë rreth 3068 MB, ndërsa indekset deri në 3427 MB, duke dhënë gjithsej 6,43 GB. Duke përdorur qasjen me JSONB, përdoren 1817 MB për tabelën dhe 318 MB për indekset, duke bërë gjithsej 2,08 GB. Kjo është tri herë më pak! Ky fakt më befasoi pak, sepse ne ruajmë emrat e pronave në çdo objekt JSONB.

Por numrat flasin vetë: në EAV ne ruajmë 2 çelësa të jashtëm të plotë mbi vlerën e atributit, duke rezultuar në 8 byte të dhëna shtesë. Për më tepër, në EAV të gjitha vlerat e pronave ruhen si tekst, ndërsa JSONB do të përdorë vlera numerike dhe logjike brenda, ku është e mundur, duke rezultuar në një volum më të vogël.

Përfundimet

Në përgjithësi, mendoj se ruajtja e vetive të entiteteve në formatin JSONB mund të thjeshtojë ndjeshëm dizenjimin dhe mirëmbajtjen e bazës suaj të të dhënave. Nëse bëni shumë kërkesa, atëherë gjithçka që ruhet në një tabelë me entitetin do të funksionojë vërtet më efektivisht. Dhe fakti që kjo e thjeshton ndërveprimin midis të dhënave është një përfitim, por gjithashtu baza e të dhënave rezultuese është 3 herë më e vogël në volum.

Gjithashtu, sipas testeve të kryera, mund të përfundosh se humbjet e performancës janë shumë të pakta. Në disa raste, JSONB madje funksionon më shpejt se EAV, gjë që e bën atë edhe më të mirë. Megjithatë, ky test standard natyrisht nuk kap të gjitha aspektet (për shembull, entitete me një numër shumë të madh vetish, rritje të konsiderueshme të numrit të vetive të të dhënave ekzistuese,...), ndaj, nëse keni ndonjë sugjerim se si t'i përmirësoni ato, ju lutemi mos ngurroni të lini komentet tuaja!

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster