Когда использовать неструктурированные типы данных в PostgreSQL? Сравнение Hstore vs. JSON vs. JSONB

https://www.citusdata.com/blog/2016/07/14/choosing-nosql-hstore-json-jsonb/
  • Перевод

С тех пор как PostgreSQL начал поддерживать NoSQL (посредством HStore, JSON и JSONB), вопрос о том, когда использовать PostgreSQL в реляционном режиме, а в каких в режиме NoSQL, стал подниматься достаточно часто. Получится ли у вас полностью отказаться от традиционных структур таблиц и работать с представлениями документов в будущем? Смешивать ли оба подхода? Ответ на этот вопрос не удивителен — все зависит от многих факторов. Каждая новая модель хранения данных включая Hstore, JSON и JSONB имеет свои идеальные варианты применения. Тут мы копнём глубже и узнаем об особенностях каждой из них и посмотрим когда что использовать



HStore


Если исключить XML, Hstore был первым по-настоящему неструктурированным типом данных, добавленным в PostgreSQL. Hstore был добавлен достаточно давно в Postgres 8.3 до upsert, до потоковой репликации, и до оконных функций. Hstore это по существу хранилище ключ/значение непосредственно в PostgreSQL. Используя 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. Т.е., при фильтрации будет использован добавленный индекс, в случае если этого потребует планировщик PostgreSQL.


Поскольку Hstore не является полным эквивалентом документа, важно понять выгодно ли использовать его в качестве такового.
Если у вас есть реляционные данные и также некоторые данные, которые не всегда могут существовать в колонке, то такой подход может стать отличным решением. Например, в большинстве случаев атрибуты каталогов продукции могут быть прекрасным примером для такого типа данных. Тогда, для некоторых продуктов, таких как книги (которые вы храните в отдельной таблице “Products”) могут быть определены такие параметры, как жанр, год издания. В другом случае для продуктов, таких как одежда, которую вы тоже храните в этой же таблице, могут быть определены уже другие параметры — размер и цвет. Добавлять же столбец в таблицу продуктов для каждого возможного параметра избыточно и неоправданно.


JSON


Начиная с версии 9.2 в Postgres реализована поддержка JSON. Теперь, PostgreSQL может составить конкуренцию MongoDB. (Хотя функциональность JSON в PostgreSQL 9.2, конечно, немного преувеличенна. Об этом ниже.)


Тип данных в формате JSON в Postgres, если разобраться в значительной степени просто текстовое поле. Всё что вы получите с типом данных JSON так это валидацию значения при вставке. Postgres обеспечивает соблюдение формата JSON. Одним небольшим потенциальным преимуществом над JSONB (который мы рассмотрим следующим) является то, что JSON сохраняет отступы в данных, поступающих в БД. Так что если вы очень требовательны к форматированию ваших данных или вам необходимо сохранить запись в той или иной структуре, JSON может оказаться полезным.


Кроме того, с течением времени Postgres приобрёл ряд довольно полезных функций. Должны ли вы использовать JSON? Ведь, PostgreSQL-ный тип 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 это может быть довольно удобным.


Наиболее распространенные примеры использования:


  1. Отслеживание событий данных, добавляя изменяющийся payload события.
  2. Хранение игровых данные достаточно распространено, особенно там, где у вас есть одиночная игра и изменяющаяся схема данных на основе состояния пользователя.
  3. Инструменты, которые объединяют несколько источников данных, пример здесь может быть инструментом, который интегрирует несколько баз данных клиентов к 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 — Если вы обрабатываете логи, вам не часто приходится запрашивать данные или не нужно использовать их как что-то большее чем для задач логирования.
Hstore — отлично работает с текстовыми данными на основе представления ключ-значение, но в целом JSONB также отлично справляется с этой задачей.

Поделиться публикацией

Похожие публикации

Комментарии 12
    +3
    NoSQL (посредством HStore, JSON и JSONB)

    Чорт. Перестаньте называть тип данных json NoSQL!
    Это вполне себе «реляционный» тип. Он индексируется, он участвует в SQL-запросах.
      +2
      но значение в JSON-поле — это ведь уже документ! %) вот отсюда и «логика» ;) а то, что по части этого документа можно построить индекс или сделать выборку — это уже приятные плюшки постгреса.
        +1
        Это не документ, вы не в монге же. Это просто составной тип. Такой же, как, к примеру, массив int[]
          +1
          какие ваши аргументы?
            –1
            Документация по postgres. Там нет слова документ.
            Это сложный тип. Объект, если вам угодно (да-да, объектно-реляционная БД). Но это не документ.
            Перестаньте тащить ваши дурные подходы из монги в почтенную реляционную базу.
              +8
              8.14.2. Designing JSON documents effectively

              https://www.postgresql.org/docs/9.5/static/datatype-json.html

              концентрация метана превышена, милейший!
        0
        JSON это не реляционный тип. Это JS нотация. Каким это образом JSON участвует в SQL запросах? JSON имеет 3 типа — 2 типа объект: {} — множество (объект), [] — массив и 1 тип — значение.Всё! Можно ли реализовать SQL на JSON? Можно. Можно ли реализовать NoSQL на JSON? Тоже ответ — да!
          0
          Вы можете привести практичный пример использования в индексе/запросе непосредственно типа json/jsonb? Вы же в курсе, что в результате создания такого индекса планировщик ничего не будет знать о структуре документа и использовать его для поиска по свойствам вашего json вы не сможете?

          Другое дело — когда мы создаем функциональные индексы. Скажем, по doc->'item'->>'property'. Но под капотом — это всего лишь тип text полученный в результате выполнения функций json_object_field_text(json_object_field(…

          Может я чего-то не понимаю, но где здесь SQL? Смогу я в ANSI найти описание диалекта a->b->>c? А XPath найду, если буду создавать индексы по xpath('/item/@property')? Имхо, это чистой воды NoSQL в интерпретации «Not Only SQL».
          +1
          Маленькое замечание. В postgresql для json и jsonb нет синтаксиса, который бы позволил обновить только одно поле (поправьте меня если ошибаюсь). Документ нужно перезаписывать целиком. Поэтому, для исключения состояния гонки, приходится навешивать критические секции на код обновляющий json и jsonb. Для hstore же подобный синтаксис имеется.
            +2
            function jsonb_set(target jsonb, path text[], new_value jsonb[, create_missing boolean])
            единственный пока доступный вариант
              0
              В 9.5 теперь можно и так делать: select '{«a»: «b»}'::jsonb || '{«a»: «c»}'::jsonb
              Жаль на проде еще не 9.5 в моей конторе.
            +4
            Два года назад рассказывал в Токио про hstore и jsonb, может кому пригодится — http://www.sai.msu.su/~megera/postgres/talks/semi-structured-postgresql-japan.pdf

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое