Проверка здоровья Redis: одного PING недостаточно¶
Проверка, возвращающая True для сервера, неспособного принять запись, хуже отсутствующей: по её ответу принимают решения. Я запустил три Redis: исправный, заполнивший лимит памяти с отключённым вытеснением и реплику только для чтения. Все ответили PONG. Два отклоняли каждый SET приложения. Readiness оставался зелёным, pod продолжали получать трафик, а проблему видел только записывающий код.
Числа получены в эксперименте статьи: три контейнера Redis 7 в одной сети. Версии: redis-client-kit 0.2.0, redis-py 8.1.0, Python 3.13.
На что отвечает PING¶
primary PING health=True ( 2.5 ms) a plain SET: ok
maxmemory 1mb, noeviction, full PING health=True ( 0.2 ms) a plain SET: OutOfMemoryError:
command not allowed when used memory > 'maxmemory'
replica of the primary PING health=True ( 5.5 ms) a plain SET: ReadOnlyError:
You can't write against a read only replica
paused primary PING health=False (501.8 ms)
PING проверяет доступность процесса: он работает, цикл событий движется, сокет отвечает. Три из четырёх проверяемых адресов проходят эту проверку, но лишь один принимает нужную запись.
Два средних случая не экзотика. maxmemory с noeviction намеренно выбирают для важных данных — ключей идемпотентности, блокировок, очередей, — чтобы Redis отклонял запись вместо молчаливого удаления ключей. Достигнув лимита, он действительно отклоняет записи до освобождения памяти. На реплику только для чтения можно попасть после незавершённого переключения, неверного DNS или выбора узла клиентом с Sentinel. Оба случая — «Redis работает, сервис нет», при зелёной проверке.
Последняя строка показывает обнаружение мёртвого сервера: False через полсекунды, благодаря таймауту сокета. Обычно проверки проектируют именно под этот, самый простой случай.
Liveness и readiness задают разные вопросы¶
Kubernetes явно разделяет их, но позволяет подключить один обработчик к обоим. Вопросы всё же разные.
Liveness спрашивает, нужно ли убить и перезапустить этот процесс. Отказ зависимости почти никогда не означает, что сам процесс мёртв: перезапуск pod не освобождает Redis. Если liveness краснеет из-за зависимости, все реплики попадают в цикл перезапусков в худший момент. Зависимостям не место в liveness.
Readiness спрашивает, можно ли сейчас направлять трафик в pod. Здесь проверка зависимости уместна. Но PING не отвечает на главный вопрос «могу ли я выполнить работу?», когда работа включает запись.
Поэтому проверка должна выполнять нужную приложению операцию. Вариант библиотеки принимает необязательный ключ; если он передан, после ping выполняется запись:
primary write_key health=True (0.7 ms)
maxmemory 1mb, noeviction, full write_key health=False (0.6 ms)
replica of the primary write_key health=False (0.8 ms)
paused primary write_key health=False (503.8 ms)
Те же четыре адреса, но ответы соответствуют реальности. Проба — SET выбранного ключа с коротким сроком жизни. OutOfMemoryError заполненного сервера и ReadOnlyError реплики превращаются в False, как и ошибка соединения. Проверка, выбрасывающая исключение, ломала бы собственный обработчик здоровья.
Цена и ограничения¶
Запись добавляет один сетевой обмен; на одном хосте эксперимент показывает существенно меньше миллисекунды. Это мало на проверку, но уже нагрузка при проверке на каждый запрос. Так делать не стоит: readiness опрашивается по расписанию kubelet, а сто проверок от ста запросов нагружают именно защищаемую зависимость.
Ограничения тоже нужно знать заранее.
Один ключ — один слот. В Redis Cluster запись идёт в слот по хешу ключа. Она доказывает возможность записи на одном узле, не во всём кластере. Покрытие слотов — отдельная проверка состояния кластера ok.
Пробная запись остаётся записью. Ключ попадает в общее пространство, занимает память и реплицируется. Задайте короткий TTL и имя, которое не спутают с данными.
Проба не воспроизводит весь сценарий доступа. Redis, принимающий двухбайтовый SET, у лимита памяти может отклонить запись на сто килобайт. Это ограниченное свидетельство работоспособности, а не полная симуляция.
По умолчанию остаётся PING. Не каждый сценарий пишет: чтение кеша с переходом к БД может работать и с репликой только для чтения, а выключать такой pod из маршрутизации было бы ошибкой. Более глубокая проверка включается вызывающим кодом. Как и в статье о fail open, клиент обязан быстро и честно сообщить результат, а решение зависит от использования.
Правило¶
Проверка здоровья должна отвечать о нужной операции, а не только сокете. Если путь запроса пишет в Redis, readiness должна проверять запись. Если он только читает и допускает соответствующий запасной путь, PING может быть достаточной базовой проверкой, а реплика — допустимой зависимостью.
Полезный общий принцип: проверка, которая никогда не срабатывает, может ничего не проверять. Если проба год зелёная, сломайте зависимость в эксперименте и убедитесь, что она заметит. Заполненный Redis с noeviction легко получить через docker run --maxmemory 1mb, реплику — через --replicaof. В эксперименте на каждый случай ушло около десяти строк; старая проверка оба пропустила бы.
PING доказывает, что Redis отвечает на PING. Готовность должна учитывать нужную возможность: реплика или заполненный основной узел могут отвечать, но отклонять запись.
Инструменты¶
Проверку предоставляет redis-client-kit: синхронная и асинхронная функции возвращают True или False, обрабатывают ошибки, ограничены таймаутом сокета и принимают необязательный ключ для ping плюс записи. Остальная библиотека собирает клиент из настроек с явными таймаутами и политикой повторов.
Три сервера ответили PONG. Два не смогли бы принять ни одной записи.