Мы уже как-то рассказывали о базе данных KeyDB — форке Redis, разработка которого началась в 2019 году. Проект распространяется под свободной лицензией BSD, и у него уже почти 6k звезд на GitHub. Авторы в свое время столкнулись с проблемами производительности оригинала и пошли хардкорным путём: взяли всё в свои руки и привнесли много нового как в части многопоточности, так и в других областях. В статье делимся еще одним положительным опытом замены Redis на KeyDB. В одном из клиентских проектов у нас довольно нагруженный Redis. Поначалу мы использовали spotahome/redis-operator для реализации Redis Failover в режиме master/slave. Но с ростом проекта стали банально упираться в гигабитную сеть на узле с master-Redis'ом: независимо от количества реплик, вся нагрузка всегда приходилась на мастер-узел, а реплики были «на подхвате». Тогда мы решили переехать на Redis-кластер: ключи шардируются между несколькими master'ами, у каждого master'а есть реплики. Это избавило от проблемы с сетевой загрузкой, так как данные расползлись по нескольким шардам, и нагрузка, соответственно, тоже распределилась. Казалось бы, вопрос с ресурсами был закрыт надолго (на такой схеме мы проработали около года). Но беда пришла, откуда не ждали. После переезда сервиса из одного дата-центра в другой, приложение на PHP вдруг стало работать медленно. Одно из подозрений упало на время ответа Redis, хотя, на первый взгляд, с ним все было неплохо. Для проверки написали простой тест, эмулирующий работу PHP-приложения с Redis:  Погоняли тест, и результат получился неожиданным — иногда Redis действительно отвечал медленно: Присмотревшись к проблеме внимательнее, мы обнаружили, что master одного из шардов потребляет почти 100% ресурсов одного ядра. Тут уже вспомнилось, что Redis однопоточный, а значит он просто не может обработать больше запросов.  В новом дата-центре мы переехали на другое железо, в целом более мощное. Но производительность на одно ядро у новых процессоров ниже, чем у предыдущих — ранее были «железные» Intel(R) Xeon(R) CPU @ 3.40GHz, а теперь — vSphere с Intel(R) Xeon(R) Gold 6132 CPU @ 2.60GHz. В результате именно это и вылилось в задержки при обращении к некоторым ключам в Redis'е, а также в целом в работе приложения. Так это выглядело на машинах в исходном кластере (здесь и далее по оси ординат — используемые процессом ядра CPU): А так — в новом, когда пришла полноценная нагрузка: Что делать?  Проект под нагрузкой, Redis с данными довольно большой, решардировать его сходу проблематично.  Пришла идея попробовать заменить его на KeyDB. Просто взять и поменять образ redis в контейнере Kubernetes на keydb — должно сработать, ведь разработчики KeyDB заявляют, что структура данных Redis поддерживается без изменений. Для начала провели опыт в тестовом окружении: в кластер Redis'а записали сотню случайных ключей, заменили образ в контейнере на образ eqalpha/keydb, а команду запуска — с redis-server -c /etc/redis.conf на keydb-server -c /etc/redis.conf --server-threads 4. Затем перезапустили по одному Pod'ы (кластер запущен как набор StatefulSet c updateStrategy OnDelete). Контейнеры перезапустились по одному, подключились к кластеру и синхронизировались. Все данные на месте: сотня тестовых ключей, которые мы записали в кластер Redis'а, прочиталась из кластера KeyDB. Все прошло успешно, поэтому мы решились проделать то же самое и с рабочим кластером. Поменяли образ в конфигурации, подождали, пока обновятся Pod'ы и стали наблюдать за временем ответа от всех шардов — теперь оно стало одинаковое для всех.  На графике видно, что новые «Redis'ы» потребляют более одного ядра: Чтобы в дальнейшем мониторить скорость ответа Redis-кластера, мы написали небольшое приложение на Go. Раз в секунду оно обращается к кластеру и помещает в него ключ со случайным именем (можно сконфигурировать префикс имени ключа) и TTL 2 сек. Случайное имя использовано для того, чтобы попадать в разные шарды кластера и сделать результат более приближенным к работе реального приложения. Время, затраченное на операцию соединения и записи, сохраняется. Хранятся 60 последних измерений. В случае, если операция записи не удалась, увеличивается счетчик неудачных попыток. Неудачные попытки не учитываются при расчете среднего времени ответа.  Приложение экспортирует метрики в формате Prometheus: максимальное, минимальное и среднее время операции за последние 60 сек., а также количество ошибок: График с результатами: Если запустить тестовое приложение на нескольких или всех узлах кластера, можно увидеть, есть ли зависимость задержек от узла: например, на узле может быть перегружена сеть. Код приложения доступен в репозиториии. Также можно воспользоваться подготовленным Docker-образом. Стоит обратить внимание, что в Redis есть метрики по спайкам, которые можно включить командой: Но это видение метрики со стороны Redis, а мы хотим наблюдать время ответа и со стороны приложения. В Redis 6 уже реализована многопоточность, впрочем, судя по описанию, не так эффективно, как в KeyDB или Thredis. Для активации этого режима нужно добавить параметр io-threads 4. Прием запросов, парсинг, обработка и отправка будут происходить в разных потоках. Это может быть полезно, когда размер ключей очень большой: в однопоточном режиме Redis не будет принимать и обрабатывать новые запросы, пока не будет отправлен ответ на предыдущий запрос. Детальное сравнение производительности Redis и KeyDB в многопоточном режиме представлено в официальной документации KeyDB. Согласно результатам, KeyDB демонстрирует значительный прирост производительности по сравнению с Redis по мере того, как становится доступно больше ядер. Даже с многопоточным вводом-выводом Redis 6 по-прежнему отстает от KeyDB из-за более низкой способности к вертикальному масштабированию. Мы еще раз проверили и убедились, что в сложной ситуации Redis можно масштабировать вертикально, просто заменив его на KeyDB. У такого способа нет сложных подводных камней, поскольку KeyDB — это форк Redis’а, и он должен без проблем подхватывать данные от оригинального проекта. Также благодаря разбору ситуации мы написали полезный экспортер и алерты на основании метрик от него. Это поможет более точно диагностировать проблемы и заранее их предотвращать. Читайте также в нашем блоге: «KeyDB как [потенциальная] замена Redis». «Одна история с оператором Redis в K8s и мини-обзор утилит для анализа данных этой БД». «Практические истории из наших SRE-будней. Часть 5».