Привет, Хабр!
Меня зовут Антон Виноградов, я Java developer в СберТехе, работаю в команде Platform V DataGrid — распределенной базы данных, основанной на Apache Ignite. Я реализовал альтернативный «ванильному» вариант сжатия данных в нашем продукте и хочу рассказать, какие особенности позволили превзойти оригинал.
В начале 2020 года мои коллеги уже рассказывали, как и зачем было организовано сжатие данных в Apache Ignite.
Сжатие данных в Apache Ignite
Кратко напомню, как мы сжимали данные раньше:
Cжатие данных производилось только на диске.
Специально увеличенные в размере страницы, занимающие несколько блоков файловой системы, например, размером в 16 Кб (4 x 4 Кб), при записи в хранилище сжимались до меньшего числа блоков файловой системы, например, до 12 Кб, 8 Кб или даже 4 Кб. Это происходило за счет использования файловых систем с разреженными файлами. При этом постраничная индексация прекрасно продолжала работать за счет того, что с точки зрения приложения страницы не изменяли свой размер.
Помимо этого, страницы сжимались и при записи в WAL, но уже без соблюдения кратности по отношению к блоку файловой системы, например, с 16 Кб до 5 Кб.
Однаĸо в WAL по-прежнему записывались:
несжатые дельты страниц (Δ), которые записываются вместо полной страницы при повторной записи в одну и ту же страницу в рамках одного чекпоинта;
несжатые логические записи (op), требующиеся для восстановления консистентности распределенной базы данных.
В итоге у нас появлялась возможность хранить больше данных на диске, но никаких других бонусов при этом мы не получали. И это подтолкнуло нас к реализации альтернативного подхода к сжатию данных, где решались бы и другие значимые для нас проблемы.
Альтернатива
В противоположность описанному варианту мы придумали сжимать отдельные записи в памяти:
В результате на страницах помещается больше данных:
Записываем на диск меньшее число страниц:
В итоге и в памяти, и на диске храним больше данных, а в 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) Невероятное уменьшение размеров WAL — неожиданный бонус, проявившийся именно на бенчмарках и кратно повышающий итоговую ценность решения.
4) Ускорение операций — следствие третьего пункта. Меньше пишем — быстрее работаем.
Даже на крутых SSD есть выигрыш по latency по отношению к любым другим вариантам:
На дисках похуже отрыв будет больше:
На дисĸе со «шпинделем» отрыв, по нашим предположениям, должен быть просто колоссальным.
Выводы
1) Сжатие значений в памяти не так эффективно, как сжатие целых страниц на диске «на бумаге», но кратно превосходит его в эффективности на реальных замерах.
Происходит это благодаря дополнительному сжатию WAL и использованию страниц меньшего размера. Грубо говоря, WAL сжимается максимально эффективно, уменьшаются все виды записей, содержащие данные. Это перевешивает тот факт, что сжатие целых страниц эффективнее сжатия отдельных значений.
И самое главное, мы достигаем первоначальной цели — уменьшения рисков неожиданной деградации системы из-за переполнения памяти и начавшегося Page Replacement.
2) Сжимать данные полезно не только для экономии места, но и для ускорения быстродействия системы.
Чем меньше вы пишете на диск, тем быстрее будут выполняться ваши операции записи. Следовательно, тем быстрее будут происходить и чтения, которые ждут своей очереди в thread pools.
WAL мы вынуждены писать синхронно. Чем больше записываем в WAL, тем хуже latency. Меньше всего в WAL (за весь бенчмарк) мы пишем при использовании нового сценария — 1,3 Гб против 2,6 Гб в старом.
3) Если ваша основная цель — минимизация объема содержимого на диске при малом числе операций записи (при околонулевой генерации WAL) и время выполнения операций чтения для вас не критично, то вам следует рассмотреть вариант использования «сжатия страниц на диске». При использовании старого сценария хранилище занимает 0,3 Гб против 0,5 Гб в новом.