Переизобретаем сжатие в распределенной базе данных

Привет, Хабр!

Меня зовут Антон Виноградов, я Java developer в СберТехе, работаю в команде Platform V DataGrid — распределенной базы данных, основанной на Apache Ignite. Я реализовал альтернативный «ванильному» вариант сжатия данных в нашем продукте и хочу рассказать, какие особенности позволили превзойти оригинал.

В начале 2020 года мои коллеги уже рассказывали, как и зачем было организовано сжатие данных в Apache Ignite.

Сжатие данных в Apache Ignite

Кратко напомню, как мы сжимали данные раньше:

Сжимаем страницы в хранилище на диске (кратно блоку файловой системы) и в WAL; не сжимаем страницы в памяти, дельты и логические записи в WAL.

Сжимаем страницы в хранилище на диске (кратно блоку файловой системы) и в WAL; не сжимаем страницы в памяти, дельты и логические записи в WAL.

Cжатие данных производилось только на диске.

Специально увеличенные в размере страницы, занимающие несколько блоков файловой системы, например, размером в 16 Кб (4×4 Кб), при записи в хранилище сжимались до меньшего числа блоков файловой системы, например, до 12 Кб, 8 Кб или даже 4 Кб. Это происходило за счет использования файловых систем с разреженными файлами. При этом постраничная индексация прекрасно продолжала работать за счет того, что с точки зрения приложения страницы не изменяли свой размер.

Помимо этого, страницы сжимались и при записи в WAL, но уже без соблюдения кратности по отношению к блоку файловой системы, например, с 16 Кб до 5 Кб.

Однаĸо в WAL по-прежнему записывались:

  • несжатые дельты страниц (Δ), которые записываются вместо полной страницы при повторной записи в одну и ту же страницу в рамках одного чекпоинта;

  • несжатые логические записи (op), требующиеся для восстановления консистентности распределенной базы данных.

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

Альтернатива

В противоположность описанному варианту мы придумали сжимать отдельные записи в памяти:

Сжимаем записи в памяти

Сжимаем записи в памяти

В результате на страницах помещается больше данных:

Используем освободившееся в странице место для хранения дополнительных данных

Используем освободившееся в странице место для хранения дополнительных данных

Записываем на диск меньшее число страниц:

На диск (хранилище, WAL) и в сеть попадают уже сжатые данные

На диск (хранилище, WAL) и в сеть попадают уже сжатые данные

В итоге и в памяти, и на диске храним больше данных, а в WAL пишем заметно меньше:

Нажми, чтобы сравнить с тем как было раньше

b7afa46d952318b30bdea1219bcd3ad8.png

Сжимаем данные в хранилище (память, диск) и в WAL (физические, логические записи и дельты)

Сжимаем данные в хранилище (память, диск) и в WAL (физические, логические записи и дельты)

  • Страницы в WAL продолжают сжиматься, как и в первом описанном варианте, но сжимаются не с 16 Кб до 5Кб, а с 4 Кб до 2 КБ; записать 2 Кб — значительно быстрее, чем 5 Кб.

  • Логические записи в WAL (op), как и дельты страниц (Δ), теперь меньше благодаря тому, что значения уже сжаты при первоначальной записи в память.

Как следствие, в WAL пишем меньше, чем в любом другом варианте со сжатием и без.

Данные на диске не так хорошо сжимаются, как в первом варианте, но зато сжимаются в памяти. А чем больше данных вмещается в память, тем позже наступает Page Replacement — механизм, который при переполнении памяти подгружает нужную страницу с диска, заменяя ею менее востребованную. Уже начавшийся Page Replacement приводит к значительной дергадации быстродействия, поэтому минимизация этого риска — очень важный бонус.

Чего мы добились в итоге

Мы сравнили оба варианта сжатия — «ванильный» сценарий сжатия страниц на дисĸе и наш новый сценарий сжатия значений в памяти.

  • Мы добавляли данные в пропорции 1:4 к их чтению.

  • Использовали алгогитм сжатия SNAPPY.

  • Комбинировали размер страницы (4 Кб, 8 Кб и 16 Кб) и различные подходы к сжатию (None — без сжатия, Value — сжатие значений, Page — страниц, WAL — журнала изменений).

  • Использовали данные в формате: Ключ — Integer, Значение — String (размером в 1 Кб, сжимаемая до ~380 байт).

И получили следующие результаты:

1) При использовании нового подхода к сжатию уменьшается потребление памяти, что и было первоначальной целью. Это увеличивает зазор до Page Replacement.

Потребление оперативной памяти уменьшилось втрое, по сравнению с несжатой страницей

Потребление оперативной памяти уменьшилось втрое, по сравнению с несжатой страницей

2) Неплохое сжатие данных на диске — очень приятный бонус к первоначальной цели.

В 3 раза меньшее использование диска, по сравнению с несжатыми данными, близко к варианту сжатия страниц на диске.

В 3 раза меньшее использование диска, по сравнению с несжатыми данными, близко к варианту сжатия страниц на диске.

3) Невероятное уменьшение размеров WAL — неожиданный бонус, проявившийся именно на бенчмарках и кратно повышающий итоговую ценность решения.

В три раза по отношению к варианту без сжатия и в два раза — к варианту сжатия страниц.

В три раза по отношению к варианту без сжатия и в два раза — к варианту сжатия страниц.

4) Ускорение операций — следствие третьего пункта. Меньше пишем — быстрее работаем.

Даже на крутых SSD есть выигрыш по latency по отношению к любым другим вариантам:

Быстрый SSD

Быстрый SSD

На дисках похуже отрыв будет больше:

Медленный SSD

Медленный SSD

На дисĸе со «шпинделем» отрыв, по нашим предположениям, должен быть просто колоссальным.

Выводы

1) Сжатие значений в памяти не так эффективно, как сжатие целых страниц на диске «на бумаге», но кратно превосходит его в эффективности на реальных замерах.

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

И самое главное, мы достигаем первоначальной цели — уменьшения рисков неожиданной деградации системы из-за переполнения памяти и начавшегося Page Replacement.

2) Сжимать данные полезно не только для экономии места, но и для ускорения быстродействия системы.

Чем меньше вы пишете на диск, тем быстрее будут выполняться ваши операции записи. Следовательно, тем быстрее будут происходить и чтения, которые ждут своей очереди в thread pools.

WAL мы вынуждены писать синхронно. Чем больше записываем в WAL, тем хуже latency. Меньше всего в WAL (за весь бенчмарк) мы пишем при использовании нового сценария — 1,3 Гб против 2,6 Гб в старом.

3) Если ваша основная цель — минимизация объема содержимого на диске при малом числе операций записи (при околонулевой генерации WAL) и время выполнения операций чтения для вас не критично, то вам следует рассмотреть вариант использования «сжатия страниц на диске». При использовании старого сценария хранилище занимает 0,3 Гб против 0,5 Гб в новом.

© Habrahabr.ru