TL; DR: JSONB puede simplificar enormemente el desarrollo de esquemas de bases de datos sin sacrificar el rendimiento de las consultas.
introducción
Demos un ejemplo clásico de probablemente uno de los casos de uso más antiguos en el mundo de una base de datos relacional (base de datos): tenemos una entidad y necesitamos guardar ciertas propiedades (atributos) de esta entidad. Pero es posible que no todas las instancias tengan el mismo conjunto de propiedades y es posible que se agreguen más propiedades en el futuro.
La forma más sencilla de resolver este problema es crear una columna en la tabla de la base de datos para cada valor de propiedad y simplemente completar las que sean necesarias para una instancia de entidad específica. ¡Excelente! Problema resuelto... hasta que tu tabla contenga millones de registros y necesites agregar un nuevo registro.
Considere el patrón EAV (), ocurre con bastante frecuencia. Una tabla contiene entidades (registros), otra tabla contiene nombres de propiedades (atributos) y una tercera tabla asocia entidades con sus atributos y contiene el valor de esos atributos para la entidad actual. Esto le brinda la posibilidad de tener diferentes conjuntos de propiedades para diferentes objetos y también agregar propiedades sobre la marcha sin cambiar la estructura de la base de datos.
Sin embargo, no estaría escribiendo esta publicación si el enfoque EVA no tuviera algunas desventajas. Entonces, por ejemplo, para obtener una o más entidades que tengan 1 atributo cada una, se requieren 2 uniones en la consulta: la primera es una unión con la tabla de atributos, la segunda es una unión con la tabla de valores. Si una entidad tiene 2 atributos, ¡entonces se necesitan 4 uniones! Además, todos los atributos normalmente se almacenan como cadenas, lo que da como resultado una conversión de tipos tanto para el resultado como para la cláusula WHERE. Si escribe muchas consultas, esto es un desperdicio en términos de uso de recursos.
A pesar de estas obvias deficiencias, EAV se ha utilizado durante mucho tiempo para resolver este tipo de problemas. Se trataba de deficiencias inevitables y simplemente no había otra alternativa mejor.
Pero entonces apareció una nueva “tecnología” en PostgreSQL...
A partir de PostgreSQL 9.4, se agregó el tipo de datos JSONB para almacenar datos binarios JSON. Aunque almacenar JSON en este formato normalmente requiere un poco más de espacio y tiempo que JSON en texto plano, realizar operaciones en él es mucho más rápido. JSONB también admite la indexación, lo que hace que las consultas sean aún más rápidas.
El tipo de datos JSONB nos permite reemplazar el engorroso patrón EAV agregando solo una columna JSONB a nuestra tabla de entidades, lo que simplifica enormemente el diseño de la base de datos. Pero muchos argumentan que esto debería ir acompañado de una disminución de la productividad... Por eso escribí este artículo.
Configurar una base de datos de prueba
Para esta comparación, creé la base de datos en una instalación nueva de PostgreSQL 9.5 en la versión de $80. Ubuntu 14.04 Después de configurar algunos parámetros en postgresql.conf ejecuté secuencia de comandos usando psql. Se crearon las siguientes tablas para presentar los datos en formato 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
);
A continuación se muestra una tabla donde se almacenarán los mismos datos, pero con atributos en una columna tipo JSONB – propiedades.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Parece mucho más sencillo, ¿no? Luego se agregó a las tablas de entidades (entidad & entidad_jsonb) 10 millones de registros y, en consecuencia, la tabla se llenó con los mismos datos utilizando el patrón EAV y el enfoque con una columna JSONB - entidad_jsonb.properties. Por lo tanto, recibimos varios tipos de datos diferentes entre todo el conjunto de propiedades. Datos de ejemplo:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}Entonces ahora tenemos los mismos datos para ambas opciones. ¡Comencemos a comparar implementaciones en el trabajo!
Simplifica tu diseño
Anteriormente se indicó que el diseño de la base de datos se simplificó enormemente: una tabla, utilizando una columna JSONB para las propiedades, en lugar de utilizar tres tablas para EAV. Pero ¿cómo se refleja esto en las solicitudes? La actualización de una propiedad de entidad se ve así:
-- 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 puede ver, la última solicitud no parece más sencilla. Para actualizar el valor de una propiedad en un objeto JSONB tenemos que usar la función y deberíamos pasar nuestro nuevo valor como un objeto JSONB. Sin embargo, no necesitamos conocer ningún identificador de antemano. Mirando el ejemplo de EAV, necesitamos conocer tanto el id_entidad como el id_atributo_entidad para poder realizar la actualización. Si desea actualizar una propiedad en una columna JSONB según el nombre del objeto, todo se hace en una simple línea.
Ahora seleccionemos la entidad que acabamos de actualizar según su nuevo color:
-- 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 acuerdo en que el segundo es más corto (¡sin unirse!) y, por lo tanto, más legible. ¡JSONB gana aquí! Usamos el operador JSON ->> para obtener el color como valor de texto de un objeto JSONB. También existe una segunda forma de lograr el mismo resultado en el modelo JSONB usando el operador @>:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Esto es un poco más complicado: verificamos si el objeto JSON en su columna de propiedades contiene un objeto que está a la derecha del operador @>. Menos legible, más productivo (ver más abajo).
Hagamos que el uso de JSONB sea aún más fácil cuando necesite seleccionar varias propiedades a la vez. Aquí es donde realmente entra en juego el enfoque JSONB: simplemente seleccionamos propiedades como columnas adicionales en nuestro conjunto de resultados sin necesidad de uniones:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
Con EAV necesitarás 2 uniones para cada propiedad que quieras consultar. En mi opinión, las consultas anteriores muestran una gran simplificación en el diseño de bases de datos. Vea más ejemplos de cómo escribir consultas JSONB, también en correo.
Ahora es el momento de hablar de rendimiento.
Rendimiento
Para comparar el rendimiento utilicé en consultas, para calcular el tiempo de ejecución. Cada consulta se ejecutó al menos tres veces porque el planificador de consultas tarda más la primera vez. Primero ejecuté las consultas sin ningún índice. Obviamente, esto era una ventaja de JSONB, ya que las uniones requeridas para EAV no podían usar índices (los campos de clave externa no estaban indexados). Después de esto, creé un índice en las 2 columnas de clave externa de la tabla de valores EAV, así como un índice. para una columna JSONB.
La actualización de datos mostró los siguientes resultados en términos de tiempo (en ms). Tenga en cuenta que la escala es logarítmica:

Vemos que JSONB es mucho (> 50000-x) más rápido que EAV si no utiliza índices, por el motivo indicado anteriormente. Cuando indexamos columnas con claves primarias, la diferencia casi desaparece, pero JSONB sigue siendo 1,3 veces más rápido que EAV. Tenga en cuenta que el índice de la columna JSONB no tiene ningún efecto aquí ya que no utilizamos la columna de propiedades en los criterios de evaluación.
Para seleccionar datos según el valor de la propiedad, obtenemos los siguientes resultados (escala normal):

Puede notar que JSONB nuevamente funciona más rápido que EAV sin índices, pero cuando EAV con índices, aún funciona más rápido que JSONB. Pero luego vi que los tiempos para las consultas JSONB eran los mismos, esto me llevó al hecho de que los índices GIN no funcionan. Aparentemente, cuando usa un índice GIN en una columna con propiedades completas, solo tiene efecto cuando usa el operador de inclusión @>. Utilicé esto en una nueva prueba y tuvo un gran impacto en el tiempo: ¡sólo 0,153 ms! Esto es 15000 veces más rápido que EAV y 25000 veces más rápido que el operador ->>.
¡Creo que fue lo suficientemente rápido!
Tamaño de la tabla de base de datos
Comparemos los tamaños de las tablas para ambos enfoques. En psql podemos mostrar el tamaño de todas las tablas e índices usando el comando dti+

Para el enfoque EAV, los tamaños de tabla son de alrededor de 3068 MB y los índices de hasta 3427 MB para un total de 6,43 GB. El enfoque JSONB utiliza 1817 MB para la tabla y 318 MB para los índices, lo que equivale a 2,08 GB. ¡Resulta 3 veces menos! Este hecho me sorprendió un poco porque almacenamos nombres de propiedades en cada objeto JSONB.
Pero aún así, los números hablan por sí solos: en EAV almacenamos 2 claves externas enteras por valor de atributo, lo que da como resultado 8 bytes de datos adicionales. Además, EAV almacena todos los valores de propiedad como texto, mientras que JSONB utilizará valores numéricos y booleanos internamente siempre que sea posible, lo que dará como resultado una huella más pequeña.
resultados
En general, creo que guardar las propiedades de la entidad en formato JSONB puede facilitar mucho el diseño y el mantenimiento de su base de datos. Si está ejecutando muchas consultas, mantener todo en la misma tabla que la entidad funcionará de manera más eficiente. Y el hecho de que esto simplifique la interacción entre datos ya es una ventaja, pero la base de datos resultante tiene un volumen 3 veces menor.
Además, según las pruebas realizadas, podemos concluir que las pérdidas de rendimiento son muy insignificantes. En algunos casos, JSONB es incluso más rápido que EAV, lo que lo hace aún mejor. Sin embargo, este punto de referencia, por supuesto, no cubre todos los aspectos (por ejemplo, entidades con una gran cantidad de propiedades, un aumento significativo en la cantidad de propiedades de los datos existentes,...), así que si tiene alguna sugerencia sobre cómo mejorarlas , ¡no dudes en dejarlo en los comentarios!
Fuente: habr.com
