SRE и надёжность Надёжность, доступность и отказоустойчивость: что чем меряют
0%

Надёжность, доступность и отказоустойчивость: что чем меряют

Надёжность, доступность и отказоустойчивость: что чем меряют

Разговор, который случается в каждой второй команде. «Мы надёжные, у нас всё в двух зонах». — «А сколько это в цифрах?» — «Ну, четыре девятки где-то». — «Это сколько минут в месяц?» — «Много. То есть мало. Сейчас посчитаю».

Четыре девятки — это 4 минуты 19 секунд в месяц. Меньше, чем занимает автоматическое переключение managed-базы на резерв. Меньше, чем нужно дежурному, чтобы проснуться, найти телефон и открыть ноутбук. Команда, произнёсшая «четыре девятки», почти наверняка имеет в виду что-то другое — но что именно, она сама пока не знает.

Эта глава — про то, чтобы такой разговор больше не происходил. Не ради точности как таковой, а потому что за тремя словами — надёжность, доступность, отказоустойчивость — стоят три разные величины, три разных способа их улучшать и три разных статьи расходов. Пока они слипаются в одно, обсуждение приоритетов остаётся обменом ощущениями.

Дальше по треку из этих понятий вырастет всё остальное: SLI и SLO — как выбрать, что мерить, бюджет ошибок — что делать с разницей между целью и фактом. Здесь мы строим фундамент: словарь и арифметику.

Три слова, которые обычно путают

Надёжность

Надёжность (reliability) — вероятность того, что система отработает без отказа на заданном интервале. Это функция от длины интервала, а не одно число. Классическая модель: если интенсивность отказов $\lambda$ постоянна, то $R(t) = e^{-\lambda t}$, а обратная к $\lambda$ величина — MTTF (mean time to failure), среднее время до отказа.

Пример. Диск с заявленным MTTF 1 200 000 часов даёт $\lambda = 1/1200000$ отказов в час. Вероятность прожить год (8760 часов) без отказа: $R(8760) = e^{-0.0073} \approx 0.9927$. То есть 0,73 % дисков откажут за год; в стойке на 200 дисков это примерно полтора диска в год — нормально работающая стойка, а не авария.

Главное следствие формулы: надёжность не аддитивна по времени. Система, надёжная на 99 % за час, за месяц надёжна на $0.99^{720} \approx 0.0007$, то есть на 0,07 %. Фраза «сервис надёжен на 99 %» без указания интервала — не утверждение, а звук.

Для софта экспоненциальная модель работает плохо: у программ нет износа, зато есть корреляция отказов с релизами, нагрузкой и временем суток. Но интуиция «надёжность привязана к интервалу» остаётся верной.

Доступность

Доступность (availability) — доля времени (или доля запросов), когда система выполняет свою функцию. Одно безразмерное число:

$$A = \frac{T_{up}}{T_{up} + T_{down}} = \frac{MTBF}{MTBF + MTTR}$$

MTBF (mean time between failures) — среднее время между отказами восстанавливаемой системы, MTTR (mean time to repair) — среднее время восстановления.

Разница с надёжностью принципиальна. Система, падающая раз в час и поднимающаяся за секунду, имеет доступность 99,97 % и почти нулевую надёжность на часовом интервале. Система, падающая раз в год и лежащая трое суток, имеет надёжность на часовом интервале почти единицу и доступность 99,18 %. Это два разных мира с разной ценой для клиента.

Из формулы следует главный практический вывод всего SRE: есть два рычага, и второй почти всегда дешевле. Поднять доступность можно, либо реже ломаясь (растить MTBF), либо быстрее чинясь (сокращать MTTR). Сокращение MTTR вдвое даёт тот же эффект, что удвоение MTBF, — но удвоить MTBF значит переписать половину системы, а вдвое ускорить восстановление часто значит написать один ранбук и один скрипт отката.

Отказоустойчивость

Отказоустойчивость (fault tolerance) — не метрика, а свойство архитектуры: способность продолжать выполнять функцию при отказе части компонентов. В процентах её померить нельзя, у неё другие параметры: какие именно отказы система переживает (отказ узла — да, отказ зоны — да, порча данных при репликации — нет); покрытие (fault coverage) — какая доля реально случающихся отказов попадает в этот список; цена переключения; степень избыточности N+1, N+2, 2N.

Отказоустойчивость — средство, доступность — цель. Путать их дорого: «у нас всё в трёх зонах» ничего не говорит о доступности, пока не измерено покрытие и не посчитано время переключения. Ниже будет пример, где дублирование даёт прибавку в 9 раз вместо ожидаемых 100.

Робастность и эластичность

Робастность (robustness) — способность сохранять функцию при возмущениях из известного множества. Таймаут, ретрай, лимит памяти, второй канал — всё это робастность: мы заранее знаем, что пойдёт не так, и ставим барьер.

Эластичность (resilience) — способность восстанавливать функцию после возмущений вне известного множества. Это про людей, обходные пути и работу в режиме, который никто не проектировал.

Дэвид Вудс в работе «Four concepts for resilience» (RESS, 2015) показывает, почему эти свойства нельзя наращивать одним способом. Робастность добавляется барьерами, каждый барьер добавляет сложность, а сложность сама становится источником отказов — причём нового, неизвестного типа, против которых барьеров нет. Команда, добавляющая после каждого инцидента ещё одну проверку, через два года получает систему, где причины инцидентов — сами проверки. Практический вывод: барьеры ставят только там, где отказ повторяем и посчитан; всё остальное закрывается способностью быстро понять и быстро откатить — это реакция на инцидент и безопасные релизы, а не ещё один if.

Откуда берётся отказ: дефект, ошибка, отказ

Самая полезная классификация в области — из работы Авиженис, Лапри, Рэнделла и Ландвера «Basic Concepts and Taxonomy of Dependable and Secure Computing» (IEEE TDSC, 2004). Она различает три вещи, которые в разговоре сливаются в «сломалось»:

  • Дефект (fault) — причина: неверная строка кода, севший диск, ошибка в конфиге, разряженная батарея в ИБП. Может годами лежать неактивным.
  • Ошибка (error) — состояние: дефект активировался и перевёл систему в некорректное внутреннее состояние. Снаружи ещё ничего не видно.
  • Отказ (failure) — событие на границе: некорректное состояние доехало до интерфейса и нарушило контракт с пользователем.

Между ними стоят барьеры, и вся отказоустойчивость живёт именно в промежутках, а не в «отсутствии дефектов».

Три следствия, ради которых стоит держать схему в голове.

Первое. «Мы починили баг» — работа с левым краем схемы, и она никогда не закончится: дефектов бесконечно много, а барьеров конечное число, и они работают против целых классов дефектов сразу. Постмортем, заканчивающийся исправлением конкретной строчки, потрачен зря — подробно в главе «Постмортемы».

Второе. Отказ определяется контрактом, а не кодом. Если контракт — «страница заказа открывается за 2 секунды», то ответ за 30 секунд с кодом 200 является отказом, хотя ни одно исключение не выброшено. Отсюда вырастает вся конструкция SLI: пока контракт не записан, слово «отказ» не определено.

Третье, самое коварное, — латентные дефекты. Резервный генератор, который не заводится. Реплика, на которую никогда не переключались. Скрипт отката, запускавшийся последний раз при написании. Всё это дефекты, активирующиеся ровно в момент, когда на них рассчитывают. Единственный способ узнать, работает ли барьер, — активировать его специально; это содержание главы «Проверка отказом».

Метрики: MTBF, MTTR и их родня

Аббревиатур в области больше, чем смысла. Полезны четыре, и все они — отрезки на временной оси одного инцидента.

Разберём типичный по структуре инцидент:

Момент Событие Отрезок
03:14 Выкатился релиз, доля 500-х поползла вверх
03:22 Сработал алерт на долю ошибок MTTD = 8 мин
03:27 Дежурный подтвердил вызов MTTA = 5 мин
03:41 Нашли связь с релизом по времени и трейсам диагностика = 14 мин
03:49 Откатили релиз, ошибки ушли митигация = 8 мин

Итого MTTR (от начала до восстановления функции) — 35 минут, из них собственно починка занимает 8. Остальные 27 — обнаружение, подтверждение и диагностика. Пропорция типичная, и из неё следует структура всего трека: чтобы вдвое сократить MTTR, вкладываться нужно не в «быстрее чинить», а в мониторинг, алерты и реакцию на инцидент.

Отдельно: митигация — не то же самое, что исправление. Инцидент заканчивается, когда функция вернулась пользователю, а не когда найдена первопричина. Откат релиза, отключение фичи флагом, перевод трафика в здоровый регион — валидные завершения инцидента. Первопричину ищут утром, письменно, на свежую голову.

Считаем требуемый MTBF

Формулу $A = MTBF/(MTBF+MTTR)$ разворачиваем: при заданной цели и MTTR получаем, как редко вы имеете право ломаться, — $MTBF = A \cdot MTTR / (1 - A)$.

def требуемый_mtbf(цель: float, mttr_мин: float) -> float:
    """Средний интервал между отказами (мин.), при котором цель достижима.
    Время O(1), память O(1)."""
    return цель * mttr_мин / (1.0 - цель)

for цель in (0.999, 0.9999):
    for mttr in (30, 5, 2):
        print(f"цель {цель:.4%}, MTTR {mttr:>2} мин → "
              f"не чаще раза в {требуемый_mtbf(цель, mttr) / 1440:.1f} суток")
Цель MTTR = 30 мин MTTR = 5 мин MTTR = 2 мин
99,9 % раз в 20,8 суток раз в 3,5 суток раз в 1,4 суток
99,99 % раз в 208 суток раз в 34,7 суток раз в 13,9 суток

Прочитайте нижнюю строку внимательно. Четыре девятки при получасовом восстановлении означают право сломаться раз в семь месяцев — для команды, которая выкатывает релизы, это фантастика. Те же четыре девятки при двухминутном восстановлении означают «раз в две недели», а это вполне реалистично.

Отсюда честная формулировка: 99,99 % — утверждение не о том, что вы не ломаетесь, а о том, что у вас автоматизировано восстановление. Требование четырёх девяток есть требование автоматического отката, автоматического переключения и алертов, срабатывающих за секунды. Без этого обещать четыре девятки нечестно независимо от количества зон доступности.

Арифметика девяток

Считаем в окне 30 дней — это стандартное окно для SLO, и в нём удобные числа: 43 200 минут, 2 592 000 секунд.

СЕКУНД_В_МЕСЯЦЕ = 30 * 24 * 3600      # 2 592 000
СЕКУНД_В_ГОДУ = 365 * 24 * 3600       # 31 536 000

def бюджет_простоя(цель: float, окно_с: int = СЕКУНД_В_МЕСЯЦЕ) -> float:
    """Секунд недоступности, допустимых целью. Время O(1), память O(1)."""
    return (1.0 - цель) * окно_с

for цель in (0.99, 0.995, 0.999, 0.9995, 0.9999, 0.99999):
    print(f"{цель:.5%}: {бюджет_простоя(цель) / 60:8.2f} мин/мес   "
          f"{бюджет_простоя(цель, СЕКУНД_В_ГОДУ) / 60:9.2f} мин/год")
Цель В месяц (30 дней) В год (365 дней) Расчёт для месяца
99 % 7 ч 12 мин 3 сут 15 ч 36 мин 43 200 × 0,01 = 432 мин
99,5 % 3 ч 36 мин 1 сут 19 ч 48 мин 43 200 × 0,005 = 216 мин
99,9 % 43 мин 12 с 8 ч 45 мин 36 с 43 200 × 0,001 = 43,2 мин
99,95 % 21 мин 36 с 4 ч 22 мин 48 с 43 200 × 0,0005 = 21,6 мин
99,99 % 4 мин 19 с 52 мин 34 с 43 200 × 0,0001 = 4,32 мин
99,999 % 25,9 с 5 мин 15 с 43 200 × 0,00001 = 0,432 мин
99,9999 % 2,6 с 31,5 с 43 200 × 0,000001 = 0,0432 мин

Каждая дополнительная девятка делит бюджет на десять — в логарифмическом масштабе ровно одна декада влево. На той же шкале удобно разместить длительности обычных эксплуатационных операций.

Месячный бюджет простоя в логарифмическом масштабе рядом с длительностью обычных операций

Почему пять девяток почти никому не нужны

Аргументов четыре, и все они арифметические, а не эстетические.

Первый: бюджет короче одной штатной операции. 25,9 секунды в месяц. Автоматический фейловер managed-базы занимает 60–90 секунд, то есть одно штатное срабатывание резерва съедает три месячных бюджета. Проверим на четырёх девятках: бюджет 259 секунд, фейловер по 90 секунд четыре раза в месяц (обновление minor-версии, перенос на другой хост, два реальных инцидента) — это 360 секунд, перерасход в 1,4 раза при том, что ничего необратимого не сломалось. Ключевой момент, который редко проговаривают: время переключения на резерв — это тоже простой, и на уровне четырёх девяток оно становится доминирующей статьёй расхода.

Второй: зависимости ставят потолок. Ваша доступность не превышает доступности обязательных зависимостей. Типичный managed-сервис базы данных даёт SLA 99,95 % (AWS, Google Cloud — актуальные цифры смотрите в документах, они меняются). Три обязательных зависимости по 99,95 % дают $0.9995^3 = 0.99850$ — 64 минуты простоя в месяц, хуже трёх девяток, ещё до первой строчки вашего кода. И отдельно: SLA-кредит — не страховка. Провайдер вернёт процент от счёта; если счёт 50 000 RUB, а за час простоя вы потеряли 2 000 000 RUB, кредит закроет 0,25 % убытка.

Третий: канал пользователя. Пользователь видит произведение вашей доступности на доступность своего канала, а мобильная сеть в дороге теряет заметно больше 1 % запросов. При вашей доступности 99,99 % он видит $0.9999 \times 0.99 = 0.98990$; при 99,999 % — $0.99999 \times 0.99 = 0.98999$. Разница 0,0089 %, то есть 9 запросов на 100 000: не отличит никогда, ни при каком объёме использования. Все деньги на переход от четырёх девяток к пяти для мобильного пользователя ушли в ноль.

Четвёртый: пять девяток нельзя измерить за разумное время. Чтобы отличить 99,999 % от 99,995 %, нужно различить 130 секунд простоя в месяц. Один инцидент средней паршивости даёт больше, значит месячное измерение вообще ничего не различает: месяц без инцидента одинаково согласуется с обеими гипотезами, месяц с инцидентом опровергает обе. Нужны годы наблюдений или объём трафика, при котором доступность считается по запросам с миллиардами событий в окне. Именно здесь проходит настоящая граница между Google и всеми остальными: не в мастерстве, а в том, что при $10^{11}$ запросов в месяц четыре знака после запятой статистически осмысленны, а при $10^{7}$ это шум.

Что из этого следует. Для подавляющего большинства сервисов разумный диапазон — от 99,5 % до 99,9 %; внутренние инструменты прекрасно живут на 99 %. Четыре девятки оправданы там, где минута простоя стоит десятки тысяч и восстановление автоматизировано полностью. Пять девяток осмысленны для узкого класса компонент — DNS-резолвер, точка терминации TLS, шина авторизации, — где нет состояния, нет релизов и всё сводится к резервированию железа. И даже там их держат не «в системе», а в конкретном компоненте.

Композиция: как доступности складываются

Отдельный сервис никого не интересует — интересует путь пользователя, а он проходит через десяток компонентов.

Последовательное соединение. Если для успеха нужны все компоненты, доступности перемножаются: $A_{ser} = \prod_{i=1}^{n} A_i$. Пять обязательных зависимостей по 99,9 % дают $0.999^5 = 0.99501$ — 99,50 %, то есть 215,6 минуты простоя в месяц. Пять хороших компонентов дали посредственный результат: каждая обязательная зависимость — вычет из вашей доступности, тем больнее, чем выше цель.

Обратная задача полезнее. Хотите 99,9 % на пути из десяти обязательных компонентов? Каждый должен давать $0.999^{1/10} = 0.9999$ — четыре девятки на звене, чтобы получить три на выходе (проверка: $0.9999^{10} = 0.99900$, ровно 43,2 минуты). Отсюда главный архитектурный рычаг: не улучшать звенья, а сокращать их число. Каждая зависимость, которую удалось сделать необязательной (кэш вместо синхронного вызова, очередь вместо RPC, значение по умолчанию вместо запроса в справочник), вычёркивается из произведения целиком — это в разы дешевле, чем поднимать девятку на каждом из десяти сервисов. Приёмы — в главе «Деградация вместо отказа» и в паттернах устойчивости.

Параллельное соединение. Если достаточно любого компонента, перемножаются вероятности отказа: $A_{par} = 1 - \prod_{i=1}^{n}(1 - A_i)$. Две реплики по 99 % дают $1 - 0.01^2 = 0.9999$ — четыре девятки из двух посредственных узлов. Выглядит как магия и является ею: посылка о независимости отказов почти всегда ложна.

Общая мода отказа — там, где ломается вся модель

Реплики не независимы. Их валит один и тот же плохой конфиг, один и тот же релиз, одна и та же запись в DNS, одна и та же квота у провайдера, один и тот же баг в клиентской библиотеке. Инженерия надёжности описывает это β-фактором — долей отказов, поражающих все резервы сразу:

$$q_{sys} = \beta q + \left((1-\beta) q\right)^{n}$$

где $q = 1 - A$ — вероятность отказа одного узла, $\beta$ — доля общих отказов.

def параллельно_с_общей_модой(a: float, n: int, beta: float) -> float:
    """n одинаковых резервов; доля beta отказов валит все сразу.
    beta берут из истории постмортемов, а не из головы.
    Время O(1), память O(1)."""
    q = 1.0 - a
    q_общ = beta * q               # общая мода: конфиг, деплой, сеть, DNS
    q_инд = (1.0 - beta) * q       # независимая часть
    return 1.0 - (q_общ + q_инд ** n)

print(f"{параллельно_с_общей_модой(0.99, 2, 0.0):.6f}")   # 0.999900 — идеал
print(f"{параллельно_с_общей_модой(0.99, 2, 0.1):.6f}")   # 0.998919 — реальность
print(f"{параллельно_с_общей_модой(0.99, 3, 0.1):.6f}")   # 0.998999 — третий узел почти не помог

Считаем руками для двух узлов по 99 % и $\beta = 0{,}1$: $q = 0{,}01$; общая часть $0{,}1 \times 0{,}01 = 0{,}001$; независимая $0{,}9 \times 0{,}01 = 0{,}009$, в квадрате $0{,}000081$; итого отказ системы $0{,}001081$, доступность 99,892 % — 46 минут 42 секунды в месяц.

Сравните три числа: один узел — 432 мин/мес, идеальный резерв — 4,3 мин/мес, реальный резерв — 46,7 мин/мес. Резервирование улучшило доступность в 9 раз, а не в 100. Третий узел добавит почти ничего: независимая часть уже пренебрежимо мала, всё упирается в общую моду.

Три надёжностные блок-схемы: последовательное соединение, параллельное и параллельное с общей модой отказа

Практический вывод один, но он определяет порядок работ: пока β велика, добавление узлов не помогает — помогает только уменьшение β. Уменьшают её вещами, которые не выглядят как «надёжность»: раздельной раскаткой релиза по зонам, отдельными конфигами, отдельными квотами, независимыми путями DNS (что там ломается), разнесёнными окнами обслуживания. Это скучная организационная работа, и она даёт больше, чем третья зона.

Где взять β? Из постмортемов: сколько инцидентов за год затронули все реплики сразу, делить на общее число. Обычно получается 0,2–0,5 — сильно хуже, чем десятая часть из примера. Об устройстве отказов подробнее — «Как ломаются распределённые системы» и модели отказов.

Доступность по времени и по запросам — это два разных числа

До сих пор мы считали долю времени. Это работает для того, что либо целиком включено, либо целиком выключено. Реальные сервисы деградируют частично, и тут время начинает врать.

Ситуация: сервис 5 минут отдавал 50 % ошибок, средний трафик 1000 запросов в секунду.

По времени. Сервис «не работал» 5 минут: $300 / 2592000 = 0{,}000116$ — 0,0116 % бюджета, доступность 99,988 %. (Считать ли половинную деградацию половиной простоя — уже произвол.)

По запросам. Плохих запросов $1000 \times 300 \times 0{,}5 = 150000$, всего за месяц $1000 \times 2592000 = 2{,}592 \times 10^9$. Доля: $150000 / (2{,}592 \times 10^9) = 0{,}0000579$ — 0,00579 % бюджета, доступность 99,9942 %.

Числа разошлись вдвое. А теперь тот же сбой на пике, где трафик в 5 раз выше среднего: плохих запросов $5000 \times 300 \times 0{,}5 = 750000$, доля $0{,}000289$ — 0,0289 % бюджета, доступность 99,971 %.

Метрика по времени в обоих случаях выдаёт одно и то же — 99,988 %. Метрика по запросам различает ночной сбой и пиковый в 5 раз, и это ближе к тому, что чувствует бизнес: пятиминутный сбой в чёрную пятницу и в четыре утра во вторник — разные события, мерить их одинаково значит сознательно ослепнуть.

  • По времени — уместно, когда сервис либо есть, либо нет: сетевой канал, кластер БД, VPN-концентратор. Плюс удобно для внешних договоров, потому что понятно юристам.
  • По запросам (событиям) — уместно почти для всего остального и точнее отражает ущерб. Именно это лежит в основе современных SLO.
  • По «хорошим окнам» — минута считается хорошей, если доля успешных запросов в ней выше порога. Сглаживает шум на низком трафике, где одна ошибка из десяти запросов даёт 10 % и рвёт любые пороги.

Разбор компромиссов между этими способами — тема следующей главы. Практическая заметка: если у вас меньше нескольких десятков запросов в минуту, ни один способ не даст устойчивого числа за короткое окно. Не рисуйте четыре знака после запятой — расширяйте окно до квартала и добавляйте активные пробы, чтобы был предсказуемый поток событий. Про то, как из метрик вообще получаются такие числа, — «Мониторинг» и наблюдаемость в распределённых системах.

Отказоустойчивость: что здесь реально меряют

Раз это не метрика, что предъявлять вместо процентов?

Покрытие отказов. Возьмите список инцидентов за год и отметьте, сколько из них механизм резервирования должен был обработать автоматически — и сколько обработал. Типичный результат первого замера — 0,3–0,5: автоматика ловит отказ узла, но не ловит деградацию (узел жив, отвечает, но за 30 секунд), порчу данных, исчерпание квоты и «серые» отказы, когда часть трафика проходит, а часть нет. Серые отказы — главная дыра в покрытии и источник самых долгих инцидентов.

Время переключения. Замерьте его, а не берите из документации, и умножьте на ожидаемое число срабатываний в месяц — получите вклад отказоустойчивости в ваш простой. На уровне четырёх девяток это доминирующая статья.

Степень избыточности и её честная стоимость:

Схема Что переживает Множитель к счёту
Один узел ничего ×1
N+1 отказ любого одного узла ×1,1…×1,5 в зависимости от N
2N (полный дубль) отказ половины мощности ×2
Мульти-AZ отказ зоны доступности ×1,8…×2,2 плюс межзонный трафик
Мульти-регион отказ региона, катастрофу ×2,2…×3 плюс сложность репликации

Правый столбец — не метафора. Мульти-регион с активной репликацией означает удвоенную инфраструктуру, оплату трафика между регионами, а главное — необходимость решить, что делать с расхождением данных при разделении сети. Это вопросы из репликации и шардирования баз, и они не про надёжность, а про корректность: неправильно разрешённый конфликт хуже честного отказа.

Единая точка отказа. Отдельно выпишите, что при отказе валит всё. Обычно в списке оказываются не серверы, а «одна запись в DNS», «один секрет с истекающим сертификатом», «один человек, который знает, как выкатить», «один аккаунт у провайдера с общей квотой». Последние два — организационные и третьей зоной доступности не лечатся.

Каскад — это усиливающая петля

Самая опасная динамика выглядит так: небольшая деградация зависимости приводит к росту нагрузки на неё же, что усиливает деградацию. Механизм — ретраи: если клиент делает до 3 попыток, а доля отказов зависимости $p$, эффективный множитель нагрузки есть сумма геометрической прогрессии.

def усиление_ретраями(p_отказа: float, попыток: int) -> float:
    """Во сколько раз вырастет нагрузка на деградирующую зависимость.
    Время O(k), память O(1)."""
    return sum(p_отказа ** i for i in range(попыток))

for p in (0.0, 0.2, 0.5, 0.8, 1.0):
    print(f"отказов {p:.0%} → нагрузка ×{усиление_ретраями(p, 3):.2f}")
Доля отказов зависимости 0 % 20 % 50 % 80 % 100 %
Множитель нагрузки на неё ×1,00 ×1,24 ×1,75 ×2,44 ×3,00

Прочитайте таблицу как динамику, а не как статику. Нагрузка растёт монотонно по мере того, как зависимости становится хуже. Сервис, начавший спотыкаться на 20 % запросов, получает +24 % трафика; от этого доля отказов растёт до 50 %, что даёт +75 %; дальше ×2,44 и ×3. Система не приходит в равновесие — она разгоняется до полной остановки.

Это ровно то, что в системном мышлении называется усиливающей петлёй: выход контура добавляется ко входу с тем же знаком. Каскадный отказ и усиливающая петля — не аналогия, а одно явление на двух языках. Знать это полезно не ради красоты: теория петель сразу подсказывает лечение — разорвать связь (размыкатель), ограничить усиление (бюджет ретраев вместо фиксированного числа попыток), добавить отрицательную обратную связь (сброс нагрузки при насыщении).

Добавьте задержки — время, пока автоскейлер заметит рост и приведёт мощность, — и получите классическую картину: система колеблется и не выходит на режим даже после устранения исходной причины. Механику разбираем в главах «Деградация вместо отказа» и «Как ломаются распределённые системы».

Цена: где инженерное решение упирается в организационное

Всё выше — арифметика. Ниже — то, во что она превращается в живой компании. Эту часть обычно пропускают, и именно поэтому программы надёжности глохнут через полгода.

Дежурство: девятка — это требование к штату

Круглосуточное покрытие — 168 часов в неделю. Чтобы человек не выгорал, доля времени под пейджером не должна превышать примерно четверти рабочего времени, а ночные вызовы должны компенсироваться отгулом. Отсюда арифметически следует минимум 6 человек в ротации для схемы «неделя дежурства, пять недель без» — или две-три географически разнесённые команды по 2–3 человека для follow-the-sun.

Соедините с предыдущей арифметикой. Цель 99,99 % при получасовом MTTR требует ломаться раз в 208 суток — недостижимо, значит MTTR нужно сократить до минут. Разбуженный ночью человек физически не даёт MTTR в минуты: одно пробуждение и открытие ноутбука — 5 минут, то есть весь месячный бюджет четырёх девяток целиком (это видно на шкале выше).

Вывод, который надо произносить вслух на встрече с руководством: четыре девятки нельзя купить дежурствами, их можно купить только автоматикой. А три девятки дежурствами купить можно — если в ротации есть шесть человек. Если в команде четыре инженера, круглосуточная ротация означает дежурство каждую вторую неделю, и через квартал у вас будет не надёжность, а увольнения. Это не вопрос стойкости людей, а вопрос арифметики расписания. Со стороны руководителя — «Дежурства и инциденты»; со стороны инженера, с расписаниями и эскалацией — «Дежурство».

Алерты без действия: механизм, а не мораль

Утверждение «алерт, на который нельзя ничего сделать, нужно удалить» обычно подаётся как гигиена. На самом деле у него есть измеримый механизм, бьющий прямо в доступность.

  1. Дежурный получает алерт, на который нет действия: смотрит, ничего не делает, закрывает.
  2. Повторяется десять раз — формируется ожидание «скорее всего, опять ничего».
  3. При следующем алерте он не идёт к ноутбуку сразу, а смотрит в телефон и ждёт, не самоустранится ли.
  4. MTTA растёт с 2 минут до 15.
  5. По формуле $A = MTBF/(MTBF+MTTR)$ рост MTTR на 13 минут при MTBF в 20 суток снижает доступность с 99,90 % до 99,86 % — съедает 18 минут месячного бюджета.

Шумные алерты не «раздражают» — они измеримо снижают доступность, потому что удлиняют самый дешёвый в сокращении отрезок MTTR. Это делает чистку алертов не заботой о людях, а работой по достижению SLO, и защищать её перед бизнесом становится намного легче («Алерты»). Тот же механизм работает с рутиной: каждый час, потраченный дежурным на ручные операции, — час, когда он не смотрит на систему и не улучшает автоматику восстановления («Рутина и автоматизация»).

Постмортемы без обвинения: тоже механизм

Забегая к главе «Постмортемы», сформулируем принцип на языке этой главы, потому что он прямо следует из схемы «дефект → ошибка → отказ».

Чтобы поставить барьер, надо точно знать, где и как дефект активировался. Эти данные есть только у одного человека — того, кто был в контуре. Если разбор заканчивается вопросом «кто это выкатил», в следующий раз этот человек не расскажет, что пропустил шаг чек-листа, потому что торопился; не признается, что не понял вывода мониторинга и решил, что всё в порядке; не сообщит, что ранбук устарел и он действовал по памяти.

Это не про моральный дух. Это про то, что при поиске виноватого поток данных о реальном устройстве работы пересыхает, и вы начинаете строить барьеры на основе выдуманной картины. Барьеры, поставленные не там, увеличивают сложность (см. выше про робастность) и не уменьшают отказы. Ричард Кук в «How Complex Systems Fail» формулирует это тезисом, который стоит выписать: практики одновременно производят и отказы, и защиту от них, и разделить эти два вклада постфактум невозможно.

Проверяемое следствие, а не лозунг: если в ваших постмортемах не встречаются формулировки «я не понял», «я решил пропустить», «документация врала» — это не значит, что таких ситуаций нет. Это значит, что вам о них не рассказывают.

Избыточность: считаем деньги честно

Сервис приносит 3 000 000 RUB выручки в месяц, простой линейно бьёт по выручке (упрощение, к нему вернёмся). Минута простоя стоит 3 000 000 / 43 200 = 69,4 RUB. Переход с 99,9 % на 99,99 % экономит 43,2 − 4,32 = 38,9 минуты в месяц, то есть 38,9 × 69,4 ≈ 2 700 RUB в месяц.

Что стоит эта девятка: вторая зона доступности с полноценным резервом (+80…100 % к счёту за инфраструктуру, скажем 120 000 RUB/мес), круглосуточная ротация из 6 человек с доплатой 25 000 RUB на человека (150 000 RUB/мес) и разово несколько человеко-месяцев на автоматизацию восстановления. Итого около 270 000 RUB в месяц против 2 700 RUB экономии. Отношение 100:1 не в пользу девятки. Инженер, который приходит с предложением «давайте сделаем четыре девятки» без этого расчёта, просит компанию потратить сто рублей, чтобы сэкономить один.

Тот же расчёт для платёжного шлюза, где минута простоя стоит 100 000 RUB (упущенные транзакции, штрафы по договорам, отток мерчантов): 38,9 × 100 000 = 3 890 000 RUB экономии против тех же 270 000 расходов — окупается четырнадцатикратно, вопросов нет.

Целевая доступность — функция от стоимости минуты, а не показатель инженерной зрелости. Команда с целью 99,5 % не менее профессиональна, чем команда с 99,99 %, — у неё другая экономика. И наоборот: команда, держащая 99,99 % там, где хватило бы 99,5 %, сжигает деньги и людей за метрику, которой никто не пользуется.

Почему сумма минут — не вся правда

Расчёт выше линеен, и это его слабое место: реальный ущерб нелинеен по длине инцидента. Сорок отдельных одноминутных сбоев пользователи, скорее всего, не заметят — попробуют ещё раз. Один непрерывный сбой на 40 минут попадёт в соцсети, вызовет шквал в поддержку, нарушит условия договоров и оставит след в памяти.

Поэтому кроме цели на доступность полезно иметь цель на длину худшего инцидента: например, «ни один инцидент не длится дольше 30 минут». Она не выводится из процента доступности, ограничивает распределение, а не среднее, и часто ведёт к другим инженерным решениям — к быстрым откатам и обходным путям вместо дополнительных резервов. Как отсюда строится бюджет ошибок и почему он меняет решение о релизе — «Бюджет ошибок».

Как выбирать целевую доступность

Процедура из четырёх шагов, каждый даёт число.

  1. Измерьте текущее — не оценивайте, а измерьте хотя бы грубо за прошедшие три месяца. Почти всегда результат ниже, чем все думали, и это уже полезный разговор.
  2. Посчитайте потолок от зависимостей — перемножьте SLA обязательных. Цель выше потолка недостижима без резервирования этих зависимостей, а это отдельный проект со своей ценой.
  3. Оцените стоимость минуты — выручка в час, штрафы по договорам, стоимость обращения в поддержку, оценка оттока. Числа будут грубые, порядок величины решает.
  4. Сравните дельту экономии со стоимостью следующей девятки. Если отношение не в пользу девятки — не берите её, даже если очень хочется.

Заметьте, что «ночной ETL» и «платёжный шлюз» требуют не разных степеней старательности, а разных инженерных решений. ETL с дорогим простоем, но терпеливым потребителем лечится ретраями и запасом времени в расписании — то есть ёмкостью и запасом. Шлюз лечится синхронным резервом и автоматическим переключением. Одинаковая цель по доступности для этих двух означала бы, что задачу мы вообще не поняли.

Что переносится из Google SRE, а что нет

Книга Site Reliability Engineering (O’Reilly, 2016) и SRE Workbook выложены бесплатно и действительно полезны. Но читать их как инструкцию — ошибка: половина практик держится на предпосылках, которых у вас нет.

Переносится почти без изменений: разделение SLI (что меряем), SLO (на что договорились) и SLA (что записано в договоре); бюджет ошибок как способ превратить спор «релиз или стабильность» в арифметику; постмортемы без поиска виноватого — разобранный выше механизм работает при любом масштабе; различение рутины (toil) и инженерной работы; принцип «сначала митигация, потом первопричина».

Не переносится:

  • Отдельная организация SRE с правом отказаться от сервиса. У Google SRE-команда может «вернуть пейджер» разработке, не держащей SLO. Это работает, когда SRE — департамент с собственным бюджетом и очередью желающих. В компании на 30 инженеров механизм превращается в межличностный конфликт.
  • Статистика. Рассуждения про четыре знака после запятой предполагают миллиарды событий в окне. При десятках тысяч запросов в час ваш SLO осмыслен на квартальном окне и с двумя знаками, не больше.
  • Собственный стек. Планировщик, сеть, балансировщики, хранилище у Google свои и с известными характеристиками отказов. Вы стоите на чужом облаке, чьи режимы отказа вам не видны и не управляемы, — это меняет и β-фактор, и то, какие барьеры вообще возможны.
  • Численность. Follow-the-sun с покрытием тремя площадками требует минимум трёх команд. У большинства читателей одна команда в одном часовом поясе, и это жёсткий предел на достижимый ночью MTTR.

Практически: в команде на 5–15 человек «внедрить SRE» означает не создать роль, а завести три артефакта — один SLO на главный пользовательский путь, короткий список алертов, где каждый ведёт к действию, и шаблон постмортема, который кто-то реально заполняет. Остальное добавляется потом и по потребности («SRE в небольшой команде»).

Типичные ошибки

  • Называть цель, не переведя её в минуты. Лечится одной строчкой в чате: «99,9 % — это 43 минуты в месяц, согласны?» Половина обсуждений заканчивается прямо здесь.
  • Ставить цель выше потолка зависимостей. Обещать 99,99 %, стоя на managed-базе с SLA 99,95 %, — значит обещать то, что не в вашей власти.
  • Считать резерв независимым. Две зоны, один конфиг, одна раскатка, один DNS — это не резерв, а копия с общей модой отказа. Проверяется постмортемами.
  • Забывать время переключения. Автофейловер за 90 секунд — это 90 секунд простоя каждый раз; на уровне 99,99 % четыре срабатывания выносят весь бюджет.
  • Улучшать MTBF там, где дешевле улучшить MTTR. Разложите последний инцидент на MTTD/MTTA/диагностику/митигацию: если починка меньше трети, ваш рычаг слева.
  • Мерить доступность по времени там, где нужен счёт по запросам. Особенно больно на сервисах с выраженным пиком.
  • Считать надёжность чисто технической задачей. Число людей в ротации и право остановить релиз — ограничения того же порядка, что архитектурные, только архитектуру можно поменять за спринт, а штат нет.

Мини-итог

  • Надёжность — вероятность дожить до конца интервала, без интервала бессмысленна. Доступность — доля времени или запросов, одно сравнимое число. Отказоустойчивость — свойство архитектуры, меряется покрытием отказов и временем переключения.
  • $A = MTBF/(MTBF + MTTR)$ даёт два рычага, и правый почти всегда дешевле: сократить время восстановления вдвое проще, чем вдвое реже ломаться.
  • Каждая девятка делит месячный бюджет на десять: 99 % — 7 ч 12 мин, 99,9 % — 43 мин, 99,99 % — 4 мин 19 с, 99,999 % — 26 с.
  • Пять девяток не нужны почти никому: бюджет короче одного фейловера, потолок ставят зависимости, канал пользователя всё равно хуже, а отличить пять девяток от четырёх статистически нельзя без миллиардов событий.
  • Обязательные зависимости перемножаются: пять по 99,9 % дают 99,5 %. Сокращение числа обязательных звеньев — более сильный рычаг, чем улучшение каждого.
  • Резервирование даёт заявленный эффект только при независимости отказов: при десятой доле общих отказов дубль улучшает доступность в 9 раз вместо 100, а третий узел не даёт почти ничего.
  • Доступность по времени и по запросам расходятся в разы на сервисах с пиками, и по запросам ближе к реальному ущербу.
  • Каскадный отказ — усиливающая петля: ретраи наращивают нагрузку ровно тогда, когда зависимость меньше всего способна её нести.
  • Целевая доступность — функция от стоимости минуты простоя, а не показатель зрелости. Дежурства, шумные алерты и штат ротации — измеримые слагаемые MTTR, а не «человеческий фактор рядом с инженерией».

Источники

  • Betsy Beyer et al. Site Reliability Engineering и The Site Reliability Workbook — бесплатно на sre.google/books. Читать критически, сверяясь со своим масштабом.
  • A. Avizienis, J.-C. Laprie, B. Randell, C. Landwehr. Basic Concepts and Taxonomy of Dependable and Secure Computing, IEEE TDSC, 2004 — doi:10.1109/TDSC.2004.2. Источник различения fault / error / failure.
  • Richard I. Cook. How Complex Systems Failhow.complexsystems.fail. Восемнадцать тезисов на четырёх страницах.
  • David D. Woods. Four concepts for resilience and the implications for the future of resilience engineering, RESS, 2015 — doi:10.1016/j.ress.2015.03.018.
  • Michael T. Nygard. Release It!, 2nd ed. — pragprog.com. Каталог режимов отказа и антипаттернов стабильности.
  • ISO/IEC 25010 — модель качества, где reliability разложена на зрелость, доступность, отказоустойчивость и восстанавливаемость: iso25000.com.
  • SLA облачных провайдеров: AWS, Google Cloud. Читать не проценты, а определения «недоступности» и порядок расчёта кредитов.
  • uptime.is — калькулятор бюджета простоя для произвольного окна.

Что дальше

Мы разобрали, чем надёжность отличается от доступности и во что обходится каждая девятка. Остался главный вопрос: что именно считать «работающим». Пока контракт не записан как конкретный измеримый показатель, все проценты выше — проценты неизвестно чего.

Дальше — SLI и SLO: как выбрать показатель, который что-то значит: как из потока событий получить число, отражающее опыт пользователя, почему «доступность API» — плохой SLI, а «доля оформлений заказа быстрее 800 мс» — хороший, и как выбрать окно и порог так, чтобы метрика не врала на низком трафике.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов