- PVSM.RU - https://www.pvsm.ru -

Привет! Меня зовут Александр Кленов, и я работаю в Tarantool. В апреле вышел Tarantool 2.10 Enterprise Edition – обновленная версия платформы in-memory вычислений. В версии 2.10 появилось несколько новых функций, о которых уже немного рассказывали на Хабре [1].
В этой статье я хочу подробнее остановиться на одной из фичей — сжатии данных в оперативной памяти. Далее я расскажу, как ей пользоваться, что может, а чего не может данный механизм, как его применять и какие существуют особенности.
Все просто — нужно указать в настройках поля, что его надо сжимать: compression = '<вариант>'.
Доступно два варианта сжатия:
zstd,
lz4.
Например:
local space = box.schema.space.create(space_name, { if_not_exists = true })
space:format({
{name = 'uid', type = 'string'},
{name = 'body', type = 'string', compression = 'zstd'},
})
Если это не новый, а существующий проект с данными, понадобится также вызвать фоновую миграцию:
box.schema.func.create('noop', {
is_deterministic = true, body = 'function(t) return t end'
})
space:upgrade{func = 'noop', format = {
{name = 'uid', type = 'string'},
{name = 'body', type = 'string', compression = 'zstd'},
}}
Подробнее смотрите в документации [2]
Итак, мы подключили фичу. Давайте посмотрим, что хорошего она может нам дать. В качестве примера возьмем реальный проект – данные крупного телеком-оператора. Это 100 тысяч различных JSON-документов общим объемом 316 МБ.
По очереди мы применим разные варианты сжатия. CPU в нашем случае = 3,6 ГГц. Для лучшего сравнения методов добавим сжатие с помощью внешней библиотеки ZLIB.
Тестирование показало следующие результаты:
|
Метод |
Время упаковки и записи в таблицу, сек |
Время чтения и распаковки, сек |
Конечный размер спейса, МБ |
Коэффициент сжатия |
Сжатие, % |
|
ZSTD |
61.222 |
0.889 |
113.234 |
2.790 |
64.16 |
|
LZ4 |
7.701 |
0.963 |
186.499 |
1.694 |
40.97 |
|
ZLIB* |
16.119 |
3.528 |
121.958 |
2.590 |
61.40 |
* Внешняя библиотека ZLIB


Исходник теста можно посмотреть на Гитхабе [3].
Ключевой вывод – все три метода работают по-разному. ZSTD сжимает эффективно, но медленно и при этом достаточно быстро распаковывает документы. LZ4 – быстро сжимает и разжимает, но коэффициент сжатия уступает остальным методам. Сжатие с использованием ZLIB сопоставимо с результатами ZSTD и при этом не слишком медленно, однако проседает скорость разжатия.
В реальных продуктовых проектах экземпляры Tarantool разделены по ролям. Для нашего примера важно выделить две:
маршрутизатор (router),
хранилище (storage).
Сжатие данных в кластере – не такая линейная задача, как сжатие одного узла. Это можно организовать разными способами в зависимости от места сжатия и разжатия. У каждого способа есть свои плюсы и минусы. Рассмотрим три варианта.

Сжимаем и разжимаем в хранилище. Этот вариант обеспечивает описываемая в статье функциональность. Подходит, если нужно быстро и при ничтожных изменениях проекта высвободить кучу места.
Звучит здорово, но это вызывает дополнительную нагрузку на ЦП хранилища и его замедление. Более того, каждая реплика будет повторять то же самое сжатие. Как следствие — снижение производительности кластера. Хранилище со встроенным упаковщиком дороже масштабировать. То есть вместо одного дополнительного упаковщика придется поставить рядом хранилище с упаковщиком.
Посмотреть пример, как сделать, можно здесь:
Локальная переменная enable_zlib в роутере должна иметь значение = false. В хранилище в 11-й строке нужно указать тип сжатия.
Такой вариант подойдет – если срочно нужно освободить место. То есть как временное решение. Если нагрузка позволяет – это может быть постоянным решением. Это про быстро и дешево.
Если вы хотите сохранить производительность кластера, потребуется больше усилий.

Сжимаем и разжимаем на роутере. Это дает больше производительности, но встроенное сжатие никак не поможет — придется подключать внешнюю библиотеку и реализовывать дополнительную логику работы с ней. В мастер и на реплики тогда будут попадать уже упакованные данные.
В качестве примера можно использовать пару роутер [4]-хранилище [5], приведенную выше. Только переменная enable_zlib в роутере должна иметь значение = true. В хранилище тип сжатия не указываем.
Плюсы:
не нагружаем хранилище;
уменьшаем трафик между роутером и стораджем;
легче масштабируем — ставим нужное количество роутеров с упаковщиком до требуемой производительности.
Минусы:
нужно реализовать логику упаковщика на роутере;
появляется задержка на роутере на сжатие и распаковку.
Если хочется большего, то можно управлять сжатием в виде отдельной роли. Это позволит регулировать число роутеров отдельно от числа инстансов сжатия. Более гибко регулировать утилизацию ресурсов. В этом случае можно рассмотреть третий вариант.

Сжимаем на отдельном экземпляре, разжимаем на роутере. То есть пишем в хранилище как есть с признаком «не упакован». Далее реализуем отдельную роль «упаковщик», которая проходит по мастер-хранилищам и упаковывает все не упакованное. Если данные поступают быстро и надо паковать быстрее — добавляем ещё экземпляры упаковщика. На реплики будут попадать уже упакованные данные.
Посмотреть пример реализации, чтобы понять идею, можно здесь [6].
Его можно нагрузить тем же нагрузочным тестом. Результаты он покажет как при записи без сжатия, потому что сжатие происходит не при записи, а в отдельном параллельном процессе.
Плюсы:
нет задержки на роутере на запись данных;
процесс сжатия масштабируется максимально выгодно;
меньше экземпляров кластера;
не нагружаем хранилище как в первом варианте.
Минусы:
дополнительный трафик в кластере — между хранилищем и упаковщиком;
некоторая дополнительная нагрузка на хранилище — плюс по одной операции чтения и записи;
нужно реализовать отдельную роль — упаковщик;
нужно реализовать логику распаковки на роутере.
Это может быть полезно:
если вы хотите быстро записывать данные - сразу записывать не сжимая;
регулировать скорость сжимания - либо уменьшить число инстансов, либо наоборот - увеличить, и через это более гибко расходовать свои ресурсы.
На фоне предложенных вариантов хочется проследить эффект от переноса сжатия с хранилища на роутер, как наиболее производительный. Давайте соберем маленький кластер на два экземпляра — роутер и хранилище. И попробуем нагрузить его при разных способах упаковки.
Реализация стенда:
Реализация роутера [4].
Реализация хранилища [5].
Модуль для нагрузочного обстрела на запись модуль [7]. И вспомогательный модуль для генерации данных [8].
Максимальная продолжительность испытаний выбрана 30 секунд. Потому, что с одной стороны не слишком долго ждать выполнения тестов, с другой – этого достаточно для тестирования нагрузки. За это время мы генерируем 100 тысяч запросов на запись на роутер от лица 10 виртуальных пользователей. Консольный вывод результатов можно посмотреть здесь [9]. Ниже результаты сведены в одну таблицу.
|
Показатель Сценарий |
Без сжатия |
ZLIB |
ZSTD |
LZ4 |
|
Запросов выполнено, шт |
100000 |
100000 |
46964 |
100000 |
|
Длительность выполнения запросов, сек |
14.1 |
27.8 |
30.0 |
15.7 |
|
K6 data_received, MB |
23 |
23 |
11 |
23 |
|
K6 data_sent, MB |
341 |
342 |
160 |
341 |
|
http_req_blocked, µs |
2.46 |
1.81 |
2.12 |
2.17 |
|
http_req_connecting, ns |
13 |
7 |
20 |
16 |
|
http_req_duration, ms |
0.716 |
2.27 |
5.78 |
0.934 |
|
http_req_failed, % |
0 |
0 |
0 |
0 |
|
http_req_receiving, µs |
31.1 |
25.59 |
25.8 |
28.71 |
|
http_req_sending, µs |
20.08 |
16.78 |
19.42 |
19.22 |
|
http_req_waiting, ms |
0.665 |
2.22 |
5.74 |
0.886 |
|
RPS |
7076 |
3595 |
1565 |
6351 |
|
iteration_duration, ms |
1.4 |
2.77 |
6.38 |
1.56 |
Итак мы видим, что по ключевым параметрам, таким как число запросов в секунду (RPS), задержке (http_req_duration) и общему времени выполнения запроса (iteration_duration) - вариант со сжатием на роутере выгодно отличается от сжатия в хранилище.
В первую очередь это происходит потому, что сжатие методом ZSTD происходит дольше, чем методом ZLIB. Также свой вклад вносит то, что данные от роутера до хранилища идут уже сжатые и таким образом в само хранилище нужно записывать меньше данных. Также плюсом идёт то, что не нужно нагружать хранилище сжатием.
Tarantool развивается и обрастает новыми фичами. Встроенный в 2.10EE механизм сжатия полезен, но не является панацеей. Порядок действий зависит от конкретной ситуации — отсюда и возникают рекомендации по применению.
Если у нас нет желания или возможности использовать внешнюю библиотеку - мы можем использовать встроенное сжатие методом LZ4. Данный метод позволяет высвободить 40% места почти без просадки по RPS (6351 LZ4 против 7076 без сжатия). При этом включается фактически «одной галочкой» — на мой взгляд весьма неплохо.
Если требуется жать сильнее, или производительнее, или дешевле — нужны дополнительные усилия, которые описаны выше.
Скачать Tarantool можно на официальном сайте [10], а получить помощь — в Telegram-чате [11].
Автор:
1div0
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/klaster/376258
Ссылки в тексте:
[1] рассказывали на Хабре: https://habr.com/ru/company/vk/blog/663240/
[2] смотрите в документации: https://www.tarantool.io/en/enterprise_doc/tuple_compression/
[3] на Гитхабе: https://github.com/a1div0/habr-post-tnt-compress/blob/master/one_instance.lua
[4] роутер: https://github.com/a1div0/habr-post-tnt-compress/blob/master/router.lua
[5] хранилище: https://github.com/a1div0/habr-post-tnt-compress/blob/master/storage.lua
[6] здесь: https://github.com/a1div0/habr-post-tnt-compress/tree/master/variant-3
[7] Модуль для нагрузочного обстрела на запись модуль: https://github.com/a1div0/habr-post-tnt-compress/blob/master/k6-test-write.js
[8] вспомогательный модуль для генерации данных: https://github.com/a1div0/habr-post-tnt-compress/blob/master
[9] здесь: https://github.com/a1div0/habr-post-tnt-compress/blob/master/k6-results.md
[10] на официальном сайте: https://www.tarantool.io/ru/download?utm_source=habr&utm_medium=articles&utm_campaign=2021
[11] в Telegram-чате: https://t.me/tarantoolru?utm_source=habr&utm_medium=articles&utm_campaign=2021
[12] Источник: https://habr.com/ru/post/672760/?utm_source=habrahabr&utm_medium=rss&utm_campaign=672760
Нажмите здесь для печати.