Disodli EAV gyda JSONB yn PostgreSQL

TL; DR: Gall JSONB symleiddio datblygiad sgema cronfa ddata yn fawr heb aberthu perfformiad ymholiad.

Cyflwyniad

Gadewch i ni roi enghraifft glasurol o un o'r achosion defnydd hynaf yn ôl pob tebyg ym myd cronfa ddata berthynol (cronfa ddata): mae gennym endid, ac mae angen i ni arbed rhai eiddo (priodoleddau) yr endid hwn. Ond efallai na fydd gan bob achos yr un set o eiddo, ac efallai y bydd mwy o eiddo yn cael eu hychwanegu yn y dyfodol.

Y ffordd hawsaf o ddatrys y broblem hon yw creu colofn yn nhabl y gronfa ddata ar gyfer pob gwerth eiddo, a llenwi'r rhai sydd eu hangen ar gyfer enghraifft endid penodol. Gwych! Problem wedi'i datrys... nes bod eich tabl yn cynnwys miliynau o gofnodion a bod angen i chi ychwanegu cofnod newydd.

Ystyriwch y patrwm EAV (Endid-Priodoledd-Gwerth), mae'n digwydd yn eithaf aml. Mae un tabl yn cynnwys endidau (cofnodion), tabl arall yn cynnwys enwau eiddo (priodoleddau), ac mae trydydd tabl yn cysylltu endidau â'u priodoleddau ac yn cynnwys gwerth y priodoleddau hynny ar gyfer yr endid presennol. Mae hyn yn rhoi'r gallu i chi gael gwahanol setiau o briodweddau ar gyfer gwahanol wrthrychau, a hefyd ychwanegu eiddo ar y hedfan heb newid strwythur y gronfa ddata.

Fodd bynnag, ni fyddwn yn ysgrifennu'r post hwn pe na bai rhai anfanteision i'r dull EVA. Felly, er enghraifft, i gael un neu fwy o endidau sydd ag 1 priodoledd yr un, mae angen 2 uniad yn yr ymholiad: mae'r cyntaf yn uniad â'r tabl priodoleddau, mae'r ail yn uniad â'r tabl gwerthoedd. Os oes gan endid 2 nodwedd, yna mae angen 4 uniad! Yn ogystal, mae'r holl briodoleddau fel arfer yn cael eu storio fel llinynnau, sy'n arwain at gastio math ar gyfer y canlyniad a'r cymal LLE. Os byddwch yn ysgrifennu llawer o ymholiadau, yna mae hyn yn eithaf gwastraffus o ran defnyddio adnoddau.

Er gwaethaf y diffygion amlwg hyn, mae EAV wedi cael ei ddefnyddio ers tro i ddatrys y mathau hyn o broblemau. Roedd y rhain yn ddiffygion anochel, ac yn syml iawn nid oedd dewis arall gwell.
Ond yna ymddangosodd “technoleg” newydd yn PostgreSQL...

Gan ddechrau gyda PostgreSQL 9.4, ychwanegwyd y math data JSONB i storio data deuaidd JSON. Er bod storio JSON yn y fformat hwn fel arfer yn cymryd ychydig mwy o le ac amser na JSON testun plaen, mae perfformio gweithrediadau arno yn llawer cyflymach. Mae JSONB hefyd yn cefnogi mynegeio, sy'n gwneud ymholiadau hyd yn oed yn gyflymach.

Mae'r math o ddata JSONB yn caniatáu inni ddisodli'r patrwm EAV beichus trwy ychwanegu dim ond un golofn JSONB at ein tabl endid, gan symleiddio dyluniad cronfa ddata yn fawr. Ond mae llawer yn dadlau y dylai hyn gyd-fynd â gostyngiad mewn cynhyrchiant... Dyna pam ysgrifennais yr erthygl hon.

Sefydlu cronfa ddata prawf

Ar gyfer y gymhariaeth hon, creais y gronfa ddata ar osodiad newydd o PostgreSQL 9.5 ar yr adeilad $80 Ocean digidol Ubuntu 14.04. Ar ôl gosod rhai paramedrau yn postgresql.conf rhedais hyn sgript gan ddefnyddio psql. Crëwyd y tablau canlynol i gyflwyno’r data ar ffurf EAV:

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

Isod mae tabl lle bydd yr un data yn cael ei storio, ond gyda phriodoleddau mewn colofn o fath JSONB - eiddo.

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

Edrych yn llawer symlach, yn tydi? Yna cafodd ei ychwanegu at y tablau endid (endid & endid_jsonb) 10 miliwn o gofnodion, ac yn unol â hynny, llenwyd y tabl gyda'r un data gan ddefnyddio'r patrwm EAV a'r dull gyda cholofn JSONB - endid_jsonb.eiddo. Felly, cawsom sawl math gwahanol o ddata ymhlith y set gyfan o eiddo. Data enghreifftiol:

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

Felly nawr mae gennym yr un data ar gyfer y ddau opsiwn. Gadewch i ni ddechrau cymharu gweithrediadau yn y gwaith!

Symleiddiwch eich dyluniad

Dywedwyd yn flaenorol bod dyluniad y gronfa ddata wedi'i symleiddio'n fawr: un tabl, trwy ddefnyddio colofn JSONB ar gyfer eiddo, yn lle defnyddio tri thabl ar gyfer EAV. Ond sut mae hyn yn cael ei adlewyrchu mewn ceisiadau? Mae diweddaru eiddo un endid yn edrych fel hyn:

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

Fel y gwelwch, nid yw'r cais olaf yn edrych yn symlach. I ddiweddaru gwerth eiddo mewn gwrthrych JSONB mae'n rhaid i ni ddefnyddio'r ffwythiant jsonb_set(), a dylai basio ein gwerth newydd fel gwrthrych JSONB. Fodd bynnag, nid oes angen i ni wybod unrhyw ddynodwr ymlaen llaw. Gan edrych ar yr enghraifft EAV, mae angen i ni wybod yr endid_id a'r endid_attribute_id er mwyn perfformio'r diweddariad. Os ydych chi am ddiweddaru eiddo mewn colofn JSONB yn seiliedig ar enw'r gwrthrych, yna mae'r cyfan wedi'i wneud mewn un llinell syml.

Nawr, gadewch i ni ddewis yr endid rydyn ni newydd ei ddiweddaru yn seiliedig ar ei liw newydd:

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

Rwy'n meddwl y gallwn gytuno bod yr ail un yn fyrrach (dim ymuno!), ac felly'n fwy darllenadwy. JSONB yn ennill yma! Rydym yn defnyddio'r gweithredwr JSON ->> i gael y lliw fel gwerth testun o wrthrych JSONB. Mae yna hefyd ail ffordd i gyflawni'r un canlyniad ym model JSONB gan ddefnyddio'r gweithredwr @>:

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

Mae hyn ychydig yn fwy cymhleth: rydym yn gwirio i weld a yw'r gwrthrych JSON yn ei golofn eiddo yn cynnwys gwrthrych sydd i'r dde i'r gweithredwr @>. Llai darllenadwy, mwy cynhyrchiol (gweler isod).

Gadewch i ni wneud defnyddio JSONB hyd yn oed yn haws pan fydd angen i chi ddewis eiddo lluosog ar unwaith. Dyma lle mae dull JSONB yn dod i mewn mewn gwirionedd: yn syml, rydyn ni'n dewis eiddo fel colofnau ychwanegol yn ein set canlyniadau heb fod angen uniadau:

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

Gydag EAV bydd angen 2 uniad arnoch ar gyfer pob eiddo rydych am ei holi. Yn fy marn i, mae'r ymholiadau uchod yn dangos symleiddio mawr mewn dylunio cronfa ddata. Gweler mwy o enghreifftiau o sut i ysgrifennu ymholiadau JSONB, hefyd yn hyn post.
Nawr mae'n bryd siarad am berfformiad.

Cynhyrchiant

I gymharu perfformiad defnyddiais ESBONIAD DADANSODDIAD mewn ymholiadau, i gyfrifo amser gweithredu. Gweithredwyd pob ymholiad o leiaf deirgwaith oherwydd bod y cynlluniwr ymholiad yn cymryd mwy o amser y tro cyntaf. Yn gyntaf, rhedais yr ymholiadau heb unrhyw fynegeion. Yn amlwg, roedd hyn yn fantais i JSONB, gan na allai'r uniadau sydd eu hangen ar gyfer EAV ddefnyddio mynegeion (ni fynegwyd meysydd allweddol tramor). Ar ôl hyn creais fynegai ar y 2 golofn allwedd dramor yn y tabl gwerth EAV, yn ogystal â mynegai GIN am golofn JSONB.

Dangosodd y diweddariad data y canlyniadau canlynol o ran amser (mewn ms). Sylwch fod y raddfa yn logarithmig:

Disodli EAV gyda JSONB yn PostgreSQL

Gwelwn fod JSONB yn llawer (> 50000-x) yn gyflymach nag EAV os nad ydych yn defnyddio mynegeion, am y rheswm a nodir uchod. Pan fyddwn yn mynegeio colofnau ag allweddi cynradd, mae'r gwahaniaeth bron yn diflannu, ond mae JSONB yn dal i fod 1,3 gwaith yn gyflymach nag EAV. Sylwch nad yw'r mynegai ar y golofn JSONB yn cael unrhyw effaith yma gan nad ydym yn defnyddio'r golofn eiddo yn y meini prawf gwerthuso.

Ar gyfer dewis data yn seiliedig ar werth eiddo, rydym yn cael y canlyniadau canlynol (graddfa arferol):

Disodli EAV gyda JSONB yn PostgreSQL

Gallwch sylwi bod JSONB eto'n gweithio'n gyflymach nag EAV heb fynegeion, ond pan fydd EAV gyda mynegeion, mae'n dal i weithio'n gyflymach na JSONB. Ond yna gwelais fod yr amseroedd ar gyfer ymholiadau JSONB yr un fath, fe ysgogodd hyn fi at y ffaith nad yw mynegeion GIN yn gweithio. Mae'n debyg pan fyddwch chi'n defnyddio mynegai GIN ar golofn â phriodweddau poblog, dim ond wrth ddefnyddio'r gweithredwr cynnwys @> y mae'n dod i rym. Defnyddiais hwn mewn prawf newydd a chafodd effaith enfawr ar yr amser: dim ond 0,153ms! Mae hyn 15000 gwaith yn gyflymach nag EAV a 25000 gwaith yn gyflymach na'r gweithredwr ->>.

Rwy'n meddwl ei fod yn ddigon cyflym!

Maint tabl cronfa ddata

Gadewch i ni gymharu maint y tablau ar gyfer y ddau ddull. Yn psql gallwn ddangos maint yr holl dablau a mynegeion gan ddefnyddio'r gorchymyn dti+

Disodli EAV gyda JSONB yn PostgreSQL

Ar gyfer y dull EAV, mae meintiau tablau tua 3068 MB a mynegeion hyd at 3427 MB ar gyfer cyfanswm o 6,43 GB. Mae dull JSONB yn defnyddio 1817 MB ar gyfer y tabl a 318 MB ar gyfer y mynegeion, sef 2,08 GB. Mae'n troi allan 3 gwaith yn llai! Roedd y ffaith hon yn fy synnu ychydig oherwydd ein bod yn storio enwau eiddo ym mhob gwrthrych JSONB.

Ond o hyd, mae'r niferoedd yn siarad drostynt eu hunain: yn EAV rydym yn storio 2 allwedd gyfanrif tramor fesul gwerth priodoledd, gan arwain at 8 beit o ddata ychwanegol. Yn ogystal, mae EAV yn storio pob gwerth eiddo fel testun, tra bydd JSONB yn defnyddio gwerthoedd rhifol a boolaidd yn fewnol lle bo modd, gan arwain at ôl troed llai.

Canlyniadau

Ar y cyfan, rwy'n credu y gall arbed eiddo endid ar ffurf JSONB ei gwneud hi'n llawer haws dylunio a chynnal eich cronfa ddata. Os ydych chi'n rhedeg llawer o ymholiadau, yna bydd cadw popeth yn yr un tabl â'r endid yn gweithio'n fwy effeithlon mewn gwirionedd. Ac mae'r ffaith bod hyn yn symleiddio'r rhyngweithio rhwng data eisoes yn fantais, ond mae'r gronfa ddata sy'n deillio o hynny 3 gwaith yn llai o ran cyfaint.

Hefyd, yn seiliedig ar y profion a gyflawnwyd, gallwn ddod i'r casgliad bod y colledion perfformiad yn ddibwys iawn. Mewn rhai achosion, mae JSONB hyd yn oed yn gyflymach nag EAV, gan ei wneud hyd yn oed yn well. Fodd bynnag, wrth gwrs, nid yw'r meincnod hwn yn cwmpasu pob agwedd (e.e. endidau sydd â nifer fawr iawn o eiddo, cynnydd sylweddol yn nifer eiddo'r data presennol,...), felly os oes gennych unrhyw awgrymiadau ar sut i'w gwella , mae croeso i chi adael yn y sylwadau!

Ffynhonnell: hab.com

Ychwanegu sylw