Radix — плагин для распределённой СУБД Picodata, реализующий протокол Redis. Благодаря ему приложения, использующие Redis, могут работать с Picodata без изменения кода.
Мы сравнили Radix 1.0.5 и Redis 8.8.0 на двух базовых командах, GET и SET, в кластере из 8 узлов, на сжимаемых и несжимаемых данных. Radix выдаёт от 76% до 170% пропускной способности Redis, а на крупных значениях кластер Redis уходит в отказ и перестаёт отвечать, тогда как Radix продолжает работать.
Пропускная способность:
| Тест | Redis, RPS | Radix, RPS |
|---|---|---|
| get-100b | 197 730 | 149 665 |
| set-100b | 184 674 | 144 118 |
| get-10k | 140 737 | 113 533 |
| set-10k | 62 137 | 106 049 |
| get-256k | 8 892 | 8 215 |
| set-256k* | 885 | 799 |
Время выполнения запросов, мс:
| Тест | Redis, p50 | Radix, p50 | Redis, p99 | Radix, p99 | Redis, макс | Radix, макс |
|---|---|---|---|---|---|---|
| get-100b | 0,9 | 1,1 | 1,7 | 4,4 | 38 | 44 |
| set-100b | 1,0 | 1,3 | 2,4 | 2,6 | 92 | 692 |
| get-10k | 1,3 | 1,5 | 2,0 | 5,2 | 65 | 38 |
| set-10k | 1,5 | 1,7 | 27,6 | 3,4 | 3 260 | 301 |
| get-256k | 4,4 | 4,9 | 5,2 | 5,4 | 54 | 29 |
| set-256k* | 3,7 | 9,9 | 238,6 | 376,8 | 28 312 | 4 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 было незначительным, и мы решили им пренебречь.
| Ресурс | Значение |
|---|---|
| CPU | AMD EPYC, 32 ядра / 64 потока |
| RAM | 96 ГБ |
| Диск | SATA SSD |
| ОС | Ubuntu 24.04 |
Конфигурация систем
Обе системы мы развернули как кластер из 8 узлов на одной машине и тестировали в двух режимах с попарно согласованными гарантиями надёжности:
| Система | Топология |
|---|---|
| Redis 8.8.0 | Кластер из 8 узлов: 4 мастера + 4 реплики |
| Radix 1.0.5 на Picodata 26.1.4 | Кластер из 8 инстансов: 4 репликасета × фактор репликации 2 |
| Режим | Redis | Radix |
|---|---|---|
| Без надёжности | AOF и RDB выключены | данные в UNLOGGED-таблицах |
| С надёжностью | AOF (appendfsync everysec) + RDB | журнал WAL + снапшоты |
В режиме «с надёжностью» в обеих системах запись переживает падение процесса. Radix использует движок memtx, в котором все данные хранятся в памяти, а надёжность обеспечивают периодические снапшоты и журнал предзаписи.
Входные данные
Мы заполняли значения случайными байтами, которые почти не сжимаются. Инструмент нагрузочного тестирования готовил набор данных заранее, а во время теста выбирал из него ключи случайно, по равномерному распределению.
Инструмент
Каждый тест работал с одной командой, только GET или только SET, и длился 120 секунд. Для каждой пары «команда × размер» мы выполнили несколько прогонов и включили в отчёт лучший. Точные команды запуска приведены в разделе «Результаты».
Набор тестов
И GET, и SET используют значения по случайному ключу. Каждый тест мы выполняли в обоих режимах надёжности.
| Тест | Команда | Размер значения | Число ключей |
|---|---|---|---|
| get-100b | GET | 100 байт | 1 000 000 |
| set-100b | SET | 100 байт | 1 000 000 |
| get-10k | GET | 10 КБ | 200 000 |
| set-10k | SET | 10 КБ | 200 000 |
| get-256k | GET | 256 КБ | 20 000 |
| set-256k | SET | 256 КБ | 20 000 |
Измеряемые параметры
| Категория | Параметры |
|---|---|
| Пропускная способность | Запросов в секунду (RPS) |
| Время выполнения запросов | p50, p99, min, max |
| CPU | Загрузка CPU всего стенда, % |
| RAM | Использованная память всего стенда, ГБ |
| Диск | Запись на диск: МБ/с, IOPS |
Генератор нагрузки работал на той же машине, что и база данных, поэтому значения CPU и RAM относятся ко всему стенду и включают потребление memtier_benchmark.
Результаты
Разберём результаты подробнее. Начнём с режима «с надёжностью», в котором данные переживают сбой. Режим без надёжности мы рассмотрим в разделе «Цена надёжности».
Пропускная способность 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-100b | Redis | Radix |
|---|---|---|
| RPS | 197 730 | 149 665 |
| время выполнения p50, мс | 0,91 | 1,10 |
| время выполнения p99, мс | 1,70 | 4,38 |
| время выполнения min, мс | 0,05 | 0,03 |
| время выполнения max, мс | 38 | 44 |
| CPU стенда сред., % | 24 | 22 |
| CPU стенда макс., % | 47 | 25 |
| RAM стенда сред., ГБ | 4,7 | 1,8 |
| RAM стенда макс., ГБ | 4,7 | 1,8 |
Телеметрия GET — 100 байт
| set-100b | Redis | Radix |
|---|---|---|
| RPS | 184 674 | 144 118 |
| время выполнения p50, мс | 1,00 | 1,27 |
| время выполнения p99, мс | 2,40 | 2,56 |
| время выполнения min, мс | 0,11 | 0,06 |
| время выполнения max, мс | 92 | 692 |
| CPU стенда сред., % | 28 | 68 |
| CPU стенда макс., % | 41 | 78 |
| RAM стенда сред., ГБ | 4,5 | 1,1 |
| RAM стенда макс., ГБ | 4,6 | 1,4 |
| запись на диск сред., МБ/с | 60,8 | 49,8 |
| запись на диск макс., МБ/с | 149 | 652 |
| запись на диск сред., IOPS | 169 | 87 |
| запись на диск макс., IOPS | 360 | 1 255 |
Телеметрия SET — 100 байт
На 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-10k | Redis | Radix |
|---|---|---|
| RPS | 140 737 | 113 533 |
| время выполнения p50, мс | 1,32 | 1,48 |
| время выполнения p99, мс | 2,02 | 5,25 |
| время выполнения min, мс | 0,14 | 0,06 |
| время выполнения max, мс | 65 | 38 |
| CPU стенда сред., % | 27 | 23 |
| CPU стенда макс., % | 68 | 36 |
| RAM стенда сред., ГБ | 8,5 | 4,9 |
| RAM стенда макс., ГБ | 8,5 | 4,9 |
Телеметрия GET — 10 КБ
| set-10k | Redis | Radix |
|---|---|---|
| RPS | 62 137 | 106 049 |
| время выполнения p50, мс | 1,53 | 1,70 |
| время выполнения p99, мс | 27,65 | 3,38 |
| время выполнения min, мс | 0,18 | 0,09 |
| время выполнения max, мс | 3 260 | 301 |
| CPU стенда сред., % | 46 | 73 |
| CPU стенда макс., % | 69 | 81 |
| RAM стенда сред., ГБ | 6,4 | 4,7 |
| RAM стенда макс., ГБ | 9,2 | 6,0 |
| запись на диск сред., МБ/с | 1 337 | 944 |
| запись на диск макс., МБ/с | 2 216 | 1 876 |
| запись на диск сред., IOPS | 1 395 | 1 232 |
| запись на диск макс., IOPS | 2 296 | 2 700 |
Телеметрия SET — 10 КБ
На этом размере 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-256k | Redis | Radix |
|---|---|---|
| RPS | 8 892 | 8 215 |
| время выполнения p50, мс | 4,45 | 4,90 |
| время выполнения p99, мс | 5,22 | 5,41 |
| время выполнения min, мс | 0,90 | 0,82 |
| время выполнения max, мс | 54 | 29 |
| CPU стенда сред., % | 10 | 22 |
| CPU стенда макс., % | 19 | 85 |
| RAM стенда сред., ГБ | 14,4 | 26,7 |
| RAM стенда макс., ГБ | 14,4 | 26,7 |
Телеметрия GET — 256 КБ
| set-256k | Redis* | Radix |
|---|---|---|
| RPS | 885 | 799 |
| время выполнения p50, мс | 3,73 | 9,86 |
| время выполнения p99, мс | 238,6 | 376,8 |
| время выполнения min, мс | 0,58 | 0,62 |
| время выполнения max, мс | 28 312 | 4 686 |
| CPU стенда сред., % | 45 | 80 |
| CPU стенда макс., % | 79 | 98 |
| RAM стенда сред., ГБ | 9,0 | 5,6 |
| RAM стенда макс., ГБ | 14,2 | 10,4 |
| запись на диск сред., МБ/с | 229 | 266 |
| запись на диск макс., МБ/с | 3 156 | 1 298 |
| запись на диск сред., IOPS | 206 | 341 |
| запись на диск макс., IOPS | 2 778 | 1 590 |
* Redis на этом размере завершил без ошибок лишь один прогон из пяти: в остальных кластер уходил в CLUSTERDOWN, и клиенты получали ошибки. Приведены значения этого единственного прогона без ошибок; остальные отбракованы. Radix прошёл все тесты без ошибок, см. «Стабильность при перегрузке».
Телеметрия SET — 256 КБ
На записи 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 на сжимаемых данных: пропускная способность и запись на диск (лог. шкала); кластер, с надёжностью
На сжимаемых данных 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 картина иная:
Redis 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 на запись (SET) по размерам значений; логарифмическая шкала
По p99 на записи виден тот же средний диапазон: на 10 КБ у Radix 3,4 мс против 27,6 мс у Redis. На 256 КБ задержки обеих систем уходят в сотни миллисекунд.
CPU и память
Средняя загрузка 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% |