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

TL; DR: JSONB mund të thjeshtojë ndjeshëm zhvillimin e skemës së DB pa bërë kompromis në performancën e kërkesave.

Hyrje

Le të marrim një shembull klasik, ndoshta njërin prej varianteve më të vjetra të përdorimit në botë të DB-ve relacional (të dhënash): kemi një entitet dhe duhet të ruajmë disa pronësi (atribute) të këtij entiteti. Por jo të gjitha instancat mund të kenë të njëjtin grup pronash, dhe gjithashtu, në të ardhmen, është e mundur të shtohen edhe pronësi të tjera.

Mënyra më e thjeshtë për të zgjidhur këtë problem është krijimi i një kolone në tabelën e DB për çdo vlerë pronësie, dhe thjesht mbushja e atyre që janë të nevojshme për një instancë të caktuar të entitetit. Shumë mirë! Problemi është zgjidhur... deri në momentin kur tabela juaj ka miliona regjistrime dhe ju duheni të shtoni një regjistrim të ri.

Le të shqyrtojmë modelin EAV (Entity-Attribute-Value), i cili ndodhet mjaft shpesh. Një tabelë përmban entitetet (regjistrimet), një tabelë tjetër përmban emrat e pronarive (atributeve), dhe një tabelë e tretë lidhet entitetet me atributet e tyre dhe përmban vlerën e këtyre atributëve për entitetin aktual. Kjo ju jep mundësinë të keni grupe të ndryshme pronash për objekte të ndryshme, si dhe të shtoni prona "në flakë", pa ndryshuar strukturën e DB.

MegjithatĂ«, nuk do ta shkruaja kĂ«tĂ« shĂ«nim po tĂ« mos ishin disa disavantazhe nĂ« qasjen e pĂ«rdorimit tĂ« 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 tabelĂ«n e atributeve, e dyta – bashkimi me tabelĂ«n e vlerave. NĂ«se entiteti ka 2 atribute, atĂ«herĂ« kĂ«rkohen tashmĂ« 4 bashkime! PĂ«r mĂ« tepĂ«r, tĂ« gjithĂ« atributet zakonisht ruhen si stringje, gjĂ« qĂ« çon nĂ« konvertimin e tipeve, si pĂ«r rezultatet ashtu edhe pĂ«r kushtin WHERE. NĂ«se shkruani shumĂ« kĂ«rkesa, kjo Ă«shtĂ« mjaft shpĂ«rdoruese, nĂ« aspektin e pĂ«rdorimit tĂ« burimeve.

Pavarësisht këtyre disavantazheve të dukshme, EAV ka qenë prej kohësh i përdorur për t'i zgjidhur këtë lloj problemesh. Këto ishin disavantazhe të pashmangshme, dhe nuk kishte një alternativë më të mirë.
Por pastaj, një "teknologji" e re u shfaq në PostgreSQL...

Që nga PostgreSQL 9.4, është shtuar tipi i të dhënave JSONB për të ruajtur të dhëna JSON në format binar. Edhe pse ruajtja e JSON në këtë format zakonisht merr pak më shumë hapësirë dhe kohë se sa JSON-i i thjeshtë tekstual, kryerja e operacioneve me të ndodh shumë më shpejt. Po ashtu, JSONB mbështet indeksimin, 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 mbi-ngarkuar EAV duke shtuar vetëm një kolonë JSONB në tabelën tonë të entiteteve, gjë që e thjeshton ndjeshëm projektimin e bazës së të dhënave. Por shumë njerëz argumentojnë se kjo duhet të shoqërohet me një ulje të performancës
 Kjo është arsyeja pse u shfaq ky artikull.

Konfigurimi i bazës së të dhënave të testit

PĂ«r kĂ«tĂ« krahasim krijova njĂ« bazĂ« tĂ« dhĂ«nash nĂ« njĂ« instalim tĂ« ri tĂ« PostgreSQL 9.5 nĂ« njĂ« paketĂ« 80-dollarĂ«sh DigitalOcean Ubuntu 14.04. Pas konfigurimit tĂ« disa parametrave nĂ« postgresql.conf, fillova kĂ«tĂ« skропtin duke pĂ«rdorur psql. PĂ«r tĂ« paraqitur tĂ« dhĂ«nat nĂ« formatin 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Ă« tabela ku do tĂ« ruhet tĂ« njĂ«jtat tĂ« dhĂ«na, por me atributet nĂ« kolonĂ«n e tipit JSONB – properties.

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

Duket shumĂ« mĂ« e thjeshtĂ«, apo jo? Pastaj u shtuan nĂ« tabelat e entiteteve (entity & entity_jsonb) 10 milionĂ« rekorde dhe pĂ«rkatĂ«sisht, u mbushĂ«n me tĂ« dhĂ«na tĂ« njĂ«jta tabelat ku pĂ«rdoret modeli EAV dhe qasja me kolumnĂ«n JSONB – entity_jsonb.properties. Pra, kĂ«shtu morĂ«m disa lloje tĂ« ndryshme tĂ« tĂ« dhĂ«nave midis gjithĂ« setit tĂ« vetive. Shembuj tĂ« dhĂ«nash:

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

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

Thjeshtësimi i dizajnit

Më parë u tha se dizajni i DB ishte thjeshtuar ndjeshëm: një tabelë, për shkak të përdorimit të kolonës JSONB për vetitë, në vend të përdorimit të tre tabelave për EAV. Por si ndikon kjo në kërkesa? Përditësimi i një veti të entitetit duket si në vijim:

-- 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ë. Për të përditësuar vlerën e pronës 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 e 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ëshironi të përditësoni pronën në kolumnën JSONB në bazë të emrit të objektit, kjo bëhet me një rresht të thjeshtë.

Tani le të zgjedhim atë entitet që sapo e përditësuam, sipas kushteve 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 mund të pajtohemi se e dyta është më e shkurtër (pa join!), dhe për pasojë më e lexueshme. Këtu JSONB fiton! Ne 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ë kolumnën e pronave përmban objektin që ndodhet në të djathtën e operatorit @>. Më pak e lexueshme, por më efikase (shih më poshtë).

Le të thjeshtojmë përdorimin e JSONB edhe më shumë, kur ju nevojitet të zgjidhni disa prona njëkohësisht. Këtu është ku qasja JSONB merr vlerën më të madhe: 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 ju do t'ju duhen 2 bashkime për çdo pronë që dëshironi të kërkoni. Në mendimin tim, kërkesat e mësipërme tregojnë një thjeshtim të madh në dizajnin e bazës së të dhënave. Mund të shihni më shumë shembuj se si të shkruani kërkesa ndaj JSONB, ndoshta gjithashtu në këtë post.
Tani është koha për të folur për performancën.

Performanca

PĂ«r tĂ« krahasuar performancĂ«n, kam pĂ«rdorur EXPLAIN ANALYZE nĂ« kĂ«rkesat, pĂ«r llogaritjen e kohĂ«s sĂ« ekzekutimit. Çdo kĂ«rkesĂ« u ekzekutua tĂ« paktĂ«n tri herĂ«, sepse herĂ«n e parĂ« planifikuesi i kĂ«rkesave kishte nevojĂ« pĂ«r mĂ« shumĂ« kohĂ«. Fillimisht, unĂ« ekzekutova kĂ«rkesat pa asnjĂ« indeks. ËshtĂ« e qartĂ« se kjo shĂ«rbeu si njĂ« avantazh pĂ«r JSONB, pasi bashkimet, tĂ« nevojshme pĂ«r EAV, nuk mund tĂ« pĂ«rdornin indekset (fushat e çelĂ«sit tĂ« jashtĂ«m nuk ishin tĂ« indeksuara). Pas kĂ«saj, unĂ« krijova njĂ« indeks pĂ«r 2 kolona tĂ« çelĂ«sit tĂ« jashtĂ«m nĂ« tabelĂ«n e vlerave EAV, si dhe njĂ« indeks GIN pĂ«r kolonĂ«n JSONB.

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

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

Shohim se JSONB është shumë më i shpejtë (> 50000-x) se EAV, nëse nuk përdoren indekset, për shkak të arsyes së përmendur më lart. Kur ne indeksojmë kolonat me çelësa primarë, ndryshimi pothuajse zhduket, por JSONB është ende 1.3 herë më i shpejtë se EAV. Vini re se indeksi në kolonën JSONB këtu nuk ka asnjë ndikim, pasi ne nuk e përdorim kolonën e pronave në kriteret e vlerësimit.

Për të zgjedhur të dhënat në bazë të vlerës së pronës, ne marrim rezultatet e mëposhtme (shkalla normale):

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

Mund tĂ« vĂ«rejmĂ« se JSONB pĂ«rsĂ«ri funksionon mĂ« shpejt se EAV pa indekse, por kur EAV ka indekse – ai pĂ«rsĂ«ri punon mĂ« shpejt se JSONB. Por pastaj pashĂ« se koha pĂ«r kĂ«rkesat JSONB ishte e njĂ«jtĂ«, kjo mĂ« shtyu tĂ« mendoj se indekset GIN nuk po funksiononin. Rrjedhimisht, kur pĂ«rdorni indekset GIN pĂ«r njĂ« kolonĂ« me pronat e mbushura, ato veprojnĂ« vetĂ«m kur pĂ«rdoret operatori i pĂ«rfshirjes @>. UnĂ« e pĂ«rdora kĂ«tĂ« nĂ« njĂ« test tĂ« ri, i cili kishte njĂ« ndikim tĂ« madh nĂ« kohĂ«: vetĂ«m 0,153 ms! Kjo Ă«shtĂ« 15000 herĂ« mĂ« shpejt se EAV, dhe 25000 herĂ« mĂ« shpejt se operatori ->>.

Mendoj se ishte mjaft shpejt!

Madhësia e tabelave të DB

Le të krahasojmë madhësitë e tabelave me të dyja qasjet. Në psql ne mund të tregojmë madhësinë e të gjitha tabelave dhe indekseve me komandën dti+

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

Për qasjen EAV, madhësitë e tabelave janë rreth 3068 MB, ndërsa indekset janë deri në 3427 MB, duke dhënë së bashku 6.43 GB. Për qasjen me JSONB përdoren 1817 MB për tabelën dhe 318 MB për indekset, që përbën 2.08 GB. Pra, është 3 herë më pak! Ky fakt më befasoi pak, sepse ne ruajmë emrat e pronave në çdo objekt JSONB.

Megjithatë, numrat flasin vetë: në EAV ruajmë 2 çelësa të jashtëm numërorë për vlerën e atributit, duke rezultuar në 8 byte të dhënash shtesë. Për më tepër, në EAV të gjitha vlerat e pronave ruajnë si tekst, ndërsa JSONB do të përdorë vlera numerike dhe logjike brenda, ku të jetë e mundur, duke rezultuar kështu në një vëllim më të vogël.

Përfundime

Në përgjithësi, mendoj se ruajtja e pronave të entiteteve në formatin JSONB mund të thjeshtojë në mënyrë të konsiderueshme projektimin dhe mirëmbajtjen e bazës tuaj të të dhënave. Nëse realizoni shumë kërkesa, atëherë gjithçka që ruhet në një tavolinë me entitetin do të funksionojë me të vërtetë më efikas. Dhe fakti se kjo e thjeshton ndërveprimin midis të dhënave është tashmë një plus, por edhe DB përfundimtare është 3 herë më e vogël në vëllim.

Gjithashtu, nga testet e realizuara, mund të përfundojmë se humbjet e performancës janë shumë të vogla. Në disa raste, JSONB madje funksionon më shpejt se EAV, gjë që e bën atë edhe më të mirë. Sidoqoftë, ky test referencë, natyrisht, nuk mbulon të gjitha aspektet (për shembull, entitete me një numër të madh pronash, rritje të konsiderueshme të numrit të pronave të të dhënave ekzistuese,
), prandaj, nëse keni ndonjë sugjerim se si t'i përmirësoni ato, ju lutem mos ngurroni të lini komentet tuaja!

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster