Недавно мы выложили на GitHub ZSON. ZSON — это расширение к PostgreSQL для прозрачного сжатия JSONB-документов. Сжатие осуществляется путем выделения строк, наиболее часто встречающихся в ваших документах, и построения словаря с этими строками. Притом строки могут быть не только ключами документа, но и значениями или, например, строками из вложенных массивов. В некоторых случаях ZSON позволяет уменьшить размер базы до двух раз и увеличить количество транзакций в секунду на 10%. В shared buffers документы хранятся в сжатом виде, за счет чего память тоже экономится.

Интересно? Читайте дальше, и вы узнаете, как пользоваться всем этим хозяйством на практике.

Замечания


Прежде, чем перейти к основному содержимому статьи, хочется отметить несколько моментов:

  • Бенчмарк ZSON'а в данной статье не приводится. Заинтересованные читатели могут ознакомиться с ним здесь. Примите во внимание, что на практике все очень сильно зависит от ваших данных, конфигурации, железа, версии СУБД и других факторов. Не верьте синтетическим бенчмаркам, проверяйте все самостоятельно!

  • Вопрос написания расширений для PostgreSQL выходит за рамки этой статьи. Заинтересованные читатели могут ознакомиться с отдельной статьей по данной теме, и далее по ссылкам. Тому, как ZSON работает внутри, если это кому-то интересно, я могу посвятить отдельную статью.

  • PostgreSQL имеет встроенный алгоритм сжатия — PGLZ. ZSON не заменяет, а дополняет его. PGLZ сжимает каждый документ в отдельности. Он не может обнаружить, что в разных документах встречаются одни и те же строки. ZSON находит эти строки и заменяет их на 16-и битовые коды. Затем документы сжимаются PGLZ, как обычно (или не сжимаются, если итоговый документ маленький и/или не жмется).

Установка


Сборка ZSON из исходников и его установка осуществляются так:

git clone https://github.com/afiskon/zson.git
cd zson
sudo make install

После установки прогоните тесты:

make installcheck

Включите ZSON для вашей базы данных:

create extension zson;

Поздравляю, ZSON установлен!

Удаление


Когда и если вы захотите удалить ZSON, просто выключите его:

drop extension zson;

А затем совсем выпилите из PostgreSQL:

sudo make uninstall

Все созданные ZSON'ом типы, таблицы и так далее, будут вычищены автоматически.

Использование


Первым делом вы должны обучить ZSON на ваших типичных документах. В результате обучения строится словарь с наиболее часто встречающимися в ваших документах строками, который потом используется для сжатия. Обучение происходит с помощью следующей процедуры:


zson_learn(
    tables_and_columns text[][],
    max_examples int default 10000,
    min_length int default 2,
    max_length int default 128,
    min_count int default 2
)

Например:

select zson_learn('{{"table1", "col1"}, {"table2", "col2"}}');

Посмотреть получившийся в итоге словарь можно так:

select * from zson_dict;

Теперь вы можете использовать ZSON, как прозрачную замену типа JSONB:

zson_test=# create table zson_example(x zson);
CREATE TABLE

zson_test=# insert into zson_example values ('{"aaa": 123}');
INSERT 0 1

zson_test=# select x -> 'aaa' from zson_example;
-[ RECORD 1 ]-
?column? | 123

Все операторы и процедуры будут работать с ZSON точно так же, как и с JSONB.

Миграция на новый словарь


По мере изменения схемы ваших документов сжатие может стать неэффективным из-за исчезновения одних строк и появления других. В этом случае вы можете переобучить ZSON на новых данных:

select zson_learn('{{"table1", "col1"}, {"table2", "col2"}}');

При этом создается новая версия словаря. При обновлении и создании новых документов они будут сжиматься с его помощью. Старые документы будут разжиматься с помощью той версии словаря, с которой они были сжаты. Обратите внимание, что словари кэшируются в памяти. Поэтому ZSON узнает о новом словаре не сразу, а спустя примерно минуту после его создания.

Определить, с помощью какой версии словаря был сжат конкретный документ, можно при помощи процедуры zson_info:

zson_test=# select zson_info(x) from test_compress where id = 1;
-[ RECORD 1 ]---------------------------------------------------
zson_info | zson version = 0, dict version = 1, ...

zson_test=# select zson_info(x) from test_compress where id = 2;
-[ RECORD 1 ]---------------------------------------------------
zson_info | zson version = 0, dict version = 0, ...

Если вы абсолютно уверены, что все документы в вашей базе сжаты с помощью новой версии словаря, вы можете безопасно удалить старый словарь:

delete from zson_dict where dict_id = 0;

На практике, впрочем, в этом мало смысла. Вы сэкономите лишь пару килобайт места на диске. Параноик внутри меня считает, что риск потерять данные из-за маааленькой невнимательности того не стоит.

Как понять, что нужно обновить словарь?


К сожалению, трудно рекомендовать универсальный подход. Например, вы можете время от времени проверять средний размер документа в вашей базе:

select pg_table_size('tt') / (select count(*) from tt)

Если он стал увеличиваться, значит пришло время обновиться словарь. Еще можно переучивать ZSON просто время от времени, например, раз в год.

Наконец, разработчики приложения сами знают, когда они сильно меняют схему документов. Можно включить переобучение ZSON в миграционные скрипты или инструкцию по обновлению приложения.

При сильном желании можно придумать и другие подходы. В общем, все сильно зависит от ситуации.

Заключение


Как видите, благодаря ZSON вы получаете лучшее из двух миров — гибкость schemaless данных, совмещенную с компактностью реляционной модели. При этом на стороне приложения вообще ничего не нужно делать, все происходит прозрачно, на стороне самой СУБД.

Если у вас есть вопросы, я буду рад ответить на них в комментариях. Любой фидбек, а также pull requests горячо приветствуются.
Поделиться с друзьями
-->

Комментарии (30)


  1. pasha_golub
    07.10.2016 15:02
    +1

    В ваших тестах выключен автовакуум. Это умышленно? Есть проблемы с вакуумом у типа?


    1. afiskon
      07.10.2016 15:09

      Это умышленно, но не из-за проблем типа. Автовакуум на всех бенчмарках приводит к тому, что бенчмарк показывает не то, что вы думаете (работу автовакуума, а не тестируемого функционала). В теории автовакуум должен работать даже быстрее, так как размер туплов уменьшается и производится меньше дискового IO, но конкретных замеров я лично не делал.


  1. yul
    07.10.2016 15:08
    +2

    С индексами работает?


    1. afiskon
      07.10.2016 15:09
      +2

      Да, конечно.


  1. alprk
    07.10.2016 15:51
    +1

    А есть такое же для xml?


    1. afiskon
      07.10.2016 16:06

      Пока нет. Но по тому же принципу делается не сложно. А Вам правда нужно или просто интересуетесь?


      1. alprk
        07.10.2016 16:21
        +2

        Действительно пригодилось бы. Но возможно мою проблему можно решить конвертацией XML в JSON


      1. SXN
        10.10.2016 10:42

        Если и xml будут тогда уже будет отлично(полноценной). Спасибо за хороший фитчи


  1. btd
    07.10.2016 19:19
    +1

    А почему похожего нету в самом PG?


    1. afiskon
      07.10.2016 21:01

      Никто еще не добавил :) В рассылке предлагают включить zson, расширенный для xml и text, в базовую поставку.


    1. Komzpa
      08.10.2016 09:52

      Возможно, потому что zson_learn() выглядит, как ужасный костыль.


      1. afiskon
        08.10.2016 11:08

        Komzpa ну вы же знаете выражение — patches are welcome! :)


  1. tsabir
    07.10.2016 22:10
    +1

    Не могу понять, что нужно передавать в tables_and_columns? Если это названия таблиц и колонок в них, то пример {{'table1', 'row1'}, ...} не совсем удачный. Может лучше {{'table1', 'col1'}, ..}? Поясните пожалуйста.


    1. afiskon
      07.10.2016 22:15

      Это, конечно же, просто косяк в тексте, имелся ввиду col. Исправил, спасибо!


  1. Ranckont
    10.10.2016 14:08

    А как лучше содержимое zson_learn перенести с тестовой на продуктивную БД?


    1. afiskon
      10.10.2016 14:08

      Да как вам удобнее так и переносите. Через COPY например.


      1. Ranckont
        10.10.2016 14:16

        А проблем с какими-нибудь zson version/dict version или id, не будет?


        1. afiskon
          10.10.2016 14:32

          Если вы не обучаете zson и на тестовом окружении и на проде одновременно, то проблем быть не должно. Иначе перед обучением на тестовом окружении сначала перенесите полностью словарь с прода.

          И конечно же, на все всегда делайте бэкапы!


          1. Ranckont
            10.10.2016 15:16

            Я так понял, что «My_schema».«Table» — работать не будет?


            1. afiskon
              10.10.2016 15:22

              Не уверен, что понял ваш вопрос. Если вы хотите включить расширение для схемы, то насколько я знаю, это работать не будет.


              1. Ranckont
                10.10.2016 15:26

                Ну да, не у всех в public данные.
                А как прикрутить?


                1. afiskon
                  11.10.2016 10:58

                  Вижу вы послали патч в ЛС. Я его замержил, спасибо!


                  1. Ranckont
                    11.10.2016 11:42

                    Там надо ещё подумать над тем, что таблица public.zson_dict, становится частью данных другой схемы, что становится как-то неправильно.
                    Или принудительно её создавать в используемых схемах или как-то в документации отобразить.


                    1. afiskon
                      11.10.2016 11:46

                      Patches are welcome ;)


                      1. Ranckont
                        11.10.2016 11:57

                        я посмотрел мельком код, я так понял название таблицы используется не только в plpgsql, но и в C
                        Не думаю, что правильнее править мне, т.к. получается, что я не во всём в курсе


  1. Ranckont
    10.10.2016 16:45

    Я думаю, можно вставить какой-нибудь шаблон, для исключения из словаря данных величин, которые будут заведомо единственные, скажем результат какой-либо последовательности.
    Или потом удалить их словаря.


    1. afiskon
      11.10.2016 10:45

      Сейчас у zson_learn есть параметр, определяющий минимальное количество раз, которое строка должна встречаться в документах (по дэфолту 2, можно указать другое значение).


      1. Ranckont
        11.10.2016 11:32

        Чего-то не работает


  1. postgrez4ik
    10.10.2016 22:34

    в постгрез будете добавлять?


    1. afiskon
      11.10.2016 10:44

      Попробую, но заранее ничего не обещаю :)