Друкарня від WE.UA

Коли використовувати JSONB у реляційній БД?

1. Історичний зліпок (Snapshot) зовнішніх API

Проблема: Ви інтегруєтесь із зовнішнім сервісом, чий API змінюється кожні кілька місяців.

  • Невдале рішення (підтаблиці): Постійні ALTER TABLE, додавання полів, які для старих записів залишаються NULL. Втрачається контекст: як саме виглядав об'єкт на момент запиту.

  • Гарне рішення (JSONB):

CREATE TABLE api_journal (
    id BIGINT,
    request JSONB,  -- ідентична копія надісланих даних
    response JSONB  -- ідентична копія отриманої відповіді
);

Перевага: Підтримка різних версій схеми даних без міграцій та збереження повної цілісності історичних даних.


2. Різнорідні події зі змінною структурою

Проблема: Система аудиту фіксує події, кожна з яких має унікальний набір атрибутів.

  • Невдале рішення: Створювати «таблицю-простирадло» з 50+ nullable-полями або городити десятки окремих таблиць під кожен тип події.

  • Гарне рішення:

CREATE TABLE audit_log (
    id BIGINT,
    event_type VARCHAR(50),
    event_data JSONB  -- гнучка структура атрибутів
);
CREATE INDEX idx_event_data ON audit_log USING GIN (event_data);

Запит (знайти платежі > 1000):

SELECT * FROM audit_log 
WHERE event_type = 'payment_created' 
  AND (event_data->>'amount')::numeric > 1000;

3. Списки вкладених об'єктів (Denormalization)

Проблема: Об'єкт має невелику кількість пов'язаних сутностей (наприклад, фрод-мітки або теги), які майже завжди читаються разом із основним об'єктом.

  • Невдале рішення: Виносити кожну дрібницю в окрему таблицю. Це змушує використовувати JOIN, що при великій кількості записів може сповільнювати систему та ускладнювати мапінг в коді.

  • Гарне рішення:

CREATE TABLE cards (
    id BIGINT,
    pan_mask VARCHAR(19),
    fraud_marks JSONB  -- масив об'єктів в одному полі
);

Пошук активних міток:

SELECT * FROM cards WHERE fraud_marks @> '[{"active": true}]';

4. Кеш та тимчасові дані (TTL)

Проблема: Дедуплікація запитів або збереження результатів обчислень на короткий час.

  • Невдале рішення: Створювати складну схему з Foreign Keys. При видаленні застарілих даних (Cleanup) потрібно враховувати порядок видалення через обмеження цілісності.

  • Гарне рішення:

CREATE TABLE dedup_cache (
    key VARCHAR(255) PRIMARY KEY,
    cached_data JSONB,
    expires_at TIMESTAMP
);

Очищення даних: Виконується однією операцією DELETE без ризику порушити зв'язки.


5. Збереження контексту помилок

Проблема: Для дебагу потрібно зберігати Stack Trace та стан об'єктів у момент виникнення Exception.

  • Невдале рішення: Зберігати все як TEXT. Його неможливо ефективно фільтрувати або аналізувати програмно.

  • Гарне рішення:

CREATE TABLE error_logs (
    id BIGINT,
    error_details JSONB -- клас помилки, стектрейс, контекст (user_id, env)
);

6. Конфігурації модулів

Проблема: Різні компоненти системи мають абсолютно різні налаштування (ліміти, ендпоінти, прапорці).

  • Невдале рішення: Паттерн EAV (Entity-Attribute-Value). Складно збирати об'єкт докупи, важко зберігати масиви та вкладеність.

  • Гарне рішення: Зберігати конфігурацію як єдиний JSON-об'єкт. Це дозволяє за один запит отримати всі налаштування модуля в їх природній ієрархічній структурі.


Коли НЕ варто використовувати JSONB:

  1. Критичні бізнес-сутності: Користувачі, замовлення, товари. Тут потрібна строга нормалізація, Foreign Keys та обмеження на рівні схеми.

  2. Масова агрегація: Функції на кшталт SUM, AVG, GROUP BY по полях всередині JSONB працюють значно повільніше, ніж по звичайних колонках.

  3. Часті апдейти: При оновленні навіть одного поля в JSONB, СКБД фактично переписує весь об'єкт. Якщо поле змінюється щосекунди — використовуйте звичайну колонку.

  4. Важливість суворої типізації: Якщо бізнес-логіка вимагає гарантії наявності поля та його типу на рівні БД.

Підсумок

JSONB — це ідеальний баланс між:

  • EAV: занадто гнучко, складно для запитів.

  • Жорсткою схемою: занадто консервативно, вимагає постійних міграцій.

Використовуйте JSONB для Snapshot-ів, поліморфних логів, метаданих та конфігурацій.


Статті про вітчизняний бізнес та цікавих людей:

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Oleksandr Klymenko
Oleksandr Klymenko@overpathz

Java Software Engineer

7.3KПрочитань
1Автори
93Читачі
На Друкарні з 19 квітня

Більше від автора

  • Як виконується SQL-запит

    Сьогодні розберемо, що відбувається з вашим SELECT * FROM users WHERE age > 21 перед тим, як БД поверне вам дані

    Теми цього довгочиту:

    Computer Science
  • Стратегічний дизайн виключень

    Виключення у Java — звісно ж класний механізм, але невміле їх використання може суттєво погіршити продуктивність системи, особливо у "критичних шляхах" виконання.

    Теми цього довгочиту:

    Java

Це також може зацікавити:

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Це також може зацікавити: