ErsÀtter EAV med JSONB i PostgreSQL

TL; DR: JSONB kan avsevÀrt förenkla utvecklingen av databasschema utan att offra frÄgeprestanda.

Inledning

LÄt oss ge ett klassiskt exempel pÄ förmodligen ett av de Àldsta anvÀndningsfallen i vÀrlden av en relationsdatabas (databas): vi har en entitet, och vi mÄste spara vissa egenskaper (attribut) för denna entitet. Men alla instanser kanske inte har samma uppsÀttning egenskaper, och fler egenskaper kan lÀggas till i framtiden.

Det enklaste sÀttet att lösa detta problem Àr att skapa en kolumn i databastabellen för varje egenskapsvÀrde och helt enkelt fylla i de som behövs för en specifik entitetsinstans. Bra! Problem löst... tills din tabell innehÄller miljontals poster och du behöver lÀgga till en ny post.

TÀnk pÄ EAV-mönstret (Entitet-Attribut-VÀrde), förekommer det ganska ofta. En tabell innehÄller entiteter (poster), en annan tabell innehÄller egenskapsnamn (attribut) och en tredje tabell associerar enheter med deras attribut och innehÄller vÀrdet av dessa attribut för den aktuella enheten. Detta ger dig möjligheten att ha olika uppsÀttningar egenskaper för olika objekt, och Àven lÀgga till egenskaper i farten utan att Àndra databasstrukturen.

Jag skulle dock inte skriva det hÀr inlÀgget om det inte fanns nÄgra nackdelar med EVA-metoden. SÄ, till exempel, för att fÄ en eller flera entiteter som har 1 attribut vardera, krÀvs 2 joins i frÄgan: den första Àr en join med attributtabellen, den andra Àr en join med vÀrdetabellen. Om en entitet har 2 attribut behövs 4 kopplingar! Dessutom lagras alla attribut vanligtvis som strÀngar, vilket resulterar i typcasting för bÄde resultatet och WHERE-satsen. Om du skriver mÄnga frÄgor Àr detta ganska slösaktigt nÀr det gÀller resursanvÀndning.

Trots dessa uppenbara brister har EAV lÀnge anvÀnts för att lösa den hÀr typen av problem. Dessa var oundvikliga brister, och det fanns helt enkelt inget bÀttre alternativ.
Men sÄ dök en ny "teknik" upp i PostgreSQL...

FrĂ„n och med PostgreSQL 9.4 lades JSONB-datatypen till för att lagra binĂ€r JSON-data. Även om lagring av JSON i det hĂ€r formatet vanligtvis tar lite mer utrymme och tid Ă€n JSON med vanlig text, Ă€r det mycket snabbare att utföra operationer pĂ„ det. JSONB stöder Ă€ven indexering, vilket gör frĂ„gorna Ă€nnu snabbare.

JSONB-datatypen tillÄter oss att ersÀtta det krÄngliga EAV-mönstret genom att lÀgga till bara en JSONB-kolumn i vÄr entitetstabell, vilket avsevÀrt förenklar databasdesign. Men mÄnga hÀvdar att detta borde Ätföljas av en minskning av produktiviteten... Det var dÀrför jag skrev den hÀr artikeln.

Skapa en testdatabas

För denna jÀmförelse skapade jag databasen pÄ en ny installation av PostgreSQL 9.5 pÄ $80 build DigitalOcean Ubuntu 14.04 Efter att ha konfigurerat nÄgra parametrar i postgresql.conf körde jag detta skript med psql. Följande tabeller skapades för att presentera data i EAV-form:

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

Nedan finns en tabell dĂ€r samma data kommer att lagras, men med attribut i en kolumn av JSONB-typ – egenskaper.

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

Ser mycket enklare ut, eller hur? Sedan lades det till i entitetstabellerna (enhet & entity_jsonb) 10 miljoner poster, och följaktligen fylldes tabellen med samma data med hjÀlp av EAV-mönstret och tillvÀgagÄngssÀttet med en JSONB-kolumn - entity_jsonb.properties. SÄledes fick vi flera olika datatyper bland hela uppsÀttningen av egenskaper. Exempeldata:

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

SÄ nu har vi samma data för bÄda alternativen. LÄt oss börja jÀmföra implementeringar pÄ jobbet!

Förenkla din design

Det har tidigare sagts att databasdesignen förenklades kraftigt: en tabell, genom att anvÀnda en JSONB-kolumn för egenskaper, istÀllet för att anvÀnda tre tabeller för EAV. Men hur Äterspeglas detta i förfrÄgningar? Att uppdatera en enhetsegenskap ser ut sÄ hÀr:

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

Som du kan se ser den sista begĂ€ran inte enklare ut. För att uppdatera vĂ€rdet pĂ„ en egenskap i ett JSONB-objekt mĂ„ste vi anvĂ€nda funktionen jsonb_set(), och bör skicka vĂ„rt nya vĂ€rde som ett JSONB-objekt. Vi behöver dock inte kĂ€nna till nĂ„gon identifierare i förvĂ€g. Om vi ​​tittar pĂ„ EAV-exemplet mĂ„ste vi kĂ€nna till bĂ„de entity_id och entity_attribute_id för att kunna utföra uppdateringen. Om du vill uppdatera en egenskap i en JSONB-kolumn baserat pĂ„ objektnamnet, görs allt pĂ„ en enkel rad.

LÄt oss nu vÀlja den enhet vi just uppdaterade baserat pÄ dess nya fÀrg:

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

Jag tror att vi kan komma överens om att den andra Àr kortare (ingen anslutning!), och dÀrför mer lÀsbar. JSONB vinner hÀr! Vi anvÀnder operatorn JSON ->> för att fÄ fÀrgen som ett textvÀrde frÄn ett JSONB-objekt. Det finns ocksÄ ett andra sÀtt att uppnÄ samma resultat i JSONB-modellen med @>-operatorn:

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

Det hÀr Àr lite mer komplicerat: vi kontrollerar om JSON-objektet i egenskapskolumnen innehÄller ett objekt som Àr till höger om @>-operatorn. Mindre lÀsbar, mer produktiv (se nedan).

LÄt oss göra det Ànnu enklare att anvÀnda JSONB nÀr du behöver vÀlja flera egenskaper samtidigt. Det Àr hÀr som JSONB-metoden verkligen kommer in: vi vÀljer helt enkelt egenskaper som ytterligare kolumner i vÄr resultatuppsÀttning utan behov av kopplingar:

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

Med EAV behöver du 2 anslutningar för varje fastighet du vill frÄga. Enligt min Äsikt visar ovanstÄende frÄgor en stor förenkling i databasdesign. Se fler exempel pÄ hur man skriver JSONB-frÄgor, Àven i detta posta.
Nu Àr det dags att prata om prestation.

ĐŸŃ€ĐŸĐžĐ·ĐČĐŸĐŽĐžŃ‚Đ”Đ»ŃŒĐœĐŸŃŃ‚ŃŒ

För att jĂ€mföra prestanda anvĂ€nde jag FÖRKLARA ANALYSERA i frĂ„gor, för att berĂ€kna exekveringstiden. Varje frĂ„ga kördes minst tre gĂ„nger eftersom frĂ„geplaneraren tar lĂ€ngre tid första gĂ„ngen. Först körde jag frĂ„gorna utan nĂ„gra index. Uppenbarligen var detta en fördel med JSONB, eftersom de kopplingar som krĂ€vs för EAV inte kunde anvĂ€nda index (utlĂ€ndska nyckelfĂ€lt indexerades inte). Efter detta skapade jag ett index pĂ„ de tvĂ„ frĂ€mmande nyckelkolumnerna i EAV-vĂ€rdestabellen, samt ett index GIN för en JSONB-kolumn.

Datauppdateringen visade följande resultat i termer av tid (i ms). Observera att skalan Àr logaritmisk:

ErsÀtter EAV med JSONB i PostgreSQL

Vi ser att JSONB Àr mycket (> 50000-x) snabbare Àn EAV om du inte anvÀnder index, av anledningen som anges ovan. NÀr vi indexerar kolumner med primÀrnycklar försvinner nÀstan skillnaden, men JSONB Àr fortfarande 1,3 gÄnger snabbare Àn EAV. Observera att indexet pÄ JSONB-kolumnen inte har nÄgon effekt hÀr eftersom vi inte anvÀnder egenskapskolumnen i utvÀrderingskriterierna.

För att vÀlja data baserat pÄ egenskapsvÀrde fÄr vi följande resultat (normal skala):

ErsÀtter EAV med JSONB i PostgreSQL

Du kan mÀrka att JSONB Äterigen fungerar snabbare Àn EAV utan index, men nÀr EAV med index fungerar det fortfarande snabbare Àn JSONB. Men sedan sÄg jag att tiderna för JSONB-frÄgor var desamma, detta fick mig att inse att GIN-index inte fungerar. Tydligen nÀr du anvÀnder ett GIN-index pÄ en kolumn med ifyllda egenskaper, trÀder det bara i kraft nÀr du anvÀnder include-operatorn @>. Jag anvÀnde detta i ett nytt test och det hade en enorm inverkan pÄ tiden: bara 0,153ms! Detta Àr 15000 25000 gÄnger snabbare Àn EAV och XNUMX XNUMX gÄnger snabbare Àn ->> operatören.

Jag tycker det var tillrÀckligt snabbt!

Databastabellstorlek

LÄt oss jÀmföra tabellstorlekarna för bÄda tillvÀgagÄngssÀtten. I psql kan vi visa storleken pÄ alla tabeller och index med kommandot dti+

ErsÀtter EAV med JSONB i PostgreSQL

För EAV-metoden Àr tabellstorlekarna cirka 3068 MB och indexerar upp till 3427 MB för totalt 6,43 GB. JSONB-metoden anvÀnder 1817 MB för tabellen och 318 MB för indexen, vilket Àr 2,08 GB. Det blir 3 gÄnger mindre! Detta faktum förvÄnade mig lite eftersom vi lagrar fastighetsnamn i varje JSONB-objekt.

Men ÀndÄ talar siffrorna för sig sjÀlva: i EAV lagrar vi 2 heltals frÀmmande nycklar per attributvÀrde, vilket resulterar i 8 byte med ytterligare data. Dessutom lagrar EAV alla egenskapsvÀrden som text, medan JSONB kommer att anvÀnda numeriska och booleska vÀrden internt dÀr det Àr möjligt, vilket resulterar i ett mindre fotavtryck.

Resultat av

Sammantaget tror jag att att spara entitetsegenskaper i JSONB-format kan göra design och underhÄll av din databas mycket enklare. Om du kör mÄnga frÄgor kommer det att fungera mer effektivt om du hÄller allt i samma tabell som enheten. Och det faktum att detta förenklar interaktionen mellan data Àr redan ett plus, men den resulterande databasen Àr 3 gÄnger mindre i volym.

Baserat pÄ utförda tester kan vi ocksÄ dra slutsatsen att prestationsförlusterna Àr mycket obetydliga. I vissa fall Àr JSONB Ànnu snabbare Àn EAV, vilket gör den Ànnu bÀttre. Men detta riktmÀrke tÀcker naturligtvis inte alla aspekter (t.ex. enheter med ett mycket stort antal egenskaper, en betydande ökning av antalet egenskaper för befintliga data,...), sÄ om du har nÄgra förslag pÄ hur man kan förbÀttra dem , lÀmna gÀrna i kommentarerna!

KĂ€lla: will.com

Köp pĂ„litlig hosting för webbplatser med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster