Substituíndo EAV por JSONB en PostgreSQL

En resumo: JSONB pode simplificar enormemente o desenvolvemento de esquemas de bases de datos sen sacrificar o rendemento das consultas.

Introdución

Tomemos un exemplo clásico, probablemente un dos casos de uso máis antigos no mundo das bases de datos relacionais: temos unha entidade e necesitamos almacenar certas propiedades (atributos) desta entidade. Pero non todas as instancias poden ter o mesmo conxunto de propiedades e poden engadirse máis propiedades no futuro.

A solución máis sinxela para este problema é crear unha columna na táboa da base de datos para cada valor de propiedade e simplemente completar as necesarias para unha instancia de entidade específica. Xenial! Problema resolto... ata que a táboa conteña millóns de rexistros e precise engadir un novo rexistro.

Consideremos o patrón EAV (Entidade-Atributo-Valor), é bastante común. Unha táboa contén entidades (rexistros), outra táboa contén nomes de propiedades (atributos) e unha terceira táboa vincula entidades aos seus atributos e contén os valores destes atributos para a entidade actual. Isto permite ter diferentes conxuntos de propiedades para diferentes obxectos, así como engadir propiedades sobre a marcha, sen modificar a estrutura da base de datos.

Non obstante, non estaría escribindo esta publicación se non houbese algúns inconvenientes na estratexia EVA. Por exemplo, recuperar unha ou máis entidades cun atributo cada unha require dúas unións na consulta: a primeira unión coa táboa de atributos e a segunda unión coa táboa de valores. Se unha entidade ten dous atributos, entón requírense catro unións! Ademais, todos os atributos normalmente almacénanse como cadeas de texto, o que resulta nunha coerción de tipo tanto para o resultado como para a cláusula WHERE. Se escribes moitas consultas, isto supón un desperdicio considerable en termos de uso de recursos.

Malia estas deficiencias obvias, o EAV leva moito tempo utilizándose para resolver este tipo de problemas. Estas eran deficiencias inevitables e simplemente non había unha alternativa mellor.
Pero entón apareceu unha nova «tecnoloxía» en PostgreSQL…

A partir de PostgreSQL 9.4, engadiuse o tipo de datos JSONB para almacenar datos JSON binarios. Aínda que almacenar JSON neste formato adoita levar un pouco máis de espazo e tempo que o JSON de texto sen formato, as operacións con el son moito máis rápidas. JSONB tamén admite a indexación, o que fai que as consultas sexan aínda máis rápidas.

O tipo de datos JSONB permítenos substituír o engorroso patrón EAV engadindo unha única columna JSONB á nosa táboa de entidades, o que simplifica significativamente o deseño da base de datos. Non obstante, moitos argumentan que isto ten un custo en canto ao rendemento... Esa é a razón pola que escribín este artigo.

Configuración dunha base de datos de probas

Para esta comparación, creei a base de datos nunha instalación nova de PostgreSQL 9.5 na compilación de 80 dólares. DigitalOcean Ubuntu 14.04 Despois de configurar algúns parámetros en postgresql.conf executei isto script usando psql. Para presentar os datos como EAV, creáronse as seguintes táboas:

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

A continuación móstrase unha táboa onde se almacenarán os mesmos datos, pero con atributos nunha columna JSONB: Propiedades.

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

Parece moito máis sinxelo, non si? Despois engadiuse ás táboas de entidades (entidade & entidade_jsonb) 10 millóns de rexistros e, en consecuencia, a táboa encheuse con datos idénticos onde se empregaron o patrón EAV e a aproximación coa columna JSONB. entity_jsonb.propertiesDeste xeito, obtivemos varios tipos de datos diferentes en todo o conxunto de propiedades. Datos de exemplo:

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

Entón, agora temos datos idénticos para ambas opcións. Comecemos a comparar as implementacións na vida real!

Simplificando o deseño

Xa se mencionou anteriormente que o deseño da base de datos se simplificou significativamente: unha táboa, usando unha columna JSONB para as propiedades, en lugar de tres táboas para EAV. Pero como se traduce isto en consultas? A actualización dunha única propiedade de entidade ten este aspecto:

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

Como podes ver, a última consulta non parece máis sinxela. Para actualizar o valor dunha propiedade nun obxecto JSONB, debemos usar a función conxunto_jsonb()e debe pasar o noso novo valor como un obxecto JSONB. Non obstante, non precisamos coñecer ningún identificador de antemán. Observando o exemplo de EAV, precisamos coñecer tanto entity_id como entity_attribute_id para realizar a actualización. Se queres actualizar unha propiedade nunha columna JSONB en función do nome do obxecto, todo faise nunha soa liña.

Agora seleccionemos a entidade que acabamos de actualizar en función da súa nova cor:

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

Creo que podemos estar de acordo en que o segundo é máis curto (sen a unión!) e, polo tanto, máis lexible. JSONB gaña aquí! Usamos o operador JSON ->> para recuperar a cor como un valor de texto do obxecto JSONB. Tamén hai unha segunda forma de conseguir o mesmo resultado no modelo JSONB usando o operador @>:

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

Isto é un pouco máis complicado: comprobamos se o obxecto JSON na columna de propiedades contén o obxecto do lado dereito do operador @>. Menos lexible, máis eficiente (véxase máis abaixo).

Simplifiquemos aínda máis o uso de JSONB cando precise seleccionar varias propiedades á vez. Aquí é onde o enfoque JSONB realmente destaca: simplemente seleccionamos as propiedades como columnas adicionais no noso conxunto de resultados, sen necesidade de unións:

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

Con EAV, necesitarás dúas unións para cada propiedade que queiras consultar. Na miña opinión, as consultas anteriores demostran unha simplificación significativa no deseño de bases de datos. Tamén podes ver máis exemplos de como escribir consultas JSONB en isto publicación.
Agora é o momento de falar de rendemento.

Produtividade

Para comparar o rendemento que empreguei EXPLICAR ANALIZAR nas consultas para calcular o tempo de execución. Cada consulta executouse polo menos tres veces, porque o planificador de consultas tarda máis a primeira vez. Primeiro, executei as consultas sen índices. Isto obviamente serviu como unha vantaxe de JSONB, xa que as unións necesarias para EAV non podían usar índices (os campos de clave estranxeira non estaban indexados). Despois diso, creei un índice nas dúas columnas de clave estranxeira da táboa de valores EAV, así como un índice GIN para a columna JSONB.

As actualizacións de datos mostraron os seguintes resultados de tempo (en ms). Teña en conta que a escala é logarítmica:

Substituíndo EAV por JSONB en PostgreSQL

Vemos que JSONB é significativamente (>50000 veces) máis rápido que EAV sen índices, polo motivo mencionado anteriormente. Cando indexamos columnas de clave primaria, a diferenza case desaparece, pero JSONB segue sendo 1,3 veces máis rápido que EAV. Teña en conta que o índice na columna JSONB non ten ningún efecto aquí, xa que non usamos a columna de propiedades nos criterios de avaliación.

Para a selección de datos baseada no valor da propiedade, obtemos os seguintes resultados (escala normal):

Substituíndo EAV por JSONB en PostgreSQL

Podes ver que JSONB é de novo máis rápido que EAV sen índices, pero cando se indexa EAV, segue sendo máis rápido que JSONB. Pero despois vin que os tempos para as consultas JSONB eran os mesmos, o que me levou ao feito de que os índices GIN non se activan. Ao parecer, cando se usa un índice GIN nunha columna con propiedades poboadas, só ten efecto cando se usa o operador de inclusión @>. Usei isto nunha nova proba e tivo un grande impacto no tempo: só 0,153 ms! Iso é 15 000 veces máis rápido que EAV e 25 000 veces máis rápido que o operador ->>.

Creo que foi bastante rápido!

Tamaño das táboas da base de datos

Comparemos os tamaños das táboas para ambas as dúas abordaxes. En psql, podemos mostrar o tamaño de todas as táboas e índices usando o comando dti+

Substituíndo EAV por JSONB en PostgreSQL

Co enfoque EAV, os tamaños das táboas son de aproximadamente 3068 MB e os índices chegan aos 3427 MB, para un total de 6,43 GB. Usando o enfoque JSONB, a táboa usa 1817 MB e os índices 318 MB, para un total de 2,08 GB. Iso é un terzo do tamaño! Este feito sorprendeume un pouco, xa que almacenamos os nomes das propiedades en cada obxecto JSONB.

Pero as cifras falan por si mesmas: en EAV, almacenamos dúas claves estranxeiras enteiras por valor de atributo, o que resulta en 8 bytes de datos adicionais. Ademais, en EAV, todos os valores das propiedades almacénanse como texto, mentres que JSONB usará valores numéricos e lóxicos internamente sempre que sexa posible, o que resultará nunha pegada máis pequena.

Resultados de

En xeral, creo que almacenar as propiedades das entidades en formato JSONB pode simplificar significativamente o deseño e o mantemento da túa base de datos. Se realizas moitas consultas, almacenar todo na mesma táboa que a entidade será realmente máis eficiente. O feito de simplificar as interaccións de datos xa é unha vantaxe, pero a base de datos resultante tamén é tres veces máis pequena.

Ademais, baseándonos nos resultados das probas de rendemento, podemos concluír que a penalización no rendemento é moi pequena. Nalgúns casos, JSONB incluso funciona máis rápido que EAV, o que o fai aínda mellor. Non obstante, esta proba de rendemento certamente non abrangue todos os aspectos (por exemplo, entidades cun número moi grande de propiedades, un aumento significativo no número de propiedades nos datos existentes, etc.), polo que se tes algunha suxestión de mellora, non dubides en deixala nos comentarios!

Fonte: www.habr.com

Compre hospedaxe fiable para sitios con protección DDoS, servidores VPS VDS 🔥 Compra aloxamento web fiable con protección DDoS, servidores VPS VDS | ProHoster