Health-checks: три разные проверки, которые зовут одним словом
Liveness спрашивает «жив ли ты», readiness — «готов ли ты принимать», startup — «успел ли ты подняться». Их путают, и цена путаницы измерима: проба, завязанная на общую базу, превращает одну аварию базы в перезапуск всех реплик сразу.
Полное техническое изложение
TL;DR
Проверка здоровья — не один вопрос, а три. Startup спрашивает, успело ли приложение подняться; liveness — поможет ли перезапуск этого процесса; readiness — можно ли слать сюда трафик. Различаются они не строгостью, а последствием: перезапуск, ожидание или вывод из ротации. Поэтому и содержимое каждой пробы выводится из её последствия: проверять надо то, что этим последствием чинится.
Отсюда главная ошибка: проба, завязанная на общую зависимость, превращает одну аварию в две. Перезапуск не делает доступнее то, что находится снаружи процесса, — база, сеть и соседний сервис от него не оживают, зато процесс теряет прогретые кеши и пулы соединений. Модель: недоступность базы стоит 1000 неуспешных запросов сама по себе и 1200, если liveness проверяет базу. Лишние 200 — это перезапуск всех реплик разом.
Дальше — числа и границы. У деградировавшей реплики один liveness даёт 520 медленных ответов, liveness плюс readiness — 80, то есть в 6,5 раза меньше; полное отсутствие проб — 1600, потому что балансировщик продолжает слать нагрузку на больную реплику. Ровно так же он слепнет от readiness, который всегда отвечает «готов». Но и обратная крайность существует: readiness — не сумма всех зависимостей сервиса, а ответ на вопрос, способна ли реплика обслужить хоть какой-то трафик. Числа здесь модельные: они выводятся из объявленных допущений, и содержательны отношения между строками, а не абсолютные значения.
- сервис работает не в одном экземпляре: несколько одинаковых реплик обслуживают общий поток запросов;
- перед ними стоит балансировщик, который решает, кому отдать очередной запрос, и может перестать отдавать их кому-то одному;
- за самими репликами кто-то следит и умеет перезапустить ту, с которой что-то не так.
- чем liveness отличается от readiness и зачем нужен startup — это и есть содержание урока;
- как оркестратор настраивает пробы: периоды, пороги, имена полей;
- каскад перезапусков, вывод из ротации, ложно-отрицательная проба.
Что здесь на самом деле спрашивают
Лестница обычно такая:
- «Зачем нужны проверки здоровья?» — разминка.
- «Чем liveness отличается от readiness?» — начало содержания.
- «Что проверять внутри пробы?» — главный вопрос темы.
- «Почему liveness не должен ходить в базу?» — вопрос про каскад.
- «Зачем нужна отдельная startup-проба?» — вопрос про медленный старт.
- «Что будет, если readiness врёт?» — вопрос про обратную ошибку.
Этот урок стоит не на замере, а на модели: у поведения проб нет общего стандарта — есть документация отдельных продуктов. Модель отвечает не на вопрос «сколько именно», а на вопрос «что с чем связано», и допущения у неё названы.
База: три вопроса, а не одна проверка
У сервиса несколько одинаковых реплик, и снаружи не видно, что происходит внутри любой из них. Спросить можно ровно одним способом: постучаться и посмотреть на ответ. Отсюда и берётся проверка здоровья — короткий вопрос, который кто-то задаёт реплике регулярно, и заранее назначенное действие на случай отрицательного ответа.
Вся тема стоит на том, что «здоров ли ты» — это не один вопрос. Действий, на которые способен наблюдатель, несколько, и разные действия чинят разное. Поэтому проверок три, и у каждой свой вопрос:
| Проба | На какой вопрос отвечает | Что делают при ответе «нет» |
|---|---|---|
| startup | приложение уже запустилось? | ничего: продолжают ждать и не судят реплику строго |
| liveness | поможет ли перезапуск этого процесса? | перезапускают процесс |
| readiness | можно ли слать сюда трафик? | перестают слать трафик, процесс не трогают |
Средняя колонка у liveness сформулирована непривычно, и это главная мысль урока. Обычно говорят «жив ли процесс» — но на такой вопрос проба не нужна: мёртвый процесс не ответит вовсе, и это видно без всяких проверок. Проба существует ради другого случая: процесс жив, отвечает на запросы, а работать перестал. И решает она не «жив или нет», а стоит ли применить к нему единственное имеющееся лекарство — перезапуск.
Правая колонка отвечает и на главный вопрос урока: что положить внутрь каждой пробы? Ответ выводится из последствия — проверять надо то, что этим последствием чинится, и ничего больше.
Этого уже достаточно, чтобы ответить на базовый вопрос собеседования. Всё дальнейшее — про цену ошибок в этом выборе: сколько стоит отсутствие readiness, почему проба, заглядывающая в общую базу, добавляет отказов к чужой аварии, и почему слишком старательный readiness вреден так же, как слишком доверчивый.
Механизм 1: последствие и есть определение
Нормативного документа у этого разделения нет: ниже — различение по последствию, которым урок пользуется как определением. Путаница начинается там, где на все три вопроса отвечают одним обработчиком.
Liveness — «поможет ли перезапуск этого процесса». Отрицательный ответ означает: процесс испорчен настолько, что вернуть его в рабочее состояние может только перезапуск. Взаимоблокировка, исчерпание пула потоков, бесконечный цикл — поломки внутренние и накопительные, и все они лечатся возвратом процесса в известное состояние.
Readiness — «можно ли слать сюда трафик сейчас». Отрицательный ответ означает: не присылайте мне запросы, но перезапускать меня не надо. Прогрев кеша, потеря соединения с зависимостью, временная перегрузка — состояния, из которых реплика выходит сама, если её не нагружать.
Startup — «успел ли подняться». Отдельная проба нужна, чтобы медленный старт не выглядел как смерть. Урок считает, что до успеха startup-пробы liveness не применяется, — в этом и есть смысл отдельной пробы.
Разница в последствии: liveness перезапускает, readiness убирает из ротации. Отсюда правило, из которого выводится всё остальное: проверять надо то, что можно починить последствием. Для liveness это читается буквально: внутрь неё попадает только то, что чинится перезапуском этого процесса.
Механизм 2: что даёт каждая проба
Модель: три реплики, постоянный поток, одна реплика с десятой секунды начинает отвечать медленно — но отвечать.
REQUESTS SERVED SLOWLY OR NOT AT ALL
------------------------------------
mode slow failed total
none 1600 1000 6000
liveness 520 1000 6000
liveness+readiness 80 1000 6000
liveness-on-db 640 1200 6000
Без проб — 1600 медленных ответов. Балансировщик не знает, что реплике плохо, и продолжает слать ей треть трафика всё время.
Только liveness — 520. Проба замечает беду и перезапускает реплику; перезапуск на пять секунд убирает её из строя, потом она возвращается — и снова деградирует. Помогает, но криво: реплика курсирует между «плохо» и «перезапускается».
Liveness плюс readiness — 80. Реплику выводят из ротации без перезапуска, и нагрузка на неё прекращается. В 6,5 раза меньше медленных ответов, чем при одном liveness.
Отсюда ответ на второй вопрос лестницы: readiness — не «то же самое, но мягче». Это другое последствие, и в случае деградации именно оно уместно: реплика, которую не нагружают, может восстановиться сама, а перезапущенная начинает с холодного старта.
Механизм 3: почему liveness не ходит в базу
Четвёртая строка прогона выше — про ошибку, которая добавляет отказов к чужой аварии. Проба здесь проверяет не только себя, но и общую зависимость: если база недоступна, liveness считается неудачным.
Её медленные ответы (640) близки к режиму с одним liveness — 520; вся разница ушла в колонку неуспешных, и ею занят этот раздел.
WHAT THE THREE MODES ACTUALLY CHANGE
------------------------------------
no probes: slow answers 1600
liveness only: slow answers 520
liveness + readiness: slow answers 80
liveness tied to the database: failures 1200
the same failures without that tie 1000
failures added by tying liveness to the database 200
База лежит десять секунд в обоих прогонах, и эти десять секунд стоят 1000 неуспешных запросов независимо от проб — починить чужую аварию проба не может.
А вот лишние 200 — целиком заслуга пробы. Механизм такой: база недоступна → liveness не проходит у всех реплик одновременно → все реплики перезапускаются → пока они поднимаются, сервис не отвечает вообще, и это продолжается после того, как база уже вернулась.
Отсюда правило, которым стоит отвечать: liveness проверяет только то, что чинится перезапуском этого процесса. База, сеть и соседний сервис перезапуском не чинятся; их место — в readiness, где последствие мягче: реплику перестают нагружать, но она остаётся живой и вернётся сама.
И вторая часть того же правила: общая зависимость делает пробы синхронными. Если проверка одинакова у всех реплик и зависит от одного внешнего объекта, они отказывают все разом — и запас надёжности, ради которого их и держат три, исчезает.
Глубже: границы правила и обе ошибки readiness
Обратная ошибка устроена наоборот и стоит в модели больше всего медленных ответов. Readiness, который отвечает «готов» всегда — например, потому что это статический обработчик, возвращающий 200, — превращает балансировщик в слепого: он продолжает слать запросы туда, где их некому обслужить.
Отдельного режима для такого readiness в модели нет; ближайший к нему — режим вообще без проб: 1600 медленных ответов. Балансировщик в обоих случаях одинаково слеп, поэтому число то же. Реплика жива и формально готова, а запросы на неё продолжают идти.
Отсюда практическая проверка, которую стоит называть на собеседовании: readiness обязан отражать способность обслужить запрос, а не факт запуска процесса. Минимальная честная реализация проверяет то, без чего запрос не выполнится: соединение с базой, наличие места в пуле, состояние очереди задач.
И обратная сторона: чем больше проверок внутри readiness, тем выше шанс, что она станет ложно-отрицательной — реплика уйдёт из ротации из-за пустяка. Поэтому проверяют не «всё, что есть», а то, без чего этот сервис не обслуживает свои запросы.
Здесь и стоит назвать границу правила, симметричную границе для liveness. Из фразы «зависимости живут в readiness» напрашивается вывод «значит, надо перечислить в ней все зависимости» — и это тоже ошибка, просто другая. Readiness отвечает не на вопрос «всё ли вокруг меня в порядке», а на вопрос способна ли эта реплика обслужить хоть какой-то трафик. Сервис, который без второй базы всё ещё отвечает на часть своих запросов, обязан остаться в ротации, когда вторая база недоступна: выведя его, вы превратите частичную деградацию в полный отказ — и сделаете это своими руками, а не аварией.
А ещё readiness, перечисляющий все зависимости, возвращает ровно ту болезнь, которую лечил предыдущий раздел. Общая зависимость делает пробы синхронными независимо от того, в какой из них она записана: все реплики уходят из ротации одновременно, потому что смотрят на один и тот же внешний объект. Последствие мягче, чем при liveness, — реплики хотя бы не перезапускаются, — но резерв исчезает так же.
Как отвечать на собеседовании
Короткий ответ: проб три, и различаются они последствием — liveness перезапускает, readiness выводит из ротации, startup откладывает liveness на время запуска. Поэтому вопрос liveness звучит не «жив ли процесс», а «поможет ли перезапуск этого процесса»: внутрь неё попадает только то, что чинится перезапуском, а зависимости — в readiness.
Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.
Если интервьюер копает глубже
Хороший ответ отличают три вещи. Первая — вы называете цену ошибки числом: в модели liveness, завязанный на базу, добавил 200 неуспешных запросов к тем 1000, что стоила сама авария, потому что перезапустились все реплики сразу. Вторая — вы объясняете, зачем readiness при наличии liveness: перезапуск возвращает реплику в строй снова и снова, а вывод из ротации прекращает нагрузку; в модели 80 медленных ответов против 520. Третья — вы упоминаете обратную ошибку: readiness, который всегда отвечает «готов», делает балансировщик слепым, и это худший случай в модели — 1600.
И одна вещь, на которой легко переусердствовать. Из правила «зависимости — в readiness» не следует, что readiness должен быть суммой всех зависимостей. Точная формулировка звучит так: readiness отвечает на вопрос, способна ли реплика обслужить хоть какой-то трафик — и реплика, которая без одной из зависимостей обслуживает часть запросов, из ротации выходить не должна. Иначе проба сама превращает частичную деградацию в полный отказ, а общая зависимость снова делает все реплики синхронными.
Дальше спросят
Может ли readiness и liveness быть одним обработчиком?
Технически может. Но последствия у проб разные: одна перезапускает, другая перестаёт нагружать. Последствия у проб разные: одна перезапускает, другая перестаёт нагружать. Общий обработчик означает, что любая причина «не готов» превращается в перезапуск.
В модели это ровно четвёртая строка: проба, проверяющая базу, добавила 200 неуспешных запросов сверх тех, что стоила сама недоступность базы. Разделение обработчиков — самая дешёвая мера в этой теме.
Зачем нужна отдельная startup-проба?
Чтобы медленный запуск не выглядел как смерть. Если приложение поднимается минуту, а liveness настроен на секунды, оно будет перезапущено, не успев стартовать, — и так по кругу.
Альтернатива без отдельной пробы — растянуть пороги liveness настолько, чтобы их хватило на запуск. Но тогда та же растянутая проверка действует и в установившемся режиме, и настоящая взаимоблокировка будет замечена через минуту вместо секунды. Отдельная проба разделяет эти два режима.
Что проверять внутри readiness?
То, без чего этот сервис не может обслужить запрос: соединение с базой, если каждый запрос идёт в базу; место в пуле; заполненность кеша, если без него ответы бессмысленны. Не «всё, что есть»: каждая лишняя проверка увеличивает шанс ложного вывода из ротации.
Полезная проверка при написании: если проба вернёт «не готов», сможет ли реплика вернуться сама? Если да — это readiness. Если помочь может только перезапуск — это liveness.
Почему перезапуск иногда всё-таки помогает?
Потому что часть поломок — внутренние и накопительные: утечка дескрипторов (урок про лимиты), взаимоблокировка, пул потоков, из которого никто не возвращается. Перезапуск возвращает процесс в известное состояние, и это единственное лекарство, которое не требует понимать причину.
Цена — потеря всего накопленного: прогретых кешей, установленных соединений, разогретого пула. Поэтому перезапуск и не должен быть реакцией на внешнюю аварию: он лечит внутреннее и портит то, что было в порядке.
Частые заблуждения
liveness и readiness — это одно и то же с разными именами
У них разные последствия: liveness перезапускает процесс, readiness перестаёт слать ему запросы. В модели для деградировавшей реплики это 520 медленных ответов против 80 — в 6,5 раза, — потому что перезапущенная реплика возвращается в строй и снова деградирует, а выведенная из ротации просто не нагружается.
проба должна проверять всё, от чего зависит сервис
В liveness — только то, что чинится перезапуском этого процесса. Модель: проба, проверяющая общую базу, добавила 200 неуспешных запросов сверх тех 1000, что стоила сама недоступность базы, потому что все реплики перезапустились разом и поднимались уже после её возвращения. С readiness механически складывать все зависимости тоже нельзя: она отвечает на вопрос, способна ли реплика обслужить хоть какой-то трафик, и лишняя проверка внутри неё превращает частичную деградацию в полный отказ.
если реплика отвечает медленно, её надо перезапустить
Перезапуск возвращает процесс в известное состояние ценой потери прогретых кешей и соединений — и деградировавшая реплика вернётся и деградирует снова. В модели это видно прямо: только liveness даёт 520 медленных ответов против 80 при выводе из ротации.
readiness, который всегда отвечает «готов», безвреден
Это худший случай в модели: 1600 медленных ответов против 80. Балансировщик слепнет и продолжает слать нагрузку туда, где её некому обслужить. Readiness обязан отражать способность обслужить запрос, а не факт того, что процесс запущен.
startup-проба — необязательная тонкость
Без неё пороги liveness приходится растягивать под самый медленный запуск — и та же растянутая проверка действует потом в установившемся режиме, где настоящую взаимоблокировку заметят через минуту вместо секунды. Отдельная проба разделяет запуск и работу.
проверки здоровья повышают доступность сами по себе
Они меняют распределение отказов, а не их количество. Недоступность базы в модели стоила 1000 неуспешных запросов при любых пробах: чужую аварию проба не чинит. Что она может — не превратить одну аварию в две.
общая зависимость в пробе делает картину точнее
Она делает пробы синхронными: все реплики отказывают одновременно, потому что смотрят на один и тот же внешний объект. Резерв из трёх реплик перестаёт быть резервом ровно в тот момент, когда он нужен.
Практика
Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона модели, а не назначен.
Практика · что напечатает
none = simulate("none")
liveness = simulate("liveness")
both = simulate("liveness+readiness")
on_db = simulate("liveness-on-db")
print(none["slow"])
print(both["slow"])
print(on_db["failed"] - liveness["failed"])Практика · оцените
Проверка знаний
Чем liveness отличается от readiness?
Это не пересказ и не отдельный текст: всё ниже взято из самой статьи — её выжимка, заголовки разборов, колонка «на самом деле» и таблица версий. Поэтому разойтись со статьёй эти тезисы не могут.
Суть
- Проверка здоровья — не один вопрос, а три. Startup спрашивает, успело ли приложение подняться; liveness — поможет ли перезапуск этого процесса; readiness — можно ли слать сюда трафик. Различаются они не строгостью, а последствием: перезапуск, ожидание или вывод из ротации. Поэтому и содержимое каждой пробы выводится из её последствия: проверять надо то, что этим последствием чинится.
- Отсюда главная ошибка: проба, завязанная на общую зависимость, превращает одну аварию в две. Перезапуск не делает доступнее то, что находится снаружи процесса, — база, сеть и соседний сервис от него не оживают, зато процесс теряет прогретые кеши и пулы соединений. Модель: недоступность базы стоит 1000 неуспешных запросов сама по себе и 1200, если liveness проверяет базу. Лишние 200 — это перезапуск всех реплик разом.
- Дальше — числа и границы. У деградировавшей реплики один liveness даёт 520 медленных ответов, liveness плюс readiness — 80, то есть в 6,5 раза меньше; полное отсутствие проб — 1600, потому что балансировщик продолжает слать нагрузку на больную реплику. Ровно так же он слепнет от readiness, который всегда отвечает «готов». Но и обратная крайность существует: readiness — не сумма всех зависимостей сервиса, а ответ на вопрос, способна ли реплика обслужить хоть какой-то трафик. Числа здесь модельные: они выводятся из объявленных допущений, и содержательны отношения между строками, а не абсолютные значения.
На самом деле
- У них разные последствия: liveness перезапускает процесс, readiness перестаёт слать ему запросы. В модели для деградировавшей реплики это 520 медленных ответов против 80 — в 6,5 раза, — потому что перезапущенная реплика возвращается в строй и снова деградирует, а выведенная из ротации просто не нагружается.
- В
liveness— только то, что чинится перезапуском этого процесса. Модель: проба, проверяющая общую базу, добавила 200 неуспешных запросов сверх тех 1000, что стоила сама недоступность базы, потому что все реплики перезапустились разом и поднимались уже после её возвращения. Сreadinessмеханически складывать все зависимости тоже нельзя: она отвечает на вопрос, способна ли реплика обслужить хоть какой-то трафик, и лишняя проверка внутри неё превращает частичную деградацию в полный отказ. - Перезапуск возвращает процесс в известное состояние ценой потери прогретых кешей и соединений — и деградировавшая реплика вернётся и деградирует снова. В модели это видно прямо: только liveness даёт 520 медленных ответов против 80 при выводе из ротации.
- Это худший случай в модели: 1600 медленных ответов против 80. Балансировщик слепнет и продолжает слать нагрузку туда, где её некому обслужить. Readiness обязан отражать способность обслужить запрос, а не факт того, что процесс запущен.
- Без неё пороги liveness приходится растягивать под самый медленный запуск — и та же растянутая проверка действует потом в установившемся режиме, где настоящую взаимоблокировку заметят через минуту вместо секунды. Отдельная проба разделяет запуск и работу.
- Они меняют распределение отказов, а не их количество. Недоступность базы в модели стоила 1000 неуспешных запросов при любых пробах: чужую аварию проба не чинит. Что она может — не превратить одну аварию в две.
- Она делает пробы синхронными: все реплики отказывают одновременно, потому что смотрят на один и тот же внешний объект. Резерв из трёх реплик перестаёт быть резервом ровно в тот момент, когда он нужен.
Что разобрано
- Что здесь на самом деле спрашивают
- База: три вопроса, а не одна проверка
- Механизм 1: последствие и есть определение
- Механизм 2: что даёт каждая проба
- Механизм 3: почему liveness не ходит в базу
- Глубже: границы правила и обе ошибки readiness
- Как отвечать на собеседовании
- Дальше спросят
- Частые заблуждения
- Практика
- Проверка знаний
Источники и что читать дальше
1 ИСТОЧНИК
- Модель этого урока: пробы и каскад перезапусковИсточник. Внешнего первоисточника у этого урока нет: общего стандарта на поведение проб не существует, есть документация отдельных продуктов. Поэтому урок стоит на модели с объявленными допущениями: три реплики, постоянный поток, одна реплика деградирует с десятой секунды, общая база недоступна с двадцатой по тридцатую, проба раз в секунду, порог — три неудачи подряд, перезапуск пять секунд. Всё, что печатает прогон, выводится из этих правил и проверяется чтением скрипта./ru/bench/probes/model.py