TL; DR: JSONB povas multe simpligi datumbazan skemon-disvolviĝon sen ofero de demanda rendimento.
Enkonduko
Ni donu klasikan ekzemplon de verŝajne unu el la plej malnovaj uzkazoj en la mondo de rilata datumbazo (datumbazo): ni havas enton, kaj ni devas konservi certajn ecojn (atributojn) de ĉi tiu ento. Sed ne ĉiuj okazoj povas havi la saman aron de propraĵoj, kaj pli da propraĵoj eble estos aldonitaj estonte.
La plej facila maniero solvi ĉi tiun problemon estas krei kolumnon en la datumbaza tabelo por ĉiu posedaĵvaloro, kaj simple plenigi tiujn, kiuj estas bezonataj por specifa enta petskribo. Bonege! Problemo solvita... ĝis via tablo enhavas milionojn da rekordoj kaj vi devas aldoni novan rekordon.
Konsideru la EAV-ŝablonon (), ĝi okazas sufiĉe ofte. Unu tabelo enhavas entojn (rekordojn), alia tabelo enhavas posednomojn (atributojn), kaj tria tabelo asocias entojn kun iliaj atributoj kaj enhavas la valoron de tiuj atributoj por la nuna unuo. Ĉi tio donas al vi la kapablon havi malsamajn arojn de propraĵoj por malsamaj objektoj, kaj ankaŭ aldoni proprietojn sur la flugo sen ŝanĝi la datumbazan strukturon.
Tamen, mi ne skribus ĉi tiun afiŝon se ne estus iuj malavantaĝoj al la EVA-aliro. Do, ekzemple, por akiri unu aŭ plurajn entojn, kiuj havas po 1 atributo, 2 kuniĝoj estas bezonataj en la demando: la unua estas kunigo kun la atributa tabelo, la dua estas kunigo kun la valortabelo. Se ento havas 2 atributojn, tiam 4 kuniĝoj estas bezonataj! Aldone, ĉiuj atributoj estas tipe stokitaj kiel ŝnuroj, kio rezultigas tipan gisadon por kaj la rezulto kaj la WHERE-frazo. Se vi skribas multajn demandojn, tiam ĉi tio estas sufiĉe malŝparema laŭ rimedo-uzo.
Malgraŭ ĉi tiuj evidentaj mankoj, EAV estas delonge uzata por solvi ĉi tiujn specojn de problemoj. Ĉi tiuj estis neeviteblaj mankoj, kaj simple ne estis pli bona alternativo.
Sed tiam nova "teknologio" aperis en PostgreSQL...
Komencante kun PostgreSQL 9.4, la JSONB-datumtipo estis aldonita por stoki JSON-binarajn datumojn. Kvankam stoki JSON en ĉi tiu formato kutime prenas iom pli da spaco kaj tempo ol klarteksto JSON, fari operaciojn sur ĝi estas multe pli rapida. JSONB ankaŭ subtenas indeksadon, kio faras demandojn eĉ pli rapidaj.
La JSONB-datumtipo permesas al ni anstataŭigi la maloportunan EAV-ŝablonon aldonante nur unu JSONB-kolumnon al nia entabelo, multe simpligante datumbazan dezajnon. Sed multaj argumentas, ke tio devus esti akompanata de malpliiĝo de produktiveco... Tial mi skribis ĉi tiun artikolon.
Establi testan datumbazon
Por ĉi tiu komparo, mi kreis la datumbazon sur freŝa instalado de PostgreSQL 9.5 sur la $80-konstruaĵo Ubuntu 14.04 Post agordado de kelkaj parametroj en postgresql.conf mi ruligis skripto uzante psql. La sekvaj tabeloj estis kreitaj por prezenti la datumojn en EAV-formo:
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
);
Malsupre estas tabelo, kie la samaj datumoj estos konservitaj, sed kun atributoj en kolumno de tipo JSONB - proprietoj.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Aspektas multe pli simpla, ĉu ne? Tiam ĝi estis aldonita al la entabeloj (ento & ento_jsonb) 10 milionoj da registroj, kaj sekve, la tabelo estis plenigita per la samaj datumoj uzante la EAV-ŝablonon kaj la aliron kun JSONB-kolumno - entity_jsonb.properties. Tiel, ni ricevis plurajn malsamajn datumtipojn inter la tuta aro de propraĵoj. Ekzemplaj datumoj:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}Do nun ni havas la samajn datumojn por ambaŭ opcioj. Ni komencu kompari efektivigojn en la laboro!
Simpligu vian dezajnon
Antaŭe estis deklarite ke la datumbaza dezajno estis tre simpligita: unu tablo, uzante JSONB-kolumnon por trajtoj, anstataŭe de uzado de tri tabloj por EAV. Sed kiel tio speguliĝas en petoj? Ĝisdatigi unu entan posedaĵon aspektas jene:
-- 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;
Kiel vi povas vidi, la lasta peto ne aspektas pli simpla. Por ĝisdatigi la valoron de posedaĵo en JSONB-objekto ni devas uzi la funkcion , kaj devus pasi nian novan valoron kiel JSONB objekto. Tamen, ni ne bezonas scii ajnan identigilon antaŭe. Rigardante la ekzemplon de EAV, ni devas scii kaj la entity_id kaj la entity_attribute_id por plenumi la ĝisdatigon. Se vi volas ĝisdatigi posedaĵon en JSONB-kolumno bazita sur la objektonomo, tiam ĉio estas farita en unu simpla linio.
Nun ni elektu la enton, kiun ni ĵus ĝisdatigis laŭ ĝia nova koloro:
-- 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';
Mi pensas, ke ni povas konsenti, ke la dua estas pli mallonga (neniu kunigo!), kaj do pli legebla. JSONB gajnas ĉi tie! Ni uzas la JSON ->> operatoron por akiri la koloron kiel tekstan valoron de la JSONB-objekto. Ekzistas ankaŭ dua maniero atingi la saman rezulton en la JSONB-modelo uzante la @> funkciigiston:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Ĉi tio estas iom pli komplika: ni kontrolas ĉu la JSON-objekto en sia kolumno de proprietoj enhavas objekton, kiu estas dekstre de la operatoro @>. Malpli legebla, pli produktiva (vidu malsupre).
Ni plifaciligu uzadon de JSONB kiam vi devas elekti plurajn trajtojn samtempe. Jen kie la JSONB-aliro vere eniras: ni simple elektas ecojn kiel kromajn kolumnojn en nia rezulta aro sen la bezono de kunigoj:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
Kun EAV vi bezonos 2 kuniĝojn por ĉiu posedaĵo, kiun vi volas pridemandi. Laŭ mi, la supraj demandoj montras grandan simpligon en datumbaza dezajno. Vidu pliajn ekzemplojn pri kiel skribi demandojn al JSONB, ankaŭ eblaj en post.
Nun estas tempo paroli pri rendimento.
Produkteco
Por kompari rendimenton mi uzis en demandoj, por kalkuli ekzekuttempon. Ĉiu demando estis efektivigita almenaŭ tri fojojn ĉar la demandplanisto prenas pli longe la unuan fojon. Unue mi kuris la demandojn sen ajnaj indeksoj. Evidente, tio estis avantaĝo de JSONB, ĉar la kunigoj necesaj por EAV ne povis uzi indeksojn (fremdaj ŝlosilaj kampoj ne estis indeksitaj). Post tio mi kreis indekson sur la 2 fremdaj ŝlosilaj kolumnoj de la EAV-valora tabelo, kaj ankaŭ indekson. por JSONB-kolumno.
La datuma ĝisdatigo montris la sekvajn rezultojn laŭ tempo (en ms). Notu ke la skalo estas logaritma:

Ni vidas, ke JSONB estas multe (> 50000-x) pli rapida ol EAV se vi ne uzas indeksojn, pro la kialo deklarita supre. Kiam ni indeksas kolumnojn kun ĉefaj ŝlosiloj, la diferenco preskaŭ malaperas, sed JSONB ankoraŭ estas 1,3 fojojn pli rapida ol EAV. Notu, ke la indekso sur la kolumno JSONB ne efikas ĉi tie, ĉar ni ne uzas la kolumnon de proprieto en la kriterioj pri taksado.
Por elekti datumojn surbaze de proprietvaloro, ni ricevas la sekvajn rezultojn (normala skalo):

Vi povas rimarki, ke JSONB denove funkcias pli rapide ol EAV sen indeksoj, sed kiam EAV kun indeksoj, ĝi ankoraŭ funkcias pli rapide ol JSONB. Sed tiam mi vidis, ke la tempoj por JSONB-demandoj estis la samaj, tio instigis min al tio, ke GIN-indeksoj ne funkcias. Ŝajne, kiam vi uzas GIN-indekson sur kolumno kun plenigitaj propraĵoj, ĝi efektiviĝas nur kiam vi uzas la inkluzividan operatoron @>. Mi uzis ĉi tion en nova testo kaj ĝi havis grandegan efikon sur la tempo: nur 0,153ms! Ĉi tio estas 15000 25000 fojojn pli rapida ol EAV kaj XNUMX XNUMX fojojn pli rapide ol la ->> funkciigisto.
Mi pensas, ke ĝi estis sufiĉe rapida!
Grando de tabelo de datumbazo
Ni komparu la tabelgrandojn por ambaŭ aliroj. En psql ni povas montri la grandecon de ĉiuj tabeloj kaj indeksoj uzante la komandon dti+

Por la EAV-aliro, tablograndecoj estas proksimume 3068 MB kaj indeksoj ĝis 3427 MB por totalo de 6,43 GB. La JSONB-aliro uzas 1817 MB por la tabelo kaj 318 MB por la indeksoj, kio estas 2,08 GB. Ĝi rezultas 3 fojojn malpli! Ĉi tiu fakto surprizis min iomete ĉar ni konservas posedaĵnomojn en ĉiu JSONB-objekto.
Sed tamen, la nombroj parolas por si mem: en EAV ni stokas 2 entjerajn fremdajn ŝlosilojn per atributa valoro, rezultigante 8 bajtojn da pliaj datumoj. Aldone, EAV stokas ĉiujn posedaĵvalorojn kiel tekston, dum JSONB uzos numerajn kaj buleajn valorojn interne kie eble, rezultigante pli malgrandan spuron.
Rezultoj
Ĝenerale, mi pensas, ke konservado de entaj propraĵoj en JSONB-formato povas multe plifaciligi desegni kaj konservi vian datumbazon. Se vi faras multajn demandojn, tiam konservi ĉion en la sama tabelo kiel la ento efektive funkcios pli efike. Kaj la fakto, ke ĉi tio simpligas la interagadon inter datumoj, jam estas pluso, sed la rezulta datumbazo estas 3 fojojn pli malgranda en volumeno.
Ankaŭ, surbaze de la testoj faritaj, ni povas konkludi, ke la rendimentaj perdoj estas tre sensignifaj. En iuj kazoj, JSONB estas eĉ pli rapida ol EAV, igante ĝin eĉ pli bona. Tamen, ĉi tiu komparnormo kompreneble ne kovras ĉiujn aspektojn (ekz. entoj kun tre granda nombro da propraĵoj, signifa pliiĝo en la nombro da propraĵoj de ekzistantaj datumoj,...), do se vi havas sugestojn pri kiel plibonigi ilin. , bonvolu lasi en la komentoj!
fonto: www.habr.com
