Не так давно, развернув в Kubernetes уже привычный инфраструктурный компонент в виде кластера Redis Sentinel + redis-sentinel-proxy, мы столкнулись с интересными проблемами. При тестировании времени переключения мастера выяснилось, что оно составляет полторы минуты. Это очень долго.  Расскажу, как получилось ускорить процесс. Redis Sentinel — это инструмент, который позволяет собрать кластер из СУБД Redis в режиме master-replica. Redis Sentinel сам мониторит, настраивает и переключает роли. Если мастер-узел падает, сервис сам выбирает узел на замену, назначает его мастером и выполняет перенастройку. Более подробно о работе сервиса можно почитать в документации. N.B. Далее для простоты буду называть Redis Sentinel просто Sentinel. Казалось бы, звучит классно. Давайте везде использовать Sentinel вместо обычного Redis! Но тут есть определенные сложности. Взять и просто подменить не получится, потому что с Sentinel надо взаимодействовать иначе: сначала нужно обратиться к нему, чтобы получить адрес мастера, и только потом установить подключение к самому мастеру. Если классическая работа с Redis — скажем, из Python — выглядит так: … то в случае с Sentinel необходимо выполнить чуть больше действий: С одной стороны, вроде бы несложно. Но все зависит от контекста. Когда мы начинаем работать с клиентом, обычно проводим кубернетизацию приложения: заводим его в контейнеры, описываем манифесты для работы в Kubernetes, добавляем чарты с инфраструктурой. Но клиент не всегда готов «прямо сейчас» взять и исправить код для работы с уже привычным ему инструментом по другому алгоритму. Бывают более приоритетные задачи — какие-то issues, которые надо закрыть «вчера». Именно для таких переходных ситуаций, когда код будет адаптирован, но позже, придумана утилита redis-sentinel-proxy. Как она работает: При старте подключается к Sentinel и начинает раз в секунду опрашивать адрес текущего мастера. Одновременно с этим ожидает входящие соединения. Когда приходит запрос на соединение от клиента, она начинает работать как прокси, подставляя адрес текущего мастера в Sentinel-кластере. Таким образом, подключение к redis-sentinel-proxy работает как будто это прямое подключение к Redis (к актуальному мастеру кластера).  И все бы хорошо, но ровно до того момента, когда — по какой-либо причине — падает мастер. И вот как это происходит: мастер падает; Sentinel выжидает по умолчанию 5 секунд*, давая мастеру время вернуться; если не дожидается — начинает выбор нового лидера: * За это отвечает параметр down-after-milliseconds. Из документации: The down-after-milliseconds value is 5000 milliseconds, that is 5 seconds, so masters will be detected as failing as soon as we don't receive any reply from our pings within this amount of time. В этот момент в redis-sentinel-proxy рвутся соединения. Что делает клиент? Правильно, переподключается. И вот тут-то и кроется корень всех зол!  Пока новый мастер не поднялся, все новые соединения подвисают, пока не отвалятся по tcp-timeout.  И здесь добавляется еще одна проблема — подвисает проверка контейнера. В Kubernetes есть readiness probe. Единственное, что она делает: redis-cli -p 9999 ping. Поскольку probe проходит внутри контейнера, при выполнении она подвисает — так же, как и ее нерадивое соединение. При этом redis-sentinel-proxy не может принимать новые соединения. Как мы выяснили ранее, у Sentinel уходит около 5 секунд на то, чтобы подождать мастера (вдруг вернётся), и еще 2 — на переключение роли мастера. А соединение в redis-sentinel-proxy висит намного дольше. Сделав замеры, я увидел страшную цифру: переключение redis-sentinel-proxy на нового мастера, с учетом зависших соединений занимает порядка 80-90 секунд! Первый подход в решении проблемы оказался быстрым, но, будем честными, костыльным. Я просто добавил timeout 1 перед командой liveness probe. В принципе, это частично решило проблему. Получилось: timeout 1 redis-cli -p 9999 ping. Если ping в пробе не прошел, и таймаут в 1 секунду истёк, контейнер получает SIGTERM, и совершает перезапуск.  Время переключения redis-sentinel-proxy на новый мастер сократилось в среднем до 20 секунд. Но мне не давало покоя то, что решение не выглядело оптимальным. К тому же, ведь код redis-sentinel-proxy несложный — там всего один файлик на 120 строк… И тогда решил вспомнить молодость и сдуть пыль с IDE (на самом деле, я ею пользуюсь часто, но для написания YAML-файлов). Потратив несколько часов на поднятие из глубин памяти синтаксиса Golang, изучение исходников и библиотеки работы с TCP, я нашел решение. Было: Стало: Да-да, ради этих пары строк кода весь шум! Ведь проблема была именно в отсутствии таймаута на соединение. А теперь — тесты! После того, как нужные изменения были внесены, соединения больше не висели.  Временные затраты по итогам тестов: 5 секунд — на ожидание Sentinel, поднимется ли мастер; 2 секунды — на смену лидера; 1-2 секунды (если укладываемся в таймаут) — на отработку readiness probe. Итого — 10 секунд (против 80-90). Читайте также в нашем блоге: «Одна история с оператором Redis в K8s и мини-обзор утилит для анализа данных этой БД»; «KeyDB как [потенциальная] замена Redis». DevOps, Network, Linux\Unix