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:
Критичні бізнес-сутності: Користувачі, замовлення, товари. Тут потрібна строга нормалізація,
Foreign Keysта обмеження на рівні схеми.Масова агрегація: Функції на кшталт
SUM,AVG,GROUP BYпо полях всередині JSONB працюють значно повільніше, ніж по звичайних колонках.Часті апдейти: При оновленні навіть одного поля в JSONB, СКБД фактично переписує весь об'єкт. Якщо поле змінюється щосекунди — використовуйте звичайну колонку.
Важливість суворої типізації: Якщо бізнес-логіка вимагає гарантії наявності поля та його типу на рівні БД.
Підсумок
JSONB — це ідеальний баланс між:
EAV: занадто гнучко, складно для запитів.
Жорсткою схемою: занадто консервативно, вимагає постійних міграцій.
Використовуйте JSONB для Snapshot-ів, поліморфних логів, метаданих та конфігурацій.