С тех пор как Postgres начал поддерживать NoSQL (посредством HStore, JSON и JSONB), вопрос о том, когда использовать Postgres в реляционном режиме, а в каких в режиме NoSQL, стал подниматься достаточно часто. Получится ли у вас полностью отказаться от традиционных структур таблиц и работать с представлениями документов в будущем? Смешивать ли оба подхода? Ответ на этот вопрос не удивителен — все зависит от многих факторов. Каждая новая модель хранения данных включая Hstore, JSON и JSONB имеет свои идеальные варианты применения. Тут мы копнём глубже и узнаем об особенностях каждой из них и посмотрим когда что использовать
HStore
Если исключить XML, Hstore был первым по-настоящему неструктурированным типом данных, добавленным в Postgres. Hstore был добавлен достаточно давно в Postgres 8.3 до upsert, до потоковой репликации, и до оконных функций. Hstore это по существу хранилище ключ/значение непосредственно в Postgres. Используя Hstore вы ограничены в выборе используемого типа данных. По сути у вас есть только строки. У вас даже нет вложенности данных; Короче говоря, это одноуровневый тип данных ключ/значение.
Достоинством Hstore является то, что вам не нужно определять ключи (в отличии от столбцов) заранее. Вы можете просто вставить запись, и она будет хранить все необходимые данные. Скажем, у вас есть пример скрипта на создание таблицы:
CREATE TABLE products (
id serial PRIMARY KEY,
name varchar,
attributes hstore
);
С помощью Hstore вы можете вставить все, что вы хотите в колонку атрибутов. В этом случае запрос на добавление этих ключей и значений будет выглядеть следующимобразом:
INSERT INTO products (name, attributes) VALUES (
'Geek Love: A Novel',
'author => "Katherine Dunn",
pages => 368,
category => fiction'
);
Запрос на выборку будет иметь вид:
SELECT name, attributes->'author' as author
FROM products
WHERE attributes->'category' = 'fiction'
Очевидным преимуществом такого подхода является гибкость, но вот где он действительно проявляет себя полностью, так это возможностью использовать различные типы индексов. В частности, GIN или GiST индекс будет индексировать каждый ключ и значение в пределах Hstore. Т.е., при фильтрации будет использован добавленный индекс, в случае если этого потребует планировщик Postgres.
Поскольку Hstore не является полным эквивалентом документа, важно понять выгодно ли использовать его в качестве такового.
Если у вас есть реляционные данные и также некоторые данные, которые не всегда могут существовать в колонке, то такой подход может статьотличным решением. Например, в большинстве случаев атрибуты каталогов продукции могут быть прекрасным примером для такоготипа данных. Тогда, для некоторых продуктов, таких как книги (которые вы храните в отдельной таблице “Products”) могут быть определены такие параметры, как жанр, год издания. В другом случае для продуктов, таких как одежда, которую вы тоже храните в этой же таблице, могут быть определены уже другие параметры — размер и цвет. Добавлять же столбец в таблицу продуктов для каждого возможного параметра избыточно и неоправданно.
JSON
Начиная с версии 9.2 в Postgres реализована поддержка JSON. Теперь, Postgres может составить конкуренцию MongoDB. (Хотя функциональность JSON в Postgres 9.2, конечно, немного преувеличенна. Об этом ниже.)
Тип данных в формате JSON в Postgres, если разобраться в значительной степени просто текстовое поле. Всё что вы получите с типом данных JSON так это валидацию значения при вставке. Postgres обеспечивает соблюдение формата JSON. Одним небольшим потенциальным преимуществом над JSONB (который мы рассмотрим следующим) является то, что JSON сохраняет отступы в данных, поступающих в БД. Так что если вы очень требовательны к форматированию ваших данных или вам необходимо сохранить запись в той или иной структуре, JSON может оказаться полезным.
Кроме того, с течением времени Postgres приобрёл ряд довольно полезных функций. Должны ли вы использовать JSON? Ведь, Postgres-ный тип JSON просто предоставляет проверку на текстовом поле. Если вы храните некоторую форму данных журнала, которую редко запрашиваете, тип данных JSON в этом случае работает хорошо. Так как JSON довольно прост, то он будет иметь гораздо более высокую пропускную способность при вставке. Для чего-либо более сложного, я бы рекомендовал использовать JSONB, который будет описан ниже.
JSONB
Наконец, в Postgres 9.4 мы получили настоящую и правильную поддержку JSON в виде JSONB. B означает “лучше” (Better). JSONB — это бинарное представление данных в формата JSON. Это означает, что данные сжимается и более эффективны для хранения, чем обычный текст. Кроме того, под капотом у него механизм, подобный Hstore. Технически, когда-то при разработке, был почти реализованный тип Hstore2 и отдельный тип JSON и впоследствии они были объединены в JSONB в том виде, как он есть сейчас.
Тип JSONB представляет в значительной степени то, что вы могли бы ожидать от типа данных JSON. Он позволяет реализовывать вложенные структуры, использовать основные типы данных, а также имеет ряд встроенных функций для работы с ним. Лучшей частью такой схожести с Hstore является индексация. Создание индекса GIN на колонке JSONB создаст индекс по каждому ключу и значению в пределах этого документа JSON. Возможность индексации и вложенность данных внутри документа означают, что JSONB превосходит Hstore в большинстве случаев.
Хотя все еще остаётся небольшой вопрос о том, в каких случаях следует использовать исключительно JSONB. Допустим, вы создаете базу данных документов и из всех вариантов выбираете Postgres. С пакетом, наподобие MassiveJS это может быть довольно удобным.
Наиболее распространенные примеры использования:
- Отслеживание событий данных, добавляя изменяющийся payload события.
- Хранение игровых данные достаточно распространено, особенно там, где у вас есть одиночная игра и изменяющаяся схема данных на основе состояния пользователя.
- Инструменты, которые объединяют несколько источников данных, пример здесь может быть инструментом, который интегрирует несколько баз данных клиентов к Salesforce, к Zendesk или к чему-то еще. Сочетание схем делает это более болезненной процедурой, чем она должна быть.
Давайте рассмотрим, другой пример работы с JSONB. Скрипт создает таблицу и вставляет некоторые данные для примера:
CREATE TABLE integrations (id UUID, data JSONB);
INSERT INTO integrations VALUES (
uuid_generate_v4(),
'{
"service": "salesforce",
"id": "AC347D212341XR",
"email": "craig@citusdata.com",
"occurred_at": "8/14/16 11:00:00",
"added": {
"lead_score": 50
},
"updated": {
"updated_at": "8/14/16 11:00:00"
}
}');
INSERT INTO integrations (
uuid_generate_v4 (),
'{
"service": "zendesk",
"email": "craig@citusdata.com",
"occurred_at": "8/14/16 10:50:00",
"ticket_opened": {
"ticket_id": 1234,
"ticket_priority": "high"
}
}');
В приведенном выше случае, можно легко найти все события, которые произошли c пользователем с email craig@citusdata.com, а затем делать какие-то действия. Например, можно провести какую-либо форму поведенческой аналитики, и вычислить пользователей которые сделали foo а затем bar, или сделать простой отчет.
Добавив индекс Gin все данные в пределах моего JSONB поля проиндексируются автоматически:
CREATE INDEX idx_integrations_data ON integrations USING gin(data);
Заключение
В большинстве случаев JSONB это, вероятно, как раз то, что вы ищите, когда планируете использовать нереляционый тип данных. Для Hstore и JSON можно также найти хорошее применение хоть и в более редких случаях. JSONB не всегда вписывается в модель данных. В случае если вы можете нормализовать схему, то у вас будет преимущество, но если в схеме, большое количество опциональных столбцов (например, с данными о событиях) или одна схема сильно отличается от другой, то JSONB подходит гораздо лучше.
Резумируя, алгоритм выбора решения:
JSONB — В большинстве случаев
JSON — Если вы обрабатываете логи, вам не часто приходится запрашивать данные или не нужно использовать их как что-то большее чем для задачь логирования.
Hstorere — отлично работает с текстовыми данными на основе представления ключ-значение, но в целом JSONB также отлично справляется с этой задачей.
Автор: Vovaka