TL; DR: JSONB verilənlər bazası sxeminin inkişafını mühüm dərəcədə asanlaşdıra bilər, sorğularda performansdan ödün vermədən.
Giriş
Klassik nümunə verək, yəqin ki, ən köhnə əlaqəli verilənlər bazası (baza) istifadə variantlarından biridir: bizdə bir varlıq var və bu varlığın müəyyən xüsusiyyətlərini (atributlarını) saxlamaq lazımdır. Lakin, bütün nümunələrin eyni xüsusiyyətlər dəstinə malik olması mümkün deyil, üstəlik, gələcəkdə daha çox xüsusiyyətlərin əlavə edilməsi mümkündür.
Bu problemin ən sadə həlli, verilənlər bazası cədvəlində hər bir xüsusiyyət dəyəri üçün sütun yaratmaqdır və sadəcə olaraq xüsusi varlığın lazım olanları doldurmaqdır. Əla! Problem həll olundu... sizdə milyonlarla qeyd olan bir cədvəl olana qədər və yeni bir qeyd əlavə etmək zərurəti yaranana qədər.
EAV modelini nəzərdən keçirək (), bu kifayət qədər tez-tez rast gəlinir. Bir cədvəl varlıqları (yazıları), digər cədvəl isə xüsusiyyət adlarını (atributları) ehtiva edir, üçüncü cədvəl isə varlıqları onların atributları ilə birləşdirir və cari varlıq üçün bu atributların dəyərini saxlayır. Bu, sizə müxtəlif obyektlər üçün fərqli xüsusiyyətlər dəstlərini əldə etməyə və verilənlər bazası strukturunu dəyişdirmədən “canlı” olaraq xüsusiyyətlər əlavə etməyə imkan verir.
Bununla belə, mən bu qeydi yazmazdım, əgər EAV istifadə yaxınlığının çatışmazlıqları olmasaydı. Məsələn, 1 atribut olan bir və ya bir neçə varlığı əldə etmək üçün sorğuda 2 join (birləşmə) tələb olunur: birincisi – atributlar cədvəli ilə birləşmə, ikincisi – dəyərlər cədvəli ilə birləşmə. Əgər varlıq 2 atributa malikdirsə, onda artıq 4 join lazımdır! Həmçinin, bütün atributlar adətən mətn şəklində saxlanılır ki, bu da nəticə və WHERE şərti üçün növün dəyişdirilməsinə gətirib çıxarır. Hesablama zamanında çox sorğu yazırsınızsa, bu, resurs istifadəsi baxımından kifayət qədər israfçıdır.
Bu nəzərəçarpan çatışmazlıqlara baxmayaraq, EAV artıq uzun müddətdir bu cür problemlərin həlli üçün istifadə olunur. Bunlar qaçılmaz çatışmazlıqlar idi, və daha yaxşı alternativi sadəcə olaraq yox idi.
Amma sonra PostgreSQL-də yeni bir “texnologiya” meydana çıxdı...
PostgreSQL 9.4-dən başlayaraq, JSON formatında ikili verilənləri saxlamaq üçün JSONB məlumat tipi əlavə edildi. Bu formatda JSON saxlandıqda, adətən, sadə mətn JSON-dan bir az daha çox yer və vaxt tələb etsə də, onunla əməliyyatlar daha sürətli həyata keçirilir. Həmçinin, JSONB indeksləməyi dəstəkləyir, bu da onlara sorğuları daha da sürətləndirir.
JSONB məlumat növü bizə EAV modelini əvəz etmək üçün yalnız bir JSONB sütunu əlavə etməklə əhəmiyyətli dərəcədə sadələşdirilmiş verilənlər bazası dizaynı imkanını verir. Lakin bir çoxları bunun performansın azaldılması ilə müşayiət olunmalı olduğunu iddia edir… Buna görə də, bu məqalə ortaya çıxdı.
Sınaq verilənlər bazasının konfiqurasiyası
Bu müqayisə üçün mən PostgreSQL 9.5-in yeni quraşdırması üzərində 80 dollar dəyərində bir verilənlər bazası yaratdım. Ubuntu 14.04. Bəzi parametrləri postgresql.conf-da konfiqurasiya etdikdən sonra başladım skripti psql vasitəsilə. EAV formatında məlumatları təqdim etmək üçün aşağıdakı cədvəllər yaradıldı:
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şağıda JSONB tipli sütunda atributlarla eyni məlumatları saxlayacaq cədvəl təqdim olunur – properties.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Daha sadə görünür, elə deyilmi? Sonra bu cədvəllərə (entity & entity_jsonb) 10 milyon qeyd əlavə olundu və müvafiq olaraq EAV modelində istifadə olunan cədvələ və JSONB sütunu ilə yanaşı eyni məlumatlarla dolduruldu – entity_jsonb.properties. Bununla da, biz bütün property setində bir neçə fərqli məlumat növü əldə etdik. Məlumatların nümunəsi:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}Beləliklə, indi eyni məlumatlara sahibik, iki variant üçün. Gəlin, icra tərzlərini müqayisə etməkdə başlayıq!
Dizaynın sadələşdirilməsi
Əvvəlcə, verilənlər bazasının dizaynının əhəmiyyətli dərəcədə sadələşdirildiyi qeyd edilib: bir cədvəl, JSONB sütunu istifadə etməklə property-lər üçün, üç cədvəl yerinə EAV üçün. Bəs bu sorğulara necə təsir edir? Bir varlığın özəlliyinin yenilənməsi belə görünü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;
Gördüyünüz kimi, sonuncu sorğu daha sadə görünmür. JSONB obyekti içərisində özəlliyin dəyərini yeniləmək üçün biz funkciyası istifadə etməliyik , və yeni dəyərimizi JSONB obyekti kimi təqdim etməliyik. Buna baxmayaraq, biz əvvəlcədən hər hansı bir identifikator bilmək məcburiyyətində deyilik. EAV nümunəsinə baxsaq, yenilənmə apararaq entity_id və entity_attribute_id bilmək lazımdır. JSONB sütunundakı özəlliyi obyekti adı əsasında yeniləmək istəsəniz, bunun hamısı bir sadə sətirlə edilir.
İndi gəlin, yeni rənginə görə yenilədiyimiz varlığı seçək:
-- 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';
Düşünürəm ki, ikincisinin daha qısa (join olmadan!) olduğunu qəbul edə bilərik və buna görə daha oxunaqlıdır. Burada JSONB-nin üstünlüyü var! Biz JSON-dan mətbəx dəyəri əldə etmək üçün JSON operatoru ->> istifadə edirik. Həmçinin, JSONB modelində eyni nəticəyə çatmağın ikinci yolu var, @> operatorunu istifadə etməklə:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Bu bir az çətindir: biz, xassələr sütunundakı JSON obyektinin sağdakı @> operatoruna olan obyekti ehtiva edib-etmədiyini yoxlayırıq. Daha az oxunaqlı, amma daha məhsuldar (daha sonra baxın).
Eyni anda bir neçə xüsusiyyəti seçmək lazım olduğunda JSONB istifadəni daha da asanlaşdıraq. Burada JSONB yanaşması əslində uyğundur: birləşmələr olmadan nəticə cədvəlimizdə əlavə sütunlar kimi xüsusiyyətləri sadəcə seçirik:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
EAV-də hər bir sorğu üçün tələb etdiyiniz hər bir xüsusiyyət üçün 2 birləşmə lazımdır. Mənim fikrimcə, yuxarıda təqdim olunan sorğular, verilənlər bazası dizaynında böyük bir sadələşdirməni nümayiş etdirir. JSONB-yə sorğular yazmaqla bağlı daha çox nümunələrə baxmaq mümkündür məqalədə.
İndi performansdan danışmağın vaxtıdır.
Performans
Performansı müqayisə etmək üçün mən sorğularda icra vaqtını hesablamaq üçün istifadə etdim. Hər bir sorğu ən azı üç dəfə icra edildi, çünki ilk dəfə sorğu planlaşdırıcısının daha çox vaxtına ehtiyacı var. İlk olaraq, mən sorğuları heç bir indeks olmadan icra etdim. Aydındır ki, bu, JSONB üçün bir üstünlük yaradır, çünki EAV üçün lazım olan birləşmələr indeksləri istifadə edə bilmədilər (xarici açar sahələri indekslənməmişdir). Bundan sonra, EAV dəyər cədvəlindəki 2 xarici açar sütunu üçün bir indeks yaratdım, eyni zamanda JSONB sütunu üçün.
Məlumatların yenilənməsi zamanı zaman (ms) üzrə aşağıdakı nəticələr əldə edildi. Qeyd edin ki, miqyas logaritmidir:

Görürük ki, JSONB EAV-dən çox (< 50000 dəfə) daha tezdir, indekslərdən istifadə etmədikdə, yuxarıda göstərilən səbəbdən. Biz əsas açarlarla sütunları indeksləşdirdikdə, fərq demək olar ki, itir, lakin JSONB hələ də EAV-dən 1,3 dəfə daha tezdir. Qeyd edin ki, burada JSONB sütunundakı indeks heç bir təsir etməz, çünki biz xüsusiyyət sütununu qiymətləndirmə meyarlarında istifadə etmirik.
Mülk dəyərinə əsaslanan məlumat seçimi zamanı aşağıdakı nəticələri əldə edirik (adi miqyas):

Görülür ki, JSONB yenidən indekssiz EAV-dən sürətli işləyir, lakin EAV indekslərlə olduğunda JSONB-dən daha sürətli olur. Ancaq sonra JSONB sorğularının müddətinin eyni olduğunu gördüm, bu da GIN indeksinin işləmədiyini göstərir. Görünür, əgər bir sütun üçün GIN indeksi istifadə edirsinizsə, yalnız @> daxil olma operatoru istifadə edildikdə çalışır. Bunu yeni testdə istifadə etdim, bu da müddətə böyük təsir göstərdi: yalnız 0.153 ms! Bu, EAV-dən 15000 dəfə, və - >> operatorundan 25000 dəfə daha sürətlidir.
Düşünürəm ki, bu kifayət qədər sürətli oldu!
Veritabanı cədvəllərinin boyutu
Hər iki yanaşma üçün cədvəl ölçülərini müqayisə edək. psql-da biz bütün cədvəllərin və indekslərin ölçüsünü göstərmək üçün komandanı istifadə edə bilərik dti+

EAV yanaşması üçün cədvəl ölçüləri təxminən 3068 MB, indekslər isə 3427 MB-ə qədərdir, bu da 6.43 GB edir. JSONB yanaşmasında cədvəl 1817 MB və indekslər 318 MB istifadə edir, bu da 2.08 GB deməkdir. Bu, 3 dəfə azdır! Bu fakt məni bir az təəccübləndirdi, çünki biz JSONB-də hər bir obyektdə xüsusiyyətlərin adlarını saxlayırıq.
Ancaq rəqəmlər özləri üçün danışıqlar aparır: EAV-də biz atributun dəyəri üçün 2 tam saylı xarici açarı saxlayırıq, nəticədə əlavə 8 bayt məlumat əldə edirik. Bundan əlavə, EAV-də bütün xüsusiyyət dəyərləri mətn şəklində saxlanılır, halbuki JSONB, mümkün olan yerlərdə ədədi və məntiqi dəyərlər istifadə edir, nəticədə daha az həcm meydana gəlir.
Yekunlar
Ümumiyyətlə, düşünürəm ki, varlıqların xüsusiyyətlərini JSONB formatında saxlamaq, verilənlər bazanızın dizaynını və xidmətini əhəmiyyətli dərəcədə asanlaşdıra bilər. Əgər siz bir çox sorğular icra edirsinizsə, bir cədvəldə olan bütün məlumatların entity ilə birlikdə olması daha effektiv olacaq. Və bu, məlumatlar arasında qarşılıqlı əlaqəni asanlaşdırdığı üçün bir üstünlükdür, həm də nəticədə bu verilənlər bazası 3 dəfə daha az həcme malikdir.
Həmçinin, aparılan testlərdən nəticə çıxarmaq olar ki, performans itkisi çox cüzidir. Bəzi hallarda JSONB, hətta EAV-dən daha sürətli işləyir, bu da onu daha yaxşı edir. Ancaq bu istinad testi, əlbəttə ki, bütün aspektləri əhatə etmir (məsələn, çox sayda xüsusiyyəti olan varlıqlar, mövcud məlumatların xüsusiyyətlərinin əhəmiyyətli artımı və s.), buna görə də hər hansı bir təklifiniz varsa, şərhlərdə qeyd etməkdən çəkinməyin!
Mənbə: habr.com
