Замена на EAV со JSONB во PostgreSQL

TL; DR: JSONB може во голема мера да го поедностави развојот на шемата на базата на податоци без да ги жртвува перформансите на барањето.

Вовед

Да дадеме класичен пример за веројатно еден од најстарите случаи на употреба во светот на релациона база на податоци (база на податоци): имаме ентитет и треба да зачуваме одредени својства (атрибути) на овој ентитет. Но, не сите примероци може да имаат ист сет на својства и може да се додадат повеќе својства во иднина.

Најлесен начин да се реши овој проблем е да се создаде колона во табелата на базата на податоци за секоја вредност на имотот и едноставно да се пополнат оние што се потребни за одреден пример на ентитет. Одлично! Проблемот е решен... додека вашата табела не содржи милиони записи и не треба да додадете нов запис.

Размислете за моделот EAV (Ентитет-Атрибут-Вредност), се јавува доста често. Една табела содржи ентитети (записи), друга табела содржи имиња на својства (атрибути), а трета табела ги поврзува ентитетите со нивните атрибути и ја содржи вредноста на тие атрибути за тековниот ентитет. Ова ви дава можност да имате различни множества на својства за различни објекти, а исто така да додавате својства во лет без да ја менувате структурата на базата на податоци.

Сепак, јас не би го пишувал овој пост доколку нема некои негативни страни на пристапот EVA. Така, на пример, за да се добие еден или повеќе ентитети кои имаат по 1 атрибут, потребни се 2 спојувања во барањето: првото е спојување со табелата со атрибути, вториот е спојување со табелата со вредности. Ако еден ентитет има 2 атрибути, тогаш се потребни 4 спојувања! Дополнително, сите атрибути обично се складираат како низи, што резултира со кастинг на типот и за резултатот и за клаузулата WHERE. Ако пишувате многу прашања, тогаш ова е прилично непотребно во однос на користењето на ресурсите.

И покрај овие очигледни недостатоци, EAV долго време се користи за решавање на овој тип на проблеми. Тоа беа неизбежни недостатоци и едноставно немаше подобра алтернатива.
Но, тогаш се појави нова „технологија“ во PostgreSQL...

Почнувајќи со PostgreSQL 9.4, типот на податоци JSONB беше додаден за складирање на JSON бинарни податоци. Иако складирањето на JSON во овој формат обично одзема малку повеќе простор и време од обичен текст JSON, извршувањето на операциите на него е многу побрзо. JSONB поддржува и индексирање, што ги прави барањата уште побрзи.

Типот на податоци JSONB ни овозможува да ја замениме незгодната шема EAV со додавање на само една колона JSONB на нашата ентитетска табела, што значително го поедноставува дизајнот на базата на податоци. Но, многумина тврдат дека ова треба да биде придружено со намалување на продуктивноста... Затоа ја напишав оваа статија.

Поставување тест база на податоци

За оваа споредба, ја создадов базата на податоци за нова инсталација на PostgreSQL 9.5 на верзијата од 80 долари Дигитален океан Ubuntu 14.04. Откако поставив некои параметри во postgresql.conf, истрчав овој скрипта користејќи psql. Следниве табели беа креирани за да се претстават податоците во форма на 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
);

Подолу е табела каде што ќе се складираат истите податоци, но со атрибути во колона од типот JSONB - својства.

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

Изгледа многу поедноставно, нели? Потоа беше додаден во табелите на ентитетите (лице & entity_jsonb) 10 милиони записи, и соодветно на тоа, табелата беше пополнета со истите податоци користејќи ја шемата EAV и пристапот со колона JSONB - entity_jsonb.својства. Така, добивме неколку различни типови на податоци меѓу целиот сет на својства. Примерок на податоци:

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

Така, сега имаме исти податоци за двете опции. Да почнеме да ги споредуваме имплементациите на работа!

Поедноставете го вашиот дизајн

Претходно беше наведено дека дизајнот на базата на податоци е значително поедноставен: една табела, со користење на колона JSONB за својства, наместо да се користат три табели за EAV. Но, како тоа се рефлектира во барањата? Ажурирањето на својството на еден ентитет изгледа вака:

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

Како што можете да видите, последното барање не изгледа поедноставно. За да ја ажурираме вредноста на имотот во објектот JSONB, мора да ја користиме функцијата jsonb_set (), и треба да ја пренесе нашата нова вредност како JSONB објект. Сепак, не треба однапред да знаеме никаков идентификатор. Гледајќи го примерот EAV, треба да ги знаеме и entity_id и entity_attribute_id за да го извршиме ажурирањето. Ако сакате да ажурирате својство во колона JSONB врз основа на името на објектот, тогаш сето тоа е направено во една едноставна линија.

Сега да го избереме ентитетот што штотуку го ажуриравме врз основа на неговата нова боја:

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

Мислам дека можеме да се согласиме дека вториот е пократок (не се придружува!), и затоа е почитлив. JSONB победува овде! Го користиме операторот JSON ->> за да ја добиеме бојата како текстуална вредност од објектот JSONB. Исто така, постои втор начин да се постигне истиот резултат во моделот JSONB со помош на операторот @>:

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

Ова е малку покомплицирано: проверуваме дали објектот JSON во колоната со својства содржи објект што е десно од операторот @>. Помалку читлив, попродуктивен (види подолу).

Ајде да го олесниме користењето на JSONB кога треба да изберете повеќе својства одеднаш. Тука навистина доаѓа пристапот JSONB: ние едноставно избираме својства како дополнителни колони во нашиот сет на резултати без потреба од спојувања:

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

Со EAV ќе ви требаат 2 спојувања за секој имот што сакате да го побарате. Според мое мислење, горенаведените прашања покажуваат големо поедноставување во дизајнот на базата на податоци. Погледнете повеќе примери за тоа како да пишувате JSONB барања, исто така во ова пост.
Сега е време да се зборува за перформансите.

Перформанси

За да ги споредам перформансите користев ОБЈАСНИ АНАЛИЗИРАЈ во пребарувања, да се пресмета времето на извршување. Секое барање беше извршено најмалку три пати бидејќи на планерот за барање му треба подолго време првиот пат. Прво ги истрчав прашањата без никакви индекси. Очигледно, ова беше предност на JSONB, бидејќи приклучоците потребни за EAV не можеа да користат индекси (полињата со странски клучеви не беа индексирани). После ова, создадов индекс на 2-те странски клучеви колони од табелата со вредности на EAV, како и индекс ГИН за колона JSONB.

Ажурирањето на податоците ги покажа следните резултати во однос на времето (во ms). Забележете дека скалата е логаритамска:

Замена на EAV со JSONB во PostgreSQL

Гледаме дека JSONB е многу (> 50000-x) побрз од EAV ако не користите индекси, поради причината наведена погоре. Кога ги индексираме колоните со примарни клучеви, разликата речиси исчезнува, но JSONB е сепак 1,3 пати побрз од EAV. Имајте предвид дека индексот на колоната JSONB нема ефект овде бидејќи не ја користиме колоната за својства во критериумите за оценување.

За избор на податоци врз основа на вредноста на имотот, ги добиваме следните резултати (нормална скала):

Замена на EAV со JSONB во PostgreSQL

Може да забележите дека JSONB повторно работи побрзо од EAV без индекси, но кога EAV со индекси, сепак работи побрзо од JSONB. Но, тогаш видов дека времињата за JSONB прашања беа исти, ова ме поттикна на фактот дека индексите GIN не работат. Очигледно кога користите GIN индекс на колона со пополнети својства, тој стапува на сила само кога го користите операторот за вклучување @>. Го користев ова во нов тест и имаше огромно влијание врз времето: само 0,153 ms! Ова е 15000 пати побрзо од EAV и 25000 пати побрзо од ->> операторот.

Мислам дека беше доволно брзо!

Големина на табела со база на податоци

Ајде да ги споредиме големините на табелите за двата пристапа. Во psql можеме да ја прикажеме големината на сите табели и индекси користејќи ја командата dti+

Замена на EAV со JSONB во PostgreSQL

За пристапот EAV, големини на табелите се околу 3068 MB и индекси до 3427 MB за вкупно 6,43 GB. Пристапот JSONB користи 1817 MB за табелата и 318 MB за индексите, што е 2,08 GB. Излегува 3 пати помалку! Овој факт малку ме изненади бидејќи ги чуваме имињата на имотот во секој JSONB објект.

Но, сепак, бројките зборуваат сами за себе: во EAV складираме 2 цели броеви странски клучеви по вредност на атрибутот, што резултира со 8 бајти дополнителни податоци. Дополнително, EAV ги зачувува сите вредности на имотот како текст, додека JSONB ќе користи внатрешно нумерички и булови вредности каде што е можно, што ќе резултира со помал отпечаток.

Резултатите од

Генерално, мислам дека зачувувањето на својствата на ентитетите во формат JSONB може да го олесни дизајнирањето и одржувањето на вашата база на податоци. Ако извршувате многу прашања, тогаш чувањето на сè во истата табела со ентитетот всушност ќе работи поефикасно. И фактот дека ова ја поедноставува интеракцијата помеѓу податоците е веќе плус, но добиената база на податоци е 3 пати помала во обем.

Исто така, врз основа на извршените тестови, можеме да заклучиме дека загубите во перформансите се многу незначителни. Во некои случаи, JSONB е дури и побрз од EAV, што го прави уште подобар. Сепак, овој репер, се разбира, не ги опфаќа сите аспекти (на пример, ентитети со многу голем број својства, значително зголемување на бројот на својства на постоечки податоци,...), па ако имате какви било предлози како да ги подобрите , Ве молиме слободно оставете ги во коментарите!

Извор: www.habr.com

Додадете коментар