Нагрузочное тестирование плагина Radix для СУБД Picodata

Radix — плагин для распределённой СУБД Picodata, реализующий протокол Redis. Благодаря ему приложения, использующие Redis, могут работать с Picodata без изменения кода.

Мы сравнили Radix 1.0.5 и Redis 8.8.0 на двух базовых командах, GET и SET, в кластере из 8 узлов, на сжимаемых и несжимаемых данных. Radix выдаёт от 76% до 170% пропускной способности Redis, а на крупных значениях кластер Redis уходит в отказ и перестаёт отвечать, тогда как Radix продолжает работать.

Пропускная способность:

ТестRedis, RPSRadix, RPS
get-100b197 730149 665
set-100b184 674144 118
get-10k140 737113 533
set-10k62 137106 049
get-256k8 8928 215
set-256k*885799

Время выполнения запросов, мс:

ТестRedis, p50Radix, p50Redis, p99Radix, p99Redis, максRadix, макс
get-100b0,91,11,74,43844
set-100b1,01,32,42,692692
get-10k1,31,52,05,26538
set-10k1,51,727,63,43 260301
get-256k4,44,95,25,45429
set-256k*3,79,9238,6376,828 3124 686

Названия тестов расшифрованы в разделе «Набор тестов».

* На 256 КБ с надёжностью обе системы теряют ~90% пропускной способности. Однако кластер Redis завершил без ошибок лишь один прогон из пяти: в остальных уходил в CLUSTERDOWN и возвращал клиентам ошибки. Radix прошёл без единой ошибки оба своих прогона. Подробнее в разделе «Стабильность при перегрузке».

Введение

Мы оценивали производительность Radix в сравнении с Redis 8.8.0, эталонной реализацией протокола, на операциях чтения (GET) и записи (SET). Измеряли пропускную способность, время выполнения запросов, потребление CPU и RAM и нагрузку на дисковую подсистему.

Radix не ставит перед собой задачу обогнать Redis на его домашней территории — эффективном кэшировании разделяемых структур данных. Radix — плагин к полнофункциональной транзакционной СУБД, что даёт качественно иной уровень надёжности хранения, безопасности и управляемости данных. Задача тестирования — убедиться, что производительность Radix сопоставима с производительностью Redis.

Для тестирования мы использовали утилиту memtier_benchmark 2.4.4 с флагами --random-data и --cluster-mode. Основные измерения мы проводили на случайных данных (флаг --random-data), потому что сжимаемость данных сильно влияет на результаты. Этому влиянию посвящён отдельный раздел «Влияние сжимаемости данных». Также нам было интересно выяснить цену надёжности, поэтому обе системы мы тестировали как с записью данных на диск, так и без неё.

Условия

Стенд

Все компоненты — обе базы данных и генератор нагрузки — работали на одной виртуальной машине. Влияние генератора нагрузки на потребление CPU было незначительным, и мы решили им пренебречь.

РесурсЗначение
CPUAMD EPYC, 32 ядра / 64 потока
RAM96 ГБ
ДискSATA SSD
ОСUbuntu 24.04

Конфигурация систем

Обе системы мы развернули как кластер из 8 узлов на одной машине и тестировали в двух режимах с попарно согласованными гарантиями надёжности:

СистемаТопология
Redis 8.8.0Кластер из 8 узлов: 4 мастера + 4 реплики
Radix 1.0.5 на Picodata 26.1.4Кластер из 8 инстансов: 4 репликасета × фактор репликации 2
РежимRedisRadix
Без надёжностиAOF и RDB выключеныданные в UNLOGGED-таблицах
С надёжностьюAOF (appendfsync everysec) + RDBжурнал WAL + снапшоты

В режиме «с надёжностью» в обеих системах запись переживает падение процесса. Radix использует движок memtx, в котором все данные хранятся в памяти, а надёжность обеспечивают периодические снапшоты и журнал предзаписи.

Входные данные

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

Инструмент

Каждый тест работал с одной командой, только GET или только SET, и длился 120 секунд. Для каждой пары «команда × размер» мы выполнили несколько прогонов и включили в отчёт лучший. Точные команды запуска приведены в разделе «Результаты».

Набор тестов

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

ТестКомандаРазмер значенияЧисло ключей
get-100bGET100 байт1 000 000
set-100bSET100 байт1 000 000
get-10kGET10 КБ200 000
set-10kSET10 КБ200 000
get-256kGET256 КБ20 000
set-256kSET256 КБ20 000

Измеряемые параметры

КатегорияПараметры
Пропускная способностьЗапросов в секунду (RPS)
Время выполнения запросовp50, p99, min, max
CPUЗагрузка CPU всего стенда, %
RAMИспользованная память всего стенда, ГБ
ДискЗапись на диск: МБ/с, IOPS

Генератор нагрузки работал на той же машине, что и база данных, поэтому значения CPU и RAM относятся ко всему стенду и включают потребление memtier_benchmark.

Результаты

Разберём результаты подробнее. Начнём с режима «с надёжностью», в котором данные переживают сбой. Режим без надёжности мы рассмотрим в разделе «Цена надёжности».

GET и SET: пропускная способностьПропускная способность GET и SET по размерам значений; кластер, с надёжностью, несжимаемые данные

100 байт

Сценарий моделирует кэш мелких объектов: токены, счётчики, флаги, идентификаторы сессий.

Команда для запуска теста (SET; для Redis отличается только порт):

memtier_benchmark -s 127.0.0.1 -p 7301 --cluster-mode \
    --command="set __key__ __data__" --command-ratio=1 --command-key-pattern=R \
    --key-prefix=mt:set: --key-minimum=1 --key-maximum=1000000 \
    -d 100 --random-data \
    -t 4 -c 12 --test-time=120 \
    --print-percentiles=50,95,99 --hide-histogram

Тест GET отличается командой (--command="get __key__", префикс ключей mt:get:) и не использует --random-data: он читает данные, загруженные на этапе подготовки.

get-100bRedisRadix
RPS197 730149 665
время выполнения p50, мс0,911,10
время выполнения p99, мс1,704,38
время выполнения min, мс0,050,03
время выполнения max, мс3844
CPU стенда сред., %2422
CPU стенда макс., %4725
RAM стенда сред., ГБ4,71,8
RAM стенда макс., ГБ4,71,8
Телеметрия GET — 100 байт

GET, пропускная способность

GET, задержка

GET, CPU

GET, RAM

set-100bRedisRadix
RPS184 674144 118
время выполнения p50, мс1,001,27
время выполнения p99, мс2,402,56
время выполнения min, мс0,110,06
время выполнения max, мс92692
CPU стенда сред., %2868
CPU стенда макс., %4178
RAM стенда сред., ГБ4,51,1
RAM стенда макс., ГБ4,61,4
запись на диск сред., МБ/с60,849,8
запись на диск макс., МБ/с149652
запись на диск сред., IOPS16987
запись на диск макс., IOPS3601 255
Телеметрия SET — 100 байт

SET, пропускная способность

SET, задержка

SET, CPU

SET, RAM

SET, диск

На 100-байтных значениях Redis быстрее и на чтении, и на записи, и заметно экономнее по CPU на записи: когда объём полезной нагрузки минимален, фиксированные накладные расходы Radix на операцию (транзакция движка, репликация) гораздо сильнее сказываются на потреблении ресурсов. При этом Radix держит 76–78% скорости Redis и ощутимо выигрывает по памяти: 1,8 против 4,7 ГБ на чтении и 1,1 против 4,5 ГБ на записи.

10 КБ

Сценарий моделирует хранение сериализованных объектов: JSON-документы, protobuf-сообщения, профили пользователей, фрагменты страниц.

Команда для запуска теста (SET; для Redis отличается только порт):

memtier_benchmark -s 127.0.0.1 -p 7301 --cluster-mode \
    --command="set __key__ __data__" --command-ratio=1 --command-key-pattern=R \
    --key-prefix=mt:set: --key-minimum=1 --key-maximum=200000 \
    -d 10000 --random-data \
    -t 4 -c 12 --test-time=120 \
    --print-percentiles=50,95,99 --hide-histogram
get-10kRedisRadix
RPS140 737113 533
время выполнения p50, мс1,321,48
время выполнения p99, мс2,025,25
время выполнения min, мс0,140,06
время выполнения max, мс6538
CPU стенда сред., %2723
CPU стенда макс., %6836
RAM стенда сред., ГБ8,54,9
RAM стенда макс., ГБ8,54,9
Телеметрия GET — 10 КБ

GET, пропускная способность

GET, задержка

GET, CPU

GET, RAM

set-10kRedisRadix
RPS62 137106 049
время выполнения p50, мс1,531,70
время выполнения p99, мс27,653,38
время выполнения min, мс0,180,09
время выполнения max, мс3 260301
CPU стенда сред., %4673
CPU стенда макс., %6981
RAM стенда сред., ГБ6,44,7
RAM стенда макс., ГБ9,26,0
запись на диск сред., МБ/с1 337944
запись на диск макс., МБ/с2 2161 876
запись на диск сред., IOPS1 3951 232
запись на диск макс., IOPS2 2962 700
Телеметрия SET — 10 КБ

SET, пропускная способность

SET, задержка

SET, CPU

SET, RAM

SET, диск

На этом размере Radix обгоняет Redis по записи сразу и по пропускной способности, и по задержке: 106 тыс. RPS против 62 тыс., p99 3,4 мс против 27,6 мс, максимальная задержка 0,3 секунды против 3,3 секунды. По памяти у Redis заметны пики — до 9,2 ГБ против ровных 6,0 ГБ у Radix.

256 КБ

Сценарий моделирует хранение крупных объектов: готовые HTML-страницы, миниатюры изображений, большие документы.

Команда для запуска теста (SET; для Redis отличается только порт):

memtier_benchmark -s 127.0.0.1 -p 7301 --cluster-mode \
    --command="set __key__ __data__" --command-ratio=1 --command-key-pattern=R \
    --key-prefix=mt:set: --key-minimum=1 --key-maximum=20000 \
    -d 256000 --random-data \
    -t 2 -c 5 --test-time=120 \
    --print-percentiles=50,95,99 --hide-histogram
get-256kRedisRadix
RPS8 8928 215
время выполнения p50, мс4,454,90
время выполнения p99, мс5,225,41
время выполнения min, мс0,900,82
время выполнения max, мс5429
CPU стенда сред., %1022
CPU стенда макс., %1985
RAM стенда сред., ГБ14,426,7
RAM стенда макс., ГБ14,426,7
Телеметрия GET — 256 КБ

GET, пропускная способность

GET, задержка

GET, CPU

GET, RAM

set-256kRedis*Radix
RPS885799
время выполнения p50, мс3,739,86
время выполнения p99, мс238,6376,8
время выполнения min, мс0,580,62
время выполнения max, мс28 3124 686
CPU стенда сред., %4580
CPU стенда макс., %7998
RAM стенда сред., ГБ9,05,6
RAM стенда макс., ГБ14,210,4
запись на диск сред., МБ/с229266
запись на диск макс., МБ/с3 1561 298
запись на диск сред., IOPS206341
запись на диск макс., IOPS2 7781 590

* Redis на этом размере завершил без ошибок лишь один прогон из пяти: в остальных кластер уходил в CLUSTERDOWN, и клиенты получали ошибки. Приведены значения этого единственного прогона без ошибок; остальные отбракованы. Radix прошёл все тесты без ошибок, см. «Стабильность при перегрузке».

Телеметрия SET — 256 КБ

SET, пропускная способность

SET, задержка

SET, CPU

SET, RAM

SET, диск

На записи 256-килобайтных несжимаемых значений с надёжностью проседают обе системы — примерно в десять раз относительно режима без надёжности (см. «Цена надёжности»). Пропускная способность становится сопоставимой, запись на диск у обеих систем близка (~250 МБ/с), тысячекратной разницы, которую мы видим на сжимаемых данных, здесь нет. Максимальная задержка, даже в прогоне без ошибок, у Redis достигает 28 секунд против 4,7 у Radix. По памяти на этом размере картина обратная той, что мы видели на мелких значениях: на чтении Radix занимает 26,7 ГБ против 14,4 ГБ у Redis.

Redis хранит данные в специализированной хеш-таблице и буферизует ответ лишь в отдельных случаях, разбор которых выходит за рамки статьи, а значения больше 64 КБ не буферизует никогда. Radix же пока буферизует ответ всегда — этим и объясняется повышенный расход оперативной памяти. Мы планируем устранить этот недостаток в следующих версиях, в рамках работ по поддержке RESP3.

Влияние сжимаемости данных

Мы измерили два предельных случая: несжимаемые данные (--random-data) и сжимаемые (настройки memtier_benchmark по умолчанию). Picodata сжимает и журнал WAL, и снапшоты (zstd), поэтому объём записи Radix на диск падает вместе со сжимаемостью данных. У Redis сжимаются только RDB-снапшоты (LZF), а AOF не сжимается никогда.

SET на сжимаемых данныхSET на сжимаемых данных: пропускная способность и запись на диск (лог. шкала); кластер, с надёжностью

На сжимаемых данных Radix впереди на записи уже с 10 КБ (111 против 103 тыс. RPS), а на 256 КБ выигрывает ×1,33 по RPS (8 110 против 6 089) при p99 6,9 мс против 41,2 мс — и пишет на диск на три порядка меньше: 2,3 МБ/с против 3 025 МБ/с. Журнал на таких данных для Radix почти бесплатен: 8 110 RPS с надёжностью против 8 637 без неё.

Ту же чувствительность Radix к сжимаемости видно и при сравнении его с самим собой:

Влияние сжимаемостиRadix SET: пропускная способность и запись на диск, сжимаемые против несжимаемых данных

На мелких и средних значениях разница невелика (100 байт: 150→144 тыс. RPS; 10 КБ: 111→106 тыс. RPS), но на 256 КБ диск становится узким местом, и пропускная способность падает с ~8 тыс. до сотен RPS.

У Redis картина иная:

Влияние сжимаемости на RedisRedis SET: пропускная способность и запись на диск, сжимаемые против несжимаемых данных

По скорости Redis тоже выигрывает от сжимаемости (10 КБ: 103 против 62 тыс. RPS; 256 КБ: 6,1 тыс. против 0,9 тыс.) — но не своими силами. Несжатый AOF на сжимаемых данных сжимала дисковая система стенда: мы видим ~3 ГБ/с записи, а на несжимаемых данных лишь ~250 МБ/с.

Чтобы проверить эту гипотезу, мы написали отдельный скрипт. Он показал, что диск стенда расположен на дисковой системе гипервизора, которая сжимает данные: 2 ГБ случайных байтов записи на диск стенда соответствовали ~5,7 ГБ записи на дисковую систему (амплификация записи произошла, скорее всего, из-за избыточности дискового массива), а 2 ГБ повторяющегося шаблона — ~150 МБ (сжатие примерно в 40 раз, учитывая амплификацию).

На несжимаемых данных этой подпорки нет: fsync-паузы растут, и пропускная способность падает. Объём записи на диск у Redis при этом не зависит от данных — AOF пишет полный объём всегда: на идеально сжимаемых 10-килобайтных значениях это ~2 ГБ/с, в 145 раз больше, чем у Radix на тех же данных. Выигрыш Redis от сжимаемости существует, только если данные сожмёт дисковая система; Radix сжимает журнал сам и не зависит от файловой системы.

Вывод: на несжимаемых данных крупная запись упирается в диск у обеих систем и результаты практически одинаковы. Чем лучше сжимаются данные, тем больше преимущество Radix: вплоть до ×1,33 по скорости и трёх порядков по объёму записи на диск, что важно для живучести дисков.

Выводы

Пропускная способность

На чтении Redis быстрее во всём диапазоне размеров, а к 256 КБ разрыв сходит на нет. На записи есть выраженный средний диапазон, где Radix впереди: на 10 КБ он выдаёт ×1,71 по RPS и на порядок лучший p99. На мелких значениях быстрее Redis (сильнее сказываются фиксированные накладные расходы Radix на операцию), а на очень крупных значениях с надёжностью проседают обе системы.

Время выполнения p99 на записьВремя выполнения p99 на запись (SET) по размерам значений; логарифмическая шкала

По p99 на записи виден тот же средний диапазон: на 10 КБ у Radix 3,4 мс против 27,6 мс у Redis. На 256 КБ задержки обеих систем уходят в сотни миллисекунд.

CPU и память

Загрузка CPU GET и SETСредняя загрузка CPU всего стенда по размерам значений (кластер, с надёжностью)

На чтении обе системы нагружают стенд сопоставимо (22–27%), и лишь на 256 КБ Radix заметно дороже (22% против 10%). На записи Radix стабильно расходует больше CPU (68–80% против 28–46%), и это понятная точка роста.

По памяти (тоже весь стенд, оценка грубая) картина зависит от объёма данных: на мелком дата-сете меньше расходует Radix (GET 100 байт: 1,8 против 4,7 ГБ), на крупном — Redis (GET 256 КБ: 14,4 против 26,7 ГБ). На записи потребление Redis колеблется сильнее: на 10 КБ пики до 9,2 ГБ при среднем 6,4 ГБ, у Radix — ровные 4,7–6,0 ГБ.

Диск

Запись на диск на записиСредняя запись на диск в тесте SET по размерам значений; кластер, с надёжностью, несжимаемые данные; логарифмическая шкала

На несжимаемых данных запись на диск у обеих систем одного порядка (256 КБ: Radix ~266, Redis ~229 МБ/с). Redis пишет AOF без сжатия и периодически переписывает его целиком, а Radix дописывает сжатый журнал и периодически снимает сжатый снапшот. Чем лучше сжимаются данные, тем сильнее это различие проявляется: на сжимаемых значениях размером 256 КБ разрыв достигает трёх порядков — 2,3 против 3 025 МБ/с (см. «Влияние сжимаемости данных»).

Цена надёжности

Приведена пропускная способность SET на несжимаемых данных.

ТестRedis, без → сRadix, без → с
SET, 100 байт180 974 → 184 674 (+2%)157 376 → 144 118 (−8%)
SET, 10 КБ153 323 → 62 137 (−59%)128 534 → 106 049 (−17%)
SET, 256 КБ8 391 → 885 (−89%)8 386 → 799 (−90%)

На мелких значениях надёжность почти бесплатна для обеих систем. На 10 КБ Redis теряет больше, Radix — меньше. На 256 КБ надёжность обходится дорого и той, и другой системе: пропускная способность падает примерно в десять раз.

Стабильность при перегрузке

Наиболее выраженная разница проявилась именно на тяжёлом сценарии — SET 256 КБ с надёжностью. Кластер Redis в этом сценарии завершил без ошибок лишь один прогон из пяти: в остальных массовая заливка данных насыщала диск, fsync-паузы превышали таймаут детектора отказов, узлы объявлялись мёртвыми, и кластер уходил в CLUSTERDOWN на секунды и десятки секунд, возвращая клиентам ошибки. Даже в единственном прогоне без ошибок максимальная задержка запроса у Redis достигала 28 секунд. Чтобы Redis вообще дошёл до этого сценария, пришлось предварительно увеличить буферы репликации и бэклог.

Radix проходил тот же сценарий на том же диске с конфигурацией по умолчанию и не вернул ни одной ошибки.

Скорее всего, дело в механизме отслеживания состояния узлов и в том, как на него влияет перегрузка диска: Redis использует gossip-детектор между узлами данных, Picodata — Raft-консенсус. В Redis обработку запросов, запись в AOF и gossip-пинги обслуживает один поток: на перегруженном диске запись в AOF встаёт за фоновым fsync’ом и блокирует поток, узел перестаёт отвечать на пинги, и живой, но медленный узел объявляется мёртвым. После этого кластер отвечает клиентам ошибкой CLUSTERDOWN. В Picodata запись в WAL вынесена в отдельный поток: перегруженный диск проявляется ростом задержки, а инстанс продолжает отвечать сети и heartbeat’ам Raft. При перегрузке Radix становится медленным (сотни RPS, высокий p99), но остаётся доступным; Redis при той же перегрузке недоступен.

За пределами бенчмарка

Несколько свойств Radix, которые могут быть важны при выборе системы.

Поддержка legacy-коннекторов

memtier_benchmark работал в --cluster-mode: коннектор сам узнаёт топологию и шлёт запрос на нужный узел, как и положено cluster-aware-коннектору. Radix при этом поддерживает и legacy-коннекторы: любой узел принимает запросы по любым ключам и перенаправляет их внутри кластера самостоятельно, если ключи лежат на другом узле. Redis Cluster в этой ситуации отвечает ошибкой MOVED. Для legacy-приложений с обычным коннектором переход на Radix — это способ получить горизонтальное масштабирование без изменения кода приложения.

Поведение при перегрузке

У кластера Radix нет режима, в котором он отказывает клиентам из-за медленного узла: деградация всегда выражается ростом задержки, а не ошибками (см. «Стабильность при перегрузке»).

Консистентность метаданных

Radix — плагин к Picodata. Для хранения настроек и ACL используются глобальные таблицы, поэтому при изменении конфигурации нет необходимости обходить все узлы кластера и вызывать на них, например, ACL LOAD — достаточно сделать это на одном узле, и ACL загрузится на весь кластер. Аналогично с любыми другими настройками, включая настройки вытеснения.

Базы данных

В Redis Cluster доступна одна база данных и команда SELECT не работает; в Radix доступны 16 баз данных и SELECT работает штатно.

Подведём итоги

Picodata постоянно использует сравнительное нагрузочное тестирование в разработке. Но одно дело — провести внутренние тесты, и совсем другое — собрать сводный публичный отчёт о производительности, в котором каждый результат проверен, а разница объясняется архитектурой того или иного продукта. Надеемся, эта статья была вам полезна и поможет при выборе решения для собственных задач. Сводные результаты тестирования приведены в таблице ниже.

СценарийИтог
GET, все размерыRedis быстрее (×1,08–1,32)
SET, 100 байтRedis ×1,28
SET, 10 КБRadix ×1,71, p99 Radix в 8 раз ниже
SET, 256 КБПаритет по RPS; Redis при этом менее стабилен
SET на сжимаемых данныхRadix впереди с 10 КБ; на 256 КБ скорость выше на 30+%,
а ввод-вывод на два порядка ниже
Цена надёжности, SET 256 КБОбе ~90%