Byte av EAV till JSONB i PostgreSQL

TL; DR: JSONB kan avsevÀrt förenkla utvecklingen av databasstrukturer utan att kompromissa med prestanda i frÄgorna.

Introduktion

LÄt oss ge ett klassiskt exempel, troligen en av de Àldsta anvÀndningarna i vÀrlden av relationsdatabaser (databaser): vi har en entitet och behöver spara specifika egenskaper (attribut) för denna entitet. Men inte alla instanser kan ha samma uppsÀttning egenskaper, och dessutom kan det i framtiden finnas behov av att lÀgga till fler egenskaper.

Det enklaste sÀttet att lösa detta problem Àr att skapa en kolumn i databastabellen för varje vÀrde av egenskapen och helt enkelt fylla i de som behövs för den specifika instansen av entiteten. UtmÀrkt! Problemet löst... tills dess att din tabell innehÄller miljontals poster och du plötsligt behöver lÀgga till en ny post.

LÄt oss övervÀga EAV-mönstret (Entity-Attribute-Value), som förekommer ganska ofta. En tabell innehÄller entiteter (poster), en annan tabell innehÄller namnen pÄ egenskaper (attribut), och en tredje tabell kopplar entiteter till deras attribut och innehÄller vÀrdet av dessa attribut för den aktuella entiteten. Detta ger dig möjlighet att ha olika uppsÀttningar egenskaper för olika objekt, samt att lÀgga till egenskaper "on the fly" utan att förÀndra databasstrukturen.

Trots detta skulle jag inte ha skrivit denna anteckning om det inte fanns nackdelar med att anvĂ€nda EAV. Till exempel, för att hĂ€mta en eller flera entiteter som har 1 attribut, krĂ€vs det 2 joinar i frĂ„gan: den första – en sammanslagning med attributtabellen, den andra – en sammanslagning med vĂ€rdestabellen. Om entiteten har 2 attribut krĂ€vs det redan 4 joinar! Dessutom lagras alla attribut vanligtvis som strĂ€ngar, vilket leder till typkonvertering bĂ„de för resultatet och för WHERE-villkoret. Om du skriver mĂ„nga frĂ„gor Ă€r det ganska resurskrĂ€vande.

Trots dessa uppenbara nackdelar, har EAV lÀnge anvÀnts för att lösa den hÀr typen av problem. Det har varit oundvikliga nackdelar, och det har helt enkelt inte funnits nÄgon bÀttre alternativ.
Men sedan dök en ny "teknologi" upp i PostgreSQL...

Sedan PostgreSQL 9.4 lades datatypen JSONB till för att lagra binĂ€ra JSON-data. Även om lagring av JSON i detta format vanligtvis tar lite mer utrymme och tid jĂ€mfört med enkel textbaserad JSON, gĂ„r operationer med det mycket snabbare. JSONB stöder ocksĂ„ indexering, vilket gör förfrĂ„gningar Ă€nnu snabbare.

Datatypen JSONB möjliggör att vi kan ersÀtta det omfattande EAV-mönstret genom att lÀgga till endast en JSONB-kolumn i vÄr entitetstabell, vilket förenklar databasdesignen avsevÀrt. Men mÄnga hÀvdar att detta bör Ätföljas av prestandanedgÄng... Det Àr just hÀr denna artikel kommer in.

InstÀllning av testdatabas

För denna jÀmförelse skapade jag en databas pÄ en ny installation av PostgreSQL 9.5 pÄ en 80-dollarsuppsÀttning DigitalOcean Ubuntu 14.04. Efter att ha justerat vissa parametrar i postgresql.conf körde jag det hÀr ett skript med hjÀlp av psql. För att representera data i EAV-format skapades följande tabeller:

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 JSONB-kolumn – properties.

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

Ser mycket enklare ut, eller hur? Sedan lades 10 miljoner poster till i entitetstabellerna (entity & entity_jsonb) och motsvarande fyllts med samma data i tabellerna dĂ€r EAV-mönstret anvĂ€nds och tillvĂ€gagĂ„ngssĂ€ttet med JSONB-kolumnen – 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"
    length:       120
    width:        3.1882420
    hassomething: true
    country:      "Belgium"
  } 
}

SÄ, nu har vi samma data för tvÄ alternativ. LÄt oss börja jÀmföra implementationerna i arbete!

Förenkling av design

Det har redan nÀmnts att databasdesignen har förenklats avsevÀrt: en tabell, tack vare anvÀndningen av en JSONB-kolumn för egenskaper, istÀllet för att anvÀnda tre tabeller för EAV. Men hur pÄverkar detta förfrÄgningarna? Uppdatering av en egenskap för en entitet ser ut som följer:

-- EAV
UPDATE entity_attribute_value 
SET value = 'blÄ' 
WHERE entity_attribute_id = 1 
  AND entity_id = 120;

-- JSONB
UPDATE entity_jsonb 
SET properties = jsonb_set(properties, '{"color"}', '"blÄ"') 
WHERE id = 120;

Som vi ser, ser inte den sista frĂ„gan enklare ut. För att uppdatera egenskapsvĂ€rdet i JSONB-objektet mĂ„ste vi anvĂ€nda funktionen jsonb_set(), och vi mĂ„ste skicka vĂ„rt nya vĂ€rde som ett JSONB-objekt. Å andra sidan behöver vi inte veta nĂ„got identifierare i förvĂ€g. Tittar vi pĂ„ exemplet med EAV behöver vi bĂ„de entity_id och entity_attribute_id för att utföra uppdateringen. Om du vill uppdatera en egenskap i JSONB-kolumnen baserat pĂ„ objektets namn, sĂ„ görs detta med en enkel rad.

LÄt oss nu vÀlja den entitet 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 = 'blÄ';

-- JSONB
SELECT name 
FROM entity_jsonb 
WHERE properties ->> 'color' = 'blÄ';

Jag tror vi kan enas om att det andra Àr kortare (utan join!), och dÀrmed mer lÀsbart. HÀr vinner JSONB! Vi anvÀnder JSON-operatören ->> för att fÄ fÀrgen som ett textvÀrde frÄn JSONB-objektet. Det finns ocksÄ ett andra sÀtt att uppnÄ samma resultat i JSONB-modellen med operatören @> :

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

Detta Àr lite mer komplicerat: vi kontrollerar om JSON-objektet i kolumnens egenskaper innehÄller objektet som finns till höger om operatören @>. Mindre lÀsbart, mer effektivt (se nedan).

LÄt oss förenkla anvÀndningen av JSONB Ànnu mer nÀr du behöver vÀlja flera egenskaper samtidigt. HÀr passar verkligen JSONB-ansatsen: vi vÀljer helt enkelt egenskaper som extra kolumner i vÄr resultatuppsÀttning utan behov av joins:

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

Med EAV behöver du 2 joins för varje egenskap som du vill frÄga. Enligt min mening visar ovanstÄende frÄgor en stor förenkling i databasdesignen. Du kan Àven se fler exempel pÄ hur man skriver frÄgor mot JSONB i detta inlÀgget.
Nu Àr det dags att prata om prestanda.

Performance

För att jÀmföra prestanda anvÀnde jag EXPLAIN ANALYZE i förfrÄgningar, för att berÀkna exekveringstiden. Varje förfrÄgan kördes minst tre gÄnger, eftersom planeringsverktyget krÀver mer tid första gÄngen. Först utförde jag förfrÄgningarna utan nÄgra index. Uppenbarligen gav detta JSONB en fördel, eftersom joins som behövs för EAV inte kunde anvÀnda index (fÀlten med frÀmmande nycklar var inte indexerade). Efter detta skapade jag ett index för tvÄ kolumner av frÀmmande nycklar i EAV-vÀrdestabellen, samt ett index GIN för JSONB-kolumnen.

Uppdateringar av data visade följande resultat för tiden (i ms). Observera att skalan Àr logaritmisk:

Byte av EAV till JSONB i PostgreSQL

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

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

Byte av EAV till JSONB i PostgreSQL

Det Àr tydligt att JSONB Äterigen fungerar snabbare Àn EAV utan index, men nÀr EAV har index Àr den fortfarande snabbare Àn JSONB. Men sedan sÄg jag att tiden för JSONB-förfrÄgningarna var densamma, vilket ledde mig till insikten att GIN-indexet inte aktiveras. Uppenbarligen, nÀr du anvÀnder GIN-index för en kolumn med fyllda egenskaper, fungerar det bara vid anvÀndning av inklusionsoperatorn @>. Jag anvÀnde detta i ett nytt test, vilket hade en enorm pÄverkan pÄ tiden: endast 0,153 ms! Det Àr 15000 gÄnger snabbare Àn EAV och 25000 gÄnger snabbare Àn operatorn ->>.

Jag tycker att det var tillrÀckligt snabbt!

Storleken pÄ databastabeller

LÄt oss jÀmföra storlekarna pÄ tabellerna med bÄda metoderna. I psql kan vi visa storleken för alla tabeller och index genom att anvÀnda kommandot dti+

Byte av EAV till JSONB i PostgreSQL

För EAV-metoden Àr storleken pÄ tabellerna cirka 3068 MB och indexen upp till 3427 MB, vilket ger totalt 6,43 GB. Vid anvÀndning av JSONB-metoden anvÀnds 1817 MB för tabellen och 318 MB för indexen, vilket innebÀr 2,08 GB. Det blir tre gÄnger mindre! Denna siffra överraskade mig nÄgot, eftersom vi lagrar namnen pÄ egenskaperna i varje JSONB-objekt.

Men siffrorna talar för sig sjÀlva: i EAV lagrar vi 2 heltal externa nycklar för attributets vÀrde, vilket resulterar i 8 byte ytterligare data. Dessutom lagras alla egenskapsvÀrden i EAV i text, medan JSONB kommer att anvÀnda numeriska och logiska vÀrden inom de omrÄden dÀr det Àr möjligt, vilket ger en mindre datastorlek.

Sammanfattning

Generellt sett anser jag att lagringen av entitets egenskaper i JSONB-formatet kan förenkla design och underhÄll av din databas avsevÀrt. Om du utför mÄnga frÄgor, kommer allt som lagras i en tabell med entiteten verkligen att fungera mer effektivt. Och det faktum att det underlÀttar interaktionen mellan data Àr redan en fördel, men den resulterande databasen Àr tre gÄnger mindre i storlek.

Dessutom, baserat pÄ genomförda tester kan slutsatsen dras att prestandaförlusterna Àr mycket obetydliga. I vissa fall fungerar JSONB till och med snabbare Àn EAV, vilket gör det Ànnu bÀttre. Men detta referenstest tÀcker givetvis inte alla aspekter (t.ex. entiteter med mycket stora egenskaper, en betydande ökning av antalet egenskaper för existerande data,...), sÄ om du har nÄgra förslag pÄ hur de kan förbÀttras, tveka inte att lÀmna en kommentar!

KĂ€lla: habr.com

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