ESPNDon't look now, Michiganders, but you're back in the Bottom 10RTP Desporto12h30 Aviso a CR7: "Jesus não vira cara à luta"The Jerusalem PostMaine Senate candidate Troy Jackson opposes US giving Israel 'blank check'PunchTinubu mourns NADECO chieftain Ralph ObiohaVanguardIGP Disu mourns 25 NAF personnel, passengers killed in crashRapplerAnyare? Villars’ AllHome, AllDay confirm store closuresCollider‘Carrie’s Mike Flanagan Confirms Why He Made a Major Change From Stephen King’s BookSouth China Morning PostChinese consortium to build Latin America’s longest cable-stayed bridge in BrazilZDF heuteAktuelle Pressemitteilungen des ZDFVariety‘In the Shadows’ Producers to Self-Release British Boxing Biopic Following Collapse of U.K. Distributor True Brit Entertainment (EXCLUSIVE)UN NewsWHO releases first global guidelines on child obesity as cases surgeTagesschauFrüherer BND-Präsident Hanning muss in Untersuchungshaft
The Daily Newsstand · Free, Always
Wednesday, October 7, 2026

Математика катастрофы: от дифференциальных уравнений до очередей G/G/1 и формулы Кингмана

Translate

Знакомая картина: в 3 часа ночи падает база данных, Alertmanager засыпает каналами, через 5 минут сеть восстанавливается, но… система остаётся «мёртвой». CPU на воркерах 100 %, Goodput на нуле, а перезапуск подов только усугубляет ситуацию. Все указывает на то, что вы столкнулись с метастабильным отказом — самым коварным типом аварий в распределённых системах.

Меня зовут Артём Баранов, в «Базисе» я занимаюсь автоматизацией и развитием инфраструктуры. В этой статье я хочу рассказать о математике метастабильного отказа и через теорию массового обслуживания — конкретно через модель G/G/1 и формулу Кингмана — проложить мост от математических абстракций к реальным логам, метрикам и алертам. Далее, предложить практикующему SRE-специалисту набор методик для анализа, диагностики и предотвращения катастроф, возникающих в информационных системах по тем или иным причинам (ошибки конфигурирования, сбои «железа» и т. д.). Это позволит инженеру распознать sustaining-эффект по логам и метрикам и осознанно выбрать рычаг (backoff, retry budget, shedding, circuit breaker), а не просто объяснить постфактум, почему возникла предыдущая авария.

Несколько замечаний, прежде чем мы начнем

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

Второе, эта статья — инженерная компиляция нескольких моделей (fluid-ODE, Линдли, Max-Plus, аппроксимация Кингмана), а не единое строгое доказательство. Каждая модель отвечает на свой вопрос и имеет жёсткую зону применимости; ниже мы явно фиксируем эти границы.

Третье, рассмотреть весь материал, в полной мере освещающий проблематику метаотказов, в рамках одной статьи невозможно. Формальные методы (TLA^+, model checking, алгебры процессов) сознательно оставлены за рамками: здесь они упомянуты как карта местности, но не разворачиваются.

1. Складка, гистерезис и два аттрактора

Классический пример бистабильности и аттракторов в метастабильных отказах — это система обработки интернет-трафика (микросервис или база данных) с механизмом автоматических повторов (retries). При временном всплеске нагрузки система из здорового состояния переходит в состояние «залипания» с нулевой пропускной способностью, где и остаётся даже после исчезновения исходной причины сбоя. Именно этот механизм и называется метастабильностью: внешний триггер уже исчез, но внутренняя петля (сустейнер) продолжает питать аттрактор отказа.

С точки зрения теории катастроф это — складка (fold): множество стационарных решений f(x,\lambda_0)=0 при изменении \lambda_0 образует на плоскости (\lambda_0,\,x^*) характерную ветвящуюся кривую — в классической картине с двумя устойчивыми ветвями и одной неустойчивой между ними (оговорки о числе корней — ниже и в разд. 2; рис. 1 построен по двухпороговой модели из того же раздела).

Точки складки и гистерезис

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

Что определяет форму складки

Амплитуда складки по \lambda_0 растёт с R — интенсивностью повторов. При R\to 0 складка вырождается в монотонную кривую, и бистабильность исчезает: без повторов метастабильного отказа не бывает. Крутизна перехода задаётся k (жёсткостью клиентского таймаута); сдвиг по оси x — параметром x_{\mathrm{crit}} (порог срабатывания повторов). Глубина N-образности функции f=\lambda_{\mathrm{total}}-\mu определяется параметром \gamma в аппроксимации \mu(x): при \gamma=0 функция монотонна, и складки нет вовсе (см. разд. 2).

Форма кривой на рис. 1 — ветви равновесий двухпороговой модели из численного примера разд. 2 (не схематический кубический fold). Для строгой S-складки с двумя устойчивыми внутренними ветвями нужна ещё и f(\infty)<0; наша модель даёт бистабильность нуля и x_b (см. замечание о классической S-складке в разд. 2). В обоих случаях — с одним или двумя порогами — бистабильность возникает при наличии петли повторов: это качественный механизм, а не конкретная форма кривой.

Однопараметрический срез

В общем случае поверхность равновесий живёт в пространстве параметров (R,\,k,\,x_{\mathrm{crit}},\,\mu_0,\,\alpha,\,\beta,\,\gamma) и устроена сложнее S-образной складки: при изменении нескольких параметров форма поверхности может меняться качественно. Для практических выводов статьи достаточно однопараметрической картины: мы рассматриваем складку как срез многомерного пространства по одному параметру при фиксированных остальных. Именно поэтому защитные механизмы из разд. 8 формулируются как способы изменить саму форму складки (retry budget и backoff уменьшают ; shedding снижает эффективную и возвращает рабочую точку в область, где складки нет), а не как способ её «обойти».

Фазовое пространство и два аттрактора

Система обладает бистабильностью, то есть имеет два устойчивых состояния равновесия (аттрактора), разделённых барьером:

  • Аттрактор 1 (здоровое состояние): низкая задержка, высокая пропускная способность, очередь запросов пуста.

  • Аттрактор 2 (отказ / livelock): задержка максимальна, полезный трафик равен нулю, очереди забиты до отказа.

Механизм перехода (как работает метастабильность)

  1. Всплеск (триггер): кратковременный рост внешнего трафика или сбой одного узла заставляет систему замедлиться.

  2. Петля обратной связи (сустейнер): клиенты начинают массово повторять таймаутнутые запросы. Повторы добавляются к реальному трафику.

  3. Смещение барьера: дополнительный объём повторов переводит систему через разделительный порог (сепаратрису) в «бассейн притяжения» второго аттрактора.

  4. Залипание: внутренний объём работы (повторы) полностью утилизирует ресурсы процессора и сети. Даже если внешний всплеск исчез, лавина внутренних повторов продолжает питать аттрактор отказа. Система не может вернуться в норму сама.

Сравнение состояний системы

Параметр

Аттрактор 1 (норма)

Аттрактор 2 (метастабильный отказ)

Нагрузка

Внешняя полезная

Внутренние повторы

Задержка (Latency)

Низкая (миллисекунды)

Стремится к бесконечности (таймауты)

Пропускная способность

Максимальная

Около нуля

Реакция на снятие триггера

Работает штатно

Система остаётся в отказе («залипла»)

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

2. Математическая модель метастабильного отказа

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

Базовое уравнение динамики очереди

Обозначим через x(t) текущую длину очереди запросов (или уровень нагрузки) в момент времени t. Изменение очереди во времени описывается уравнением

\frac{\mathrm{d}x}{\mathrm{d}t} = \lambda_{\mathrm{total}}(x) - \mu(x), где:

  • \lambda_{\mathrm{total}}(x) — суммарный входящий поток запросов (функция генерации работы);

  • \mu(x) — реальная скорость обработки запросов системой (функция мощности обслуживания).

Границы применимости

Fluid-ODE предполагает x \gg 1: очередь рассматривается как непрерывная величина. При x \to 0 модель теряет смысл — там работает дискретная рекурсия Линдли (разд. 3).

Повторы: функция генерации работы

При росте очереди x растёт задержка, и клиенты начинают генерировать повторные запросы (retries). Суммарный поток состоит из базового полезного трафика и лавины повторов:

\lambda_{\mathrm{total}}(x) = \lambda_0 + R \cdot P_{\mathrm{timeout}}(x), \qquad P_{\mathrm{timeout}}(x) = \frac{1}{1 + e^{-k(x-x_{\mathrm{crit}})}}

Здесь R — предельная надбавка к базовому потоку от повторов (при полном насыщении сигмоиды). P_{\mathrm{timeout}}(x) — вероятность того, что запрос превысит таймаут: удобная модель поведения клиентов с фиксированным таймаутом. Вероятность превышения порога резко растёт, как только очередь переваливает через критический размер (возможны и другие формы — ступенчатая, кусочно-линейная — с тем же качественным эффектом). При x \gg x_{\mathrm{crit}} вероятность стремится к 1, поток повторов насыщается на уровне \lambda_0 + R.

Деградация: функция обслуживания

В идеальной системе скорость обработки постоянна. Но при перегрузке (рост ) ресурсы процессора тратятся на накладные расходы: контекст-свитчинг, сборку мусора, lock contention, очистку буферов. Производительность падает. Мы используем феноменологическую аппроксимацию функции обслуживания \mu (x):

\mu(x) = \mu_0 \cdot \frac{1 + \gamma x}{1 + \alpha x^{\beta}}, \qquad \beta > 1, \gamma \ge 0

Это не универсальный физический закон планировщика ОС, а удобная кривая, которая ловит конкуренцию параллелизма и thrashing. Параметры: \mu_0 — предельная скорость обслуживания при ; \alpha — масштаб нелинейности; \beta > 1 — степень ускорения деградации с ростом .

Параметр \gamma > 0 играет ключевую роль: он даёт слабый начальный рост (параллелизм), а дальше — падение. Функция f = \lambda_{\mathrm{total}} - \mu при этом становится немонотонной.

Важное уточнение о числе корней

С одной сигмоидой P(x) и одним горбом функция f имеет не более одного экстремума: производная f' = R\,k\,P(1-P) - \mu' представляет собой разность колокола и функции с единственной сменой знака, поэтому f' обнуляется не более одного раза. Это даёт не более двух корней f = 0 — но не классическую S-складку с тремя.

Полная S-складка с тремя корнями (два устойчивых равновесия и одно неустойчивое между ними) возникает при двух порогах срабатывания повторов: например, две группы клиентов с разными \tau, дающие две сигмоиды в . Схематически ветви равновесий такой модели показаны на рис. 1; явный численный пример — ниже. В обоих случаях — с одним или двумя порогами — сама бистабильность возникает из-за петли повторов, а не из-за конкретной формы кривой: это ключевой качественный механизм.

Численный пример: три корня при двух сигмоидах

Утверждение о трёх корнях при двух порогах срабатывания повторов неочевидно; приведём явный пример. Возьмём \mu_0=1,\quad \gamma=1,\quad \alpha=0{,}1,\quad \beta=2, \lambda_0=0{,}5,\quad     R_1=1{,}5,\ k_1=10,\ x_{\mathrm{crit},1}=0{,}5,\quad     R_2=2{,}5,\ k_2=10,\ x_{\mathrm{crit},2}=3{,}0.

Тогда \lambda_{\mathrm{total}}(x)     = 0{,}5     + \frac{1{,}5}{1+e^{-10(x-0{,}5)}}     + \frac{2{,}5}{1+e^{-10(x-3{,}0)}},     \qquad     \mu(x)=\frac{1+x}{1+0{,}1\,x^{2}}.

Значения f(x)=\lambda_{\mathrm{total}}(x)-\mu(x):

f(x)

0{,}0

0{,}510

1{,}000

-0{,}490

0{,}5

1{,}250

1{,}463

-0{,}213

0{,}6

1{,}597

1{,}544

+0{,}053

1{,}0

1{,}990

1{,}818

+0{,}172

1{,}4

2{,}000

2{,}007

-0{,}007

2{,}0

2{,}143

-0{,}143

2{,}5

2{,}017

2{,}154

-0{,}137

2{,}75

2{,}190

2{,}135

+0{,}055

3{,}0

3{,}250

2{,}105

+1{,}145

Последовательность знаков : -,\,+,\,-,\,+, то есть три смены знака и, по теореме о промежуточном значении, три корня f(x)=0 на интервалах (0{,}5;\,0{,}6), (1{,}0;\,1{,}4), (2{,}5;\,2{,}75); численно , , .

Об устойчивости трёх корней

Знак в окрестности корня определяет устойчивость равновесия \mathrm{d}x/\mathrm{d}t=f(x): — устойчивое, f'>0 — неустойчивое. В нашем примере

f’(x_a)>0,\qquad f’(x_b)<0,\qquad f’(x_c)>0,

то есть картина «неустойчивое — устойчивое — неустойчивое». Это надо прочесть так. Устойчивое равновесие одно — : это и есть «сбойный» аттрактор (аналог x_2 из разд. 1). Роль здорового аттрактора x_1 играет граница : при f<0, и очередь дренируется к нулю. Точка x_a — сепаратриса x_{\mathrm{sep}}: если триггер вытолкнул систему правее , она сваливается в . Третий корень x_c — дополнительное неустойчивое равновесие: выше него очередь растёт без ограничения.

Замечание о «классической» S-складке

В нашем примере картина не полностью совпадает с классической кубической складкой, у которой два устойчивых внутренних равновесия разделены неустойчивым: для этого нужно , а в нашей модели f(\infty)=\lambda_0+R_1+R_2>0. Качественный механизм — наличие порога (сепаратрисы) и залипание в устойчивом равновесии — сохраняется; в терминах аттракторов система по-прежнему бистабильна (нуль и ), только первый аттрактор лежит на границе области, а не во внутренней точке. Буквальная S-складка с двумя устойчивыми внутренними равновесиями требует дополнительной нелинейности — например, насыщения повторов при x\to\infty или полки в ; это выходит за рамки статьи.

Инженерное примечание

В реальных ИТ-системах классическая бистабильность с двумя устойчивыми внутренними состояниями возникает даже при одной сигмоиде , если:

  • ёмкость очереди жёстко ограничена сверху (Q_{\max}): за этой границей поток обрезается, и приобретает разрыв;

  • функция деградации падает до нуля быстрее, чем насыщаются повторы: тогда у появляется второй горб и, соответственно, два экстремума.

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

Математически при имеем \mu'(x)<0 для всех x>0. Поскольку для сигмоиды \lambda_{\mathrm{total}}'(x)=R\,k\,P(1-P)\ge 0 всегда (сигмоида монотонно возрастает), получаем f'(x)=\lambda_{\mathrm{total}}'(x)-\mu'(x)>0 всюду: даже на плато, где \lambda_{\mathrm{total}}'\to 0, вклад -\mu'>0 сохраняет строгую монотонность . Следовательно, больше одного корня f=0 быть не может: бистабильности в этой fluid-модели при не существует. Немонотонность из-за конкуренции «параллелизм \leftrightarrow деградация» — необходимое условие складки; для полной S-складки с тремя корнями нужны ещё два порога повторов (см. выше).

Аттракторы: точки равновесия

Стационарные состояния системы — корни уравнения \lambda_{\mathrm{total}}(x) = \mu(x) (точки пересечения кривой притока и кривой оттока). Число корней зависит от формы кривых: в однопороговой модели (одна сигмоида ) равновесие, как правило, единственное; для трёх равновесий нужны два порога срабатывания повторов (см. обсуждение выше).

Там, где три корня существуют, система качественно бистабильна: неустойчивый корень (сепаратриса) разделяет бассейны притяжения здорового и сбойного режимов. Ниже — каноническая карта / / (та же, что в разд. 1); как она ложится на численный пример с граничным аттрактором — сразу после списка.

В численном примере выше роль играет граница (аттрактор на краю области), роль — ; сепаратриса совпадает с . Классическая картина с внутренним x_1>0 требует дополнительной нелинейности (см. замечание о классической S-складке выше).

Сепаратриса — не то же самое, что порог таймаута : первая разделяет бассейны притяжения складки, вторая — порог срабатывания повторов.

Load shedding: математика выхода из аттрактора

Чтобы вернуть систему в здоровое состояние , нужно временно изменить топологию фазового пространства — уничтожить аттрактор . Это делается через сброс нагрузки (load shedding). Администраторы или алгоритмы принудительно режут входящий трафик до значения \lambda_{\mathrm{drop}}, при котором \lambda_{\mathrm{total}}(x)<\mu(x) для всех от текущего значения очереди до . Тогда \mathrm{d}x/\mathrm{d}t < 0 на всём пути к здоровому равновесию: очередь начинает разжиматься, и система «стекает» обратно. Как только очередь упала ниже , жёсткое ограничение трафика можно снимать — система вернётся в здоровый аттрактор .

Почему система остаётся в сбойном состоянии после снятия триггера

Система остаётся в сбойном состоянии из-за внутренней регенеративной петли обратной связи. Когда внешний триггер (всплеск ) исчезает, накопленная внутри системы «энергия» (очередь запросов) продолжает сама себя воспроизводить. В уравнениях этот эффект удобно разложить на три аспекта одного механизма.

1. Повторы от «прошлого» трафика (память системы)

Даже если новый внешний трафик вернулся к норме, в очереди уже находится огромное количество запросов. Клиенты, чьи запросы встали в очередь во время всплеска, не дождались ответа и получили таймаут. Вероятность для них близка к 1, потому что очередь физически превышает . Слагаемое R\cdot P_{\mathrm{timeout}}(x) выдаёт свой максимальный вклад: потока самого по себе достаточно, чтобы удерживать точку пересечения графиков в районе .

2. Падение полезной мощности (феномен деградации)

Из-за гигантской очереди реальная скорость упала до минимума. Сервер тратит почти все ресурсы CPU не на полезную работу, а на обслуживание самой перегрузки: прерывания от сети, переключение контекста между тысячами потоков, парсинг заголовков повторных запросов, которые он тут же откидывает по таймауту. Замкнутый круг: система обрабатывает слишком медленно, потому что очередь большая — очередь остаётся большой, потому что система обрабатывает слишком медленно.

3. «Бассейн притяжения» аттрактора

Исчезновение триггера лишь слегка смещает положение точки , но не уничтожает сам аттрактор. Текущая координата уже находится глубоко внутри бассейна притяжения сбойного аттрактора — правее сепаратрисы . Для возврата нужно преодолеть энергетический барьер, но у системы нет для этого внутренних ресурсов.

Три аспекта не независимы: память питает деградацию, деградация закрепляет бассейн притяжения. Но защитные меры адресуются каждому из них по отдельности.

Что это даёт на практике

Три «кита» этого раздела — память системы, падение мощности и «бассейн притяжения» — это три разных повода для аварии, и лечатся они по-разному. Память разрывается клиентскими рычагами (backoff, retry budget); деградация — серверными (shedding: снять нагрузку, пока не восстановится); бассейн притяжения — только жёстким сбросом ниже сепаратрисы . Если на инциденте вы видите только один из трёх признаков, этого недостаточно: sustaining-эффект держится одновременно на всех трёх, и защита должна закрывать каждый.

3. Переход к рекурсии Линдли

Если дифференциальное уравнение описывало систему непрерывно и макроскопически (fluid approximation), то рекурсия Линдли (Lindley’s recurrence) позволяет заглянуть на уровень дискретных запросов и связать время ожидания конкретного клиента с поведением системы в условиях очередей типа .

Что такое рекурсия Линдли в базе

В классической теории очередей время ожидания W_n для n-го запроса (до начала его обработки) вычисляется исходя из времени ожидания предыдущего запроса:

W_n = \max(0, W_{n-1} + S_{n-1} - T_n),

где S_{n-1} — время обслуживания предыдущего запроса, T_n — интервал между приходом (n-1)-го и -го запросов.

Оператор \max(0,\ldots) отражает нелинейность: если сервер простаивал (W_{n-1} + S_{n-1} < T_n), новый запрос обрабатывается мгновенно, а время ожидания равно нулю.

Как Линдли объясняет метастабильный отказ

В контексте метастабильности и повторов параметры S и T перестают быть независимыми случайными величинами. Они становятся эндогенными функциями состояния очереди W_{n-1}: зависят от бэклога и больше не являются независимыми внешними величинами.

Эффект повторов: изменение интервала прихода

Когда система здорова, определяется только внешним трафиком. Но если растёт и превышает клиентский таймаут , клиент шлёт повтор. Интервал между входящими пакетами на сервере резко сокращается: T_n \to T_{\min}. Плотность входящего потока растёт, среднее \mathbb{E}[T] уменьшается.

Эффект деградации: изменение времени обслуживания

Из-за огромного количества параллельных запросов и накладных расходов (обработка повторов, которые в итоге будут отброшены) эффективное время полезного обслуживания увеличивается. Каждому запросу достаётся меньше квантов CPU.

Фазовый переход в терминах рекурсии

Математическое условие стабильности очереди по Линдли: среднее время обслуживания должно быть меньше среднего интервала между запросами\mathbb{E}[S] < \mathbb{E}[T]
(плюс (S-T)_+=\max(0,\,S-T), где (S-T)_+=\max(0,\,S-T)).

Классическое условие \mathbb{E}[S]<\mathbb{E}[T] справедливо для изолированных систем с i.i.d. интервалами S_n и ; в условиях эндогенной зависимости (когда и становятся функциями бэклога , см. выше) оно применяется локально как квазистационарное приближение, а не как строгий критерий стационарности марковской цепи.

В метастабильной системе из-за нелинейных петель обратной связи возникают две зоны стабильности:

Почему система не возвращается сама?

Даже если внешний источник уменьшает генерацию исходных задач (мы убрали триггер), накопленное гигантское значение в рекурсии определяет поведение системы на много шагов вперёд. Пока бэклог настолько велик, что t_{k-1}+B_{k-1}-a_k>\tau для всех новых прибытий (обозначения t_k, B_k, a_k введены в разд. 4), каждый новый запрос таймаутит: сервер физически не успевает начать его обработку до a_k+\tau, клиенты генерируют , и условие \mathbb{E}[S] > \mathbb{E}[T] поддерживается. Система «заперта» внутри рекурсивного роста.

Что это даёт на практике

Рекурсия Линдли превращает качественную картину «система залипла» в два числа, которые считаются из логов до аварии: среднее время обслуживания \mathbb{E}[S] и минимальный интервал прихода T_{\min}, который возникает при шторме повторов. Пока \mathbb{E}[S] < \mathbb{E}[T] с запасом — система в здоровом аттракторе. Как только уходит ниже , рекурсия превращается в линейный рост, и это и есть переход в сбойный режим. Оба числа измеряются на живом трафике; методика — ниже.

Как измерить по логам среднее время обслуживания и минимальный интервал прихода

Обе величины берутся с конкретного ingress-шлюза приложения (не усреднённо по кластеру) за скользящее окно 5-15 мин.

  1. Время обслуживания . Для каждого запроса — время от начала его обработки на сервере до отправки ответа, без времени ожидания в очереди. Источник: отдельная метрика обработки (например, http_request_duration без queue wait) либо разность timestamp в access-логе. Если метрика включает и wait, и service — разделить, иначе будет расти автоматически, и причина не отличится от следствия.

  2. Интервалы прихода . Для каждой пары соседних принятых запросов — разность их timestamp прихода.

  3. Три окна. Разбить анализируемый период на «до инцидента», «во время», «после снятия триггера».

  4. Три числа на окно. (или медиана , если хвосты тяжёлые), и 5-й перцентиль — последний и есть (компромисс: 1-й перцентиль слишком шумный в коротком окне, 10-й теряет хвост).

  5. Тренды. Смотреть на скользящее окно, а не на средние за час: инцидент живёт минуты.

Что искать в числах

\mathbb{E}[S]/\mathbb{E}[T] \ll 1 — здоровый режим; \mathbb{E}[S]/\mathbb{E}[T] \to 1 (на практике выше 0{,}85) — приближение к сепаратрисе; — сбойный аттрактор. Признак активного sustaining-эффекта — T_{\min} < \mathbb{E}[S]: даже самые «быстрые» приходы не успевают за обслуживанием, и каждый повтор продлевает петлю.

В примере разд. 5: \mathbb{E}[S] = 100 мс в норме и  мс в сбое; \mathbb{E}[T] = 120 мс; при \Delta_{\mathrm{retry}} = 50 мс T_{\min} \approx 50 мс. В норме \rho \approx 0{,}83; в сбое и — оба индикатора срабатывают.

4. Модифицированная рекурсия: таймаут, drop и повторы

В классической модели заявки живут в очереди, пока не будут обработаны. Чтобы учесть поведение клиентов и ограничения ИТ-систем, введём отсечение по таймауту и генерацию повторов. Пусть:

  • — жёсткий таймаут ожидания в очереди: при t_k - a_k > \tau клиент закрывает соединение;

  • p — вероятность повторного запроса после таймаута;

  • \Delta_{\mathrm{retry}} — задержка перед повторной отправкой;

  • c_{\mathrm{drop}} — цена выброса: время CPU на приём и отбрасывание просроченной заявки.

Drop-on-timeout

Под временем ожидания здесь понимается время до начала обработки (t_k - a_k), а не до её завершения: таймаут отсекает заявку ещё в очереди. Мы моделируем сервер, который определяет просрочку при извлечении из очереди. — это стоимость обработки просроченной заявки: чтение заголовков, запись в лог, инкремент метрик, отдача ответа, освобождение сокета. Для сервиса с логированием и метриками это порядка 10-20 мс, а не 1-3 мс: даже минимальный лог и одна запись в метрику съедают больше миллисекунды. В симуляции мы используем 20 мс как реалистичную оценку для сервиса с активным логированием. Если заявка ждала начала обработки дольше , клиент уже ушёл, а сервер всё равно платит c_{\mathrm{drop}}.

B_k =  \begin{cases}  d(t_k),  & t_k - a_k \le \tau \\        c_{\mathrm{drop}}, & t_k - a_k > \tau    \end{cases}

Здесь d(t_k) \in \{d,\, d_{\mathrm{bad}}\} — длительность полезной работы, зависящая от состояния зависимости в момент начала. Рекурсия на момент начала: \begin{equation}     t_k = \max(a_k,\, t_{k-1} + B_{k-1}). \end{equation}

Ключевой момент

Если c_{\mathrm{drop}} = 0 — модель восстанавливается сама: сервер не тратит CPU на мёртвые заявки, очередь дренируется. Именно c_{\mathrm{drop}} > 0 замыкает петлю: сервер платит за каждую просроченную заявку, и лавина повторов поддерживает себя. Сама по себе очистка по таймауту спасает систему при отсутствии повторных запросов; как только появляется шторм повторов, одной очистки уже недостаточно — петля снова замыкается.

Связь с fluid-моделью

Инженерный смысл c_{\mathrm{drop}}>0 в микро-модели: это стоимость бесполезной работы, которая в fluid-модели (разд. 2) соответствует снижению эффективной производительности — сервер тратит ресурсы на просроченные заявки, а не на полезные. Дискретная петля в Линдли и непрерывная деградация в ODE — две стороны одного механизма.

Шторм повторов (sustaining effect)

Клиентский таймаут срабатывает по часам клиента в момент a_k + \tau, независимо от того, когда сервер извлечёт просроченную заявку. С вероятностью повтор планируется на a_k + \tau + \Delta_{\mathrm{retry}}. Поток прибытий перестаёт быть экзогенным: интервал до следующей заявки зависит от состояния очереди. Именно эта обратная связь превращает оператор \max в ловушку метастабильности.

5. Симуляция

Параметры штатного режима

Здесь d обозначает время обслуживания — то же, что в разд. 4; индексы и d_{\mathrm{bad}} — норма и режим сбоя.

Параметр

Значение

/

100/200 мс

120 мс ()

150 мс

20 мс

(вероятность повтора)

0,9

50 мс

40 заявок

Число прогонов

100

Порог по

Стоит отметить, что c_{\mathrm{drop}} не просто «немного больше нуля» замыкает петлю. При \rho=0{,}83, p=0{,}9, \Delta_{\mathrm{retry}}=50 мс устойчивая петля требует c_{\mathrm{drop}}\gtrsim 10 мс. Это прямо согласуется с оценкой «единицы–десятки мс»: если бы c_{\mathrm{drop}}=2 мс, сервер успевал бы дренировать буфер, и метастабильный отказ не наступал бы. Значение 20 мс в таблице — не «подгонка ради красивого графика», а реалистичный порядок величины выше этого порога. Порог проверен численно: скрипт lindley_sweep_cdrop.py прогоняет baseline-сценарий по 100 прогонов при от 1 до 20 мс.

Результат свипа резкий: при c_{\mathrm{drop}} \le 10 мс система восстанавливается во всех 100 прогонах; при c_{\mathrm{drop}} = 12 мс восстанавливается 79/100; при c_{\mathrm{drop}} = 12 мс восстановление практически отсутствует. Переход сосредоточен в узкой полосе 10-15 мс, что подтверждает бистабильный характер отказа: граница между аттракторами резкая, а не размытая. Значение 20 мс выбрано с запасом над серединой перехода (\approx 12 мс), чтобы результат не зависел от джиттера.

Эта оценка согласуется с грубым аналитическим порогом. При \lambda_0 = 1/\mathbb{E}[T] \approx 8{,}3 rps и в полном коллапсе (все заявки таймаутят) суммарный поток на входе равен \lambda_{\mathrm{total}} = \lambda_0/(1-p) = \lambda_0\bigl(1 + p/(1-p)\bigr) \approx 83{,}3 rps: базовый поток и лавина повторов оба платят . Сервер обрабатывает просроченные заявки со скоростью \approx 1/c_{\mathrm{drop}} заявок в секунду, поэтому дренирование возможно лишь при c_{\mathrm{drop}} \lesssim 1/83{,}3 \approx 12 мс (при c_{\mathrm{drop}} \gtrsim 12 мс система коллапсирует). Это совпадает с эмпирикой свипа: именно при c_{\mathrm{drop}}=12 мс восстанавливается 79/100 прогонов — на границе аналитического порога. Аналитическая оценка — нижняя граница: она не учитывает джиттер задержек повтора и переходные процессы, поэтому эмпирическая полоса перехода шире и сдвинута в сторону меньших .

Сценарий

Окно сбоя — wall-clock 36\ldots48 с: функция обслуживания переключается на d_{\mathrm{bad}} = 200 мс. Пока очередь ещё пуста, первая заявка действительно обслуживается за (таймаут отсекает ожидание в очереди, t_k-a_k, а не полное время ответа). Но d_{\mathrm{bad}} > \mathbb{E}[T]=120 мс даёт \rho>1: бэклог растёт, и уже через доли секунды ожидание превышает \tau=150 мс. С этого момента почти все заявки уходят в ветку drop-on-timeout и платят B_k=c_{\mathrm{drop}}=20 мс вместо полезной работы, а клиенты взрывают поток повторами. То есть — триггер набора бэклога; фаза сбоя на графиках поддерживается уже связкой c_{\mathrm{drop}}+повторы. После 48 с корень проблемы исчезает ( снова 100 мс), но клиенты агрессивно шлют повторы. Сценарий baseline в этой статье — drop-on-timeout с и фиксированной задержкой повтора , без backoff, retry budget и shedding (см. разд. 8). Ось X на рис. 3 — время в секундах (0\ldots120).

Почему drop-on-timeout остаётся в baseline

Drop-on-timeout сам по себе — простейший механизм защиты от перегрузки: сервер не тратит CPU на просроченные заявки, и очередь дренируется. Мы оставляем его в baseline сознательно, чтобы изолировать эффект клиентской политики повторов: при модель восстанавливается сама (см. разд. 4), и роль повторов была бы не видна. Такой выбор отделяет клиентский рычаг (backoff/jitter/budget) от серверного (shedding) и делает сравнение защит в разд. 8 честным.

Результаты

На основе моделирования получается классическая картина метастабильного отказа:

Рисунок 3. Baseline: drop-on-timeout и фиксированная задержка повтора без backoff/budget/shedding. После устранения сбоя (синяя пунктирная линия,  с) очередь не рассасывается — sustaining effect шторма запросов. Ось ординат обрезана на  мс (та же шкала, что на рис. 5 и 6); в baseline очередь уходит за эту границу и не возвращается. Сравнивать следует поведение после снятия триггера (вертикаль ) и сводку в табл. 1.
Рисунок 3. Baseline: drop-on-timeout и фиксированная задержка повтора без backoff/budget/shedding. После устранения сбоя (синяя пунктирная линия, \approx 48 с) очередь не рассасывается — sustaining effect шторма запросов. Ось ординат обрезана на  мс (та же шкала, что на рис. 5 и 6); в baseline очередь уходит за эту границу и не возвращается. Сравнивать следует поведение после снятия триггера (вертикаль T_{\mathrm{rec}}) и сводку в табл. 1.

Итог фазы без защиты. Внешний триггер исчез, сервер снова готов работать за 100 мс, но перегруженная очередь сама генерирует избыточный входящий поток. Нужны либо клиентские (задержка / джиттер), либо серверные (сброс нагрузки / предохранитель) разрывы контура.

Воспроизводимость

Скрипты симуляции (генератор Линдли с drop-on-timeout, свип по , ансамбли по 100 прогонов для baseline, backoff+jitter+budget и shedding) и исходные данные прогонов доступны в репозитории https://github.com/redbeardster/metastable.

Фрагмент: оценка виртуального ожидания (FIFO + drop).

def _service_at(start):
    return D_BAD if FAULT_START <= start <= FAULT_END else D_OK

def offer_wait(now, server_free, buffer, tau):
    t_free = server_free
    for arr, _rcnt in buffer:
        start = max(t_free, arr)
        t_free = start + (C_DROP if start - arr > tau
                          else _service_at(start))
    return max(0.0, t_free - now)

6. Тропическая математика: без мистики

Раздел можно пропустить при первом чтении: практический вывод сведён к процедуре в конце этого раздела («оставить не больше p_{\mathrm{crit}} трафика на самом жёстком цикле»). Аппарат ({\max},+) приведён здесь, потому что он даёт язык для сетевого анализа петель повторов и объясняет, почему одна лишь маскировка связей не спасает.

Рекурсия Линдли содержит оператор . В тропической алгебре (\max,+) (idempotent / tropical semirings) он становится линейным: нелинейная рекурсия превращается в линейную систему, к которой применим аппарат собственных значений и собственных векторов. Этот аппарат позволяет описывать метастабильные отказы через спектр матрицы переходов: знак тропического собственного значения задаёт, растёт очередь или релаксирует к нулю.

Переход к тропическому полукольцу

В тропическом полукольце операции a\oplus b=\max(a,b) и a\otimes b=a+b превращают рекурсию Линдли в линейное уравнение

W_n = (A_{n-1}\otimes W_{n-1})\oplus e,

где A_{n-1}=S_{n-1}-T_n, e=0 — тропическая единица. Нейтральный элемент по \oplus — -\infty (в терминах топологии сети это отсутствие ребра между узлами); для одного сервера критерий роста сводится к знаку S-T, содержательный спектр появляется только для сети узлов.

Обозначения

Ниже \lambda — тропическое собственное значение (maximum cycle mean), а не интенсивность входящего потока из ODE/Kingman. Коэффициент загрузки сервера по-прежнему \rho. Тропический спектральный радиус матрицы обозначаем \rho_{\max}; в одномерном случае \rho_{\max}=\lambda.

Сеть сервисов: где Max-Plus уместен

Аппарат матриц A и осмыслен для сети дискретно-событийных узлов (тандем, синхронизации) в детерминированном худшем случае. Пример 2\times 2 ниже — именно сеть, а не замена модели . Он не доказывает стохастическую метастабильность; он оценивает, когда детерминированный цикл перегрузок растёт (\rho_{\max}>0) и какую долю трафика нужно срезать, чтобы \rho_{\max}<0.

Представим два связанных микросервиса в состоянии метастабильного отказа. Пусть узел 1 имеет время обслуживания S_1=5{,}5 мс при собственном интервале прихода T_1=1{,}5 мс, а узел 2 — S_2=6{,}5 мс и T_2=0{,}5 мс. В обоих случаях S_i > T_i: каждый узел сам себя перегружает.

Элемент A_{ij} = S_i - T_j — чистое превышение обслуживания над приходом на ребре j \to i: положительное значение означает, что бэклог узла i растёт, когда триггер приходит от узла j. Диагональные элементы — само-петли узлов, внедиагональные — перекрёстные потоки: узел 1 вызывает узел 2 и наоборот. Подставляя числа, получаем матрицу тропических переходов

A_{\mathrm{fault}} = \begin{pmatrix} S_1 - T_1 & S_1 - T_2 \\ S_2 - T_1 & S_2 - T_2 \end{pmatrix} = \begin{pmatrix} 4 & 5 \\ 5 & 6 \end{pmatrix}

Тождество A_{11}+A_{22}=A_{12}+A_{21}=10 — это просто суммарное S_1+S_2 - T_1 - T_2; оно выполняется автоматически для любой матрицы вида A_{ij}=S_i - T_j. Циклы:

  • цикл 1–1: вес 4, средний вес 4;

  • цикл 2–2: вес 6, средний вес 6;

  • цикл 1–2–1 (взаимное влияние): вес 5+5=10, средний вес 5.

Максимум \lambda = 6 > 0 достигается на цикле 2–2: это доминирующая петля обратной связи, по которой узел 2 сам себя подпитывает. При каждой итерации (один полный круг по доминирующему циклу — «событие» в терминологии Max-Plus) вектор очередей в среднем прибавляет 6 единиц времени, то есть система находится в аттракторе отказа и сама себя перегружает. Именно эту петлю — или её эквивалент в реальной системе — и нужно разрывать: защиты из разд. 8 снижают долю сохраняемого трафика настолько, чтобы максимум по всем циклам стал отрицательным.

Физический смысл

Тропическое собственное значение — это доминирующая петля обратной связи: цикл с наибольшим средним весом S_\Sigma-T_\Sigma на ребро. Знак задаёт асимптотику очереди: \lambda > 0 — бэклог растёт линейно со скоростью за событие, \lambda < 0 — дренируется, \lambda = 0 — критический режим. Единицы — те же, что у и (в примере — мс).

Тот же цикл длины  с S_1+S_2=12 мс и T_1+T_2=2 мс даёт mean cycle (12-2)/2=5 — один из порогов в разд. 6.3; финальный берётся как минимум по всем циклам.

Критическая доля сброса

Критическую долю сброса p_{\mathrm{crit}} не стоит путать с загрузкой и с тропическим : здесь — доля сохраняемого трафика, при которой \rho_{\max}=0 (ниже — сброс 1-p).

Пусть — доля сохраняемого трафика. Когда мы отбрасываем часть запросов, средний интервал между теми, которые сервер всё-таки берёт в обработку, растёт как T(p)=T_{\mathrm{fault}}/p. Для цикла C длины |C| с суммарным service S_\Sigma и суммарным interarrival T_\Sigma тропический радиус равен

\rho_C(p) = \frac{S_\Sigma - \bigl(T_\Sigma / p\bigr)}{|C|},

а условие \rho_C(p) < 0 равносильно p < T_\Sigma/S_\Sigma: каждому циклу соответствует свой порог дренирования.

Поскольку — это максимум по всем циклам (разд. 6), доля сохраняемого трафика должна удовлетворять самому жёсткому из этих порогов:

p_{\mathrm{crit}} = \min_C \frac{T_\Sigma(C)}{S_\Sigma(C)}.

Доминирующий цикл (в примере — цикл 2–2) задаёт нижнюю границу.

Пример

Для того же сценария, что в разделе выше:

  • цикл 1–1: порог T_1/S_1 = 1{,}5/5{,}5 \approx 27{,}3\%;

  • цикл 2–2: порог T_2/S_2 = 0{,}5/6{,}5 \approx 7{,}7\%;

  • цикл 1–2–1: порог (T_1+T_2)/(S_1+S_2) = 2/12 \approx 16{,}7\%.

Минимум — p_{\mathrm{crit}}\approx 7{,}7\%, задаваемый доминирующей петлёй 2–2. Это верхняя граница доли сохраняемого трафика: сбросить придётся \approx 92{,}3\%, то есть агрессивнее, чем требуется для перекрёстного цикла в одиночку.

Важно: если бы мы ориентировались только на цикл 1–2–1 (порог 16{,}7\%), при p=10\% доминирующая петля 2–2 оставалась бы положительной, и не стал бы отрицательным. Оценка должна идти по самому жёсткому циклу.

Физический смысл и подстановка чисел

Если p > 7{,}7\% (пропускаем слишком много трафика), — очереди продолжат расти, сброс неэффективен. Если p < 7{,}7\% — все циклы, включая доминирующий 2–2, становятся отрицательными: уравнение Линдли превращается в сжимающее отображение, и вектор очередей «стекает» к нулю.

Подставим числа в рекурсию Линдли на доминирующем цикле 2–2. При  мс и  мс эффективный интервал между принятыми заявками равен T_2/p, а дрейф одного шага:

A(p) = S_2 - \frac{T_2}{p}.

Три режима:

, мс

A(p), мс

Режим

10\%

5{,}0

+1{,}5

рост бэклога

7{,}7\%

6{,}5

\approx 0

критический режим

5\%

10{,}0

-3{,}5

дренирование

При p=5\% рекурсия принимает вид \begin{equation}     W_n = \max\bigl(0,\; W_{n-1} - 3{,}5~\text{мс}\bigr), \end{equation}то есть каждое событие срезает с бэклога фиксированные 3{,}5 мс. Из насыщенного состояния W_0=800 мс очередь дренируется за \lceil 800/3{,}5 \rceil = 229 событий. При знак противоположен, и та же рекурсия даёт линейный рост W_n = W_{n-1} + 1{,}5 мс.

Как только очереди опустели, можно безопасно вернуть p=1.

Реальная доля сброса обычно выше этой нижней границы: джиттер (разброс интервалов прихода/обслуживания) и тяжёлые хвосты требуют запаса, а не точного равенства p=p_{\mathrm{crit}}.

Важно: разрыв одного ребра не спасает (элемент в матрице — отсутствие дуги). После удаления дуги матрица принимает вид

A’ = \begin{pmatrix} 4 & -\infty \\ 5 & 6 \end{pmatrix}, \quad \lambda = \max(4,6) = 6 > 0

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

Инженерное резюме

Тропическая алгебра даёт инженеру три вещи, которых нет ни в fluid-ODE (разд. 2), ни в дискретно-событийной симуляции (разд. 5). Во-первых, узкое место в сети сервисов — это цикл обратной связи, а не отдельный узел: два микросервиса, вызывающих друг друга с повторами, образуют петлю, которая живёт своей жизнью и не видна при анализе по одному узлу. Во-вторых, знак доминирующего собственного значения (максимум по средним весам циклов) за одну операцию отвечает на вопрос «система растёт или дренируется». Отличие от классического спектра: там \|x(n)\|\sim\rho^n (экспоненциальный рост), здесь x(n)\sim\lambda n (линейный) — для очередей естественна именно линейная метрика. В-третьих, критическая доля сброса вычисляется по логам, без запуска симуляции.

Практическая процедура

  1. Построить граф вызовов между сервисами с рёбрами A_{ij}=S_i-T_j: S_i — среднее время обслуживания узла , T_j — средний интервал между принятыми заявками от узла .

  2. Найти простые циклы (на практике — длины 2-3; длиннее уже редкость).

  3. Для каждого цикла вычислить p_C = T_\Sigma(C)/S_\Sigma(C).

  4. p_{\mathrm{crit}} = \min_C p_C; сбросить не менее 1-p_{\mathrm{crit}} трафика.

  5. Вернуть нагрузку только после того, как очереди опустеют.

Главный вывод на инженерном языке: если узел сам себя перегружает через собственную петлю повторов, он задаёт порог сброса жёстче, чем перекрёстные циклы, — сбрасывать надо по самому жёсткому циклу, а не «по среднему по сети». В нашем примере это , а не \approx 83{,}3\%, как подсказала бы наивная оценка по перекрёстному циклу 1–2–1. Именно поэтому adaptive concurrency control в проде держит нагрузку существенно ниже половины входного трафика: порог из этого примера задаёт нижнюю границу, а с запасом на джиттер и тяжёлые хвосты практический ориентир оказывается в районе 10-20%.

Оценка — детерминированная, worst-case, и даёт нижнюю границу доли сброса; реальная доля выше из-за джиттера и тяжёлых хвостов. Стохастический аналог — формула Кингмана: она даёт среднюю картину в открытой системе без обратной связи. Обе модели дополняют друг друга: тропический анализ отвечает на вопрос «что и на сколько рвать», Кингман — «где мы стоим по загрузке прямо сейчас».

7. Формула Кингмана: количественный компас

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

Но всё это — качественные или полуколичественные модели. Они показывают механизм, но не дают простого способа ежедневно мониторить реальную систему и отвечать на вопросы: насколько я близок к сепаратрисе прямо сейчас? Какие у меня C_a^2 и C_s^2 в часы пик? При каком росте трафика я пересеку опасную зону?

Для ответа нужна теория массового обслуживания — модель и формула Кингмана:

W_q \approx \frac{\rho}{1-\rho} \cdot \frac{C_a^2 + C_s^2}{2} \cdot \mathbb{E}[S],

где — загрузка, C_a^2, C_s^2 — квадраты коэффициентов вариации интервалов прихода и времени обслуживания, — среднее время обслуживания. Именно эта формула становится компасом, который превращает абстрактную теорию в повседневный инструмент SRE-инженера.

Что такое i.i.d. и почему это важно.

Формула Кингмана опирается на предположение, что интервалы прихода \Delta t_i = t_i - t_{i-1} — это i.i.d. (independent and identically distributed). Термин содержит два независимых требования.

  • Independent (независимые): величина \Delta t_i не зависит от предыдущих \Delta t_{i-1}, \Delta t_{i-2}, \ldots Знание истории не помогает предсказать следующий интервал.

  • Identically distributed (одинаково распределённые): все имеют одно и то же распределение, не меняющееся во времени и не зависящее от состояния системы.

Физически это означает: система не имеет памяти, и входящий поток не зависит от того, что происходит на выходе. Именно в такой системе формула Кингмана честно описывает стационарную задержку как функцию загрузки. Если хотя бы одно из двух требований нарушено, стационарного режима может не быть вовсе, и W_q из формулы теряет смысл.

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

Область применимости

Формула Кингмана справедлива при \rho < 1, i.i.d. интервалах прихода (см. выше) и конечных вторых моментах и . Инженерно это означает: она корректна в открытой системе без обратной связи. Пока повторов нет, поток внешний, и — это функция загрузки.

Она теряет применимость в двух случаях.

Именно поэтому в примере из разд. 7 (при \rho \geq 3{,}2) формула уже неприменима. Для таких сценариев работают рычаги из разд. 8: retry budget и shedding, а не расчёт по Кингману.

Сравнение моделей

Модель

M/M/1

1

1

\dfrac{\rho}{1-\rho}\cdot\mathbb{E}[S]

M/G/1

1

произв.

\dfrac{\rho}{1-\rho}\cdot\dfrac{1+C_s^2}{2}\cdot\mathbb{E}[S]

(Кингман)

произв.

произв.

\dfrac{\rho}{1-\rho}\cdot\dfrac{C_a^2+C_s^2}{2}\cdot\mathbb{E}[S]

— частный случай при C_s^2=1 и одновременно частный случай аппроксимации Кингмана при C_a^2=C_s^2=1. Формула Поллачека–Хинчина для точна; формула Кингмана для — приближение.

Как читать по логам

Как увидеть угрозу в своих логах до того, как она превратится в аварию.

  1. Собрать интервалы , посчитать C_a^2 = (\sigma_{\Delta t} / \mathbb{E}[\Delta t])^2. Если C_a^2\approx 1 — поток пуассоновский; если C_a^2\gg 1 — трафик идёт пачками (первый признак угрозы).

  2. Собрать времена обслуживания , посчитать C_s^2 = (\sigma_S / \mathbb{E}[S])^2. Если C_s^2\approx 0 — все запросы одинаковы; если C_s^2>1 (а иногда 10 или 50) — «тяжёлые» запросы блокируют быстрые.

  3. Если C_a^2 + C_s^2 > 4 и \rho > 0{,}85 — система в красной зоне. Порог  — эмпирическое «правило большого пальца» SRE-команд, а не теорема. При такой вариативности и высокой загрузке любой сбой сети на  мс с высокой вероятностью запустит лавину повторов. Правило контекстно-зависимо: если клиентский таймаут многократно превышает среднее время обслуживания (\tau/\mathbb{E}[S] \gg 10), лавина повторов может не запуститься даже при высокой вариативности.

Важно для распределённых систем

нужно измерять на конкретном ingress-шлюзе приложения, а не усреднять по кластеру в Prometheus. При нескольких шлюзах потоки на каждом независимы, и общая окажется ниже, чем на любом отдельном: для оценки лавины повторов ориентироваться следует на худший (наиболее пачечный) из шлюзов, а не на усреднённый показатель. Балансировщик (round-robin, least-connections) искусственно сглаживает пачки трафика и маскирует thundering herd.

Что важно понимать: множитель растёт гиперболически, а не экспоненциально. При он равен 9, при — уже 49, то есть в 5,4 раза больше при росте нагрузки всего на 8 п.п. Это и есть «крутой обрыв»: любой микро-всплеск мгновенно превращает очередь в бесконечную.

Пример

Сервис заказов с нормальным потоком 80 rps и временем обработки 10 мс (\rho = 0{,}8). Из-за блокировки БД время обработки выросло до 13 мс (\rho = 1{,}04). Клиенты с 3 повторами (то есть до 4 попыток на запрос) дают до 320 rps — при номинальном это \rho \ge 3{,}2; при актуальных 13 мс ещё выше. Система парализована, хотя база данных уже восстановилась. При формула Кингмана неприменима дважды: \rho > 1 нарушает условие сходимости, а повторы делают поток эндогенным — теряется i.i.d. Именно поэтому здесь важны не расчёты , а рычаги из разд. 8: retry budget и shedding.

8. Защитные механизмы

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

Клиентская защита: backoff, jitter, бюджет повторов

Фиксированная \Delta_{\mathrm{retry}} = 50 мс синхронизирует повторы в плотные пачки. Экспоненциальная задержка (exponential backoff) с full jitter (Brooker, AWS Builders’ Library) разносит повторные попытки по времени: \mathrm{Delay}_{\mathrm{base}} = \mathrm{Base} \cdot 2^n, \mathrm{Delay}_{\mathrm{final}} = \mathrm{Uniform}(0, \mathrm{Delay}_{\mathrm{base}})(с потолком MaxBackoff). Это уменьшает и возвращает .

Retry budget. Лимит попыток обязателен: 3 попытки = 1 исходная + 2 повтора. Без него вложенные повторы на разных уровнях стека перемножаются, и один пользовательский запрос порождает десятки внутренних.

Почему это спасает рекурсию: экспонента отодвигает повторные попытки далеко вперёд; джиттер уничтожает синхронизацию («пульсацию») и размазывает паразитный трафик; средняя задержка между повторными становится больше d = 100 мс, оператор перестаёт накапливать бэклог, буфер очищается.

Результат симуляции: медиана времени восстановления по 100 прогонам T_{\mathrm{rec}}\approx 0{,}9 с, goodput на хвосте \approx 100\%, ценой abandon \approx 50 транзакций на прогон. Drop-on-timeout с не отключается: меняется только клиентская политика повторов (backoff + jitter + budget).

Серверная защита: shedding и circuit breaker

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

Load shedding. Порог W_{\max} < \tau (100 < 150 мс), чтобы сброс срабатывал раньше, чем клиент сдастся. Оценивает offer wait с учётом FIFO-бэклога. Отдаёт 503 без клиентского повтора — иначе плотность ошибок умножится на стоимость обработки, и gate запрётся сам. В терминах формулы Кингмана shedding снижает и не даёт вырасти за счёт бесполезной работы. Затраты прокси на fail-fast (в коде — константа c_{\mathrm{fast}}\approx 2 мс) в модели не бронируются на тред-пуле приложения (server_free): gate сидит перед очередью сервиса и отвечает 503 без занятия воркера.

Ограничение модели (клиенты и 503)

Симуляция предполагает, что клиент различает таймаут () и отказ прокси (503): на 503 повтор не планируется, транзакция сразу уходит в abandon. На практике это идеализация: сторонние библиотеки и «глупые» клиенты нередко шлют повторы и на 503, и тогда shedding переносит петлю на уровень прокси. В проде это закрывается retry budget на клиенте и/или заголовком Retry-After; в модели такой сценарий не воспроизводится.

Circuit breaker. Следит за долей ошибок и таймаутов в скользящем окне. При превышении — Open, вызовы к проблемной зависимости превентивно отвергаются, не тратя локальные ресурсы на заведомо неуспешные попытки. Circuit breaker обнуляет \lambda_{\mathrm{retry}} и даёт время на восстановление.

Результат симуляции: медиана времени восстановления по 100 прогонам T_{\mathrm{rec}}\approx 0{,}1 с, goodput , без abandon. Drop-on-timeout сохраняется; добавляется load shedding на прокси.

Про circuit breaker

В этой статье CB упоминается как дополнительный механизм изоляции проблемной зависимости — концептуально. В симуляции реализован только load shedding на прокси; для разрыва петли повторов этого достаточно, а CB в проде добавляется поверх того же gate.

Сравнение

Таблица 1 суммирует ансамбль из 100 прогонов. Колонка «Восстановление» — число прогонов с восстановлением (из 100). Колонка — медиана времени восстановления по прогонам, в которых восстановление зафиксировано; для baseline значение не определено (восстановления нет).

Таблица 1. Сравнение сценариев защиты по 100 прогонам. Baseline — сценарий без backoff/budget/shedding; drop-on-timeout включён.

Сценарий

Восстановление

Goodput хвост

(медиана)

Baseline (без backoff/budget/shedding)

0/100

\approx 0\%

—

Backoff + jitter + budget

100/100

\approx 0{,}9 с

Shedding (прокси, без повтора)

\approx 0{,}1 с

Итог: обе защиты возвращают систему в норму, но с разной ценой. Backoff сохраняет больше транзакций, shedding — быстрее восстанавливает сервер.

Почему  мс в профиле DET

Если генератор квазидетерминированный (120 \pm 2 мс), S = 100 мс, а T_{\min} = 118 мс > S, сервер гарантированно успевает «остыть». Формально W_q = 0, но микро-всплески на границе дают среднее \approx 0{,}023 мс — на два порядка меньше шага дискретизации.

При пуассоновском приходе (та же ) DES даёт \approx 240 мс — в согласии с аналитическим M/D/1 \approx 250 мс. Порог W_{\max} нужно калибровать по реальному закону прихода: если DET-настройки выкатить в пуассоновский прод, gate будет отдавать ложные 503 ещё до аварии.

Этот раздел подтверждает, что все эффекты разд. 5 вызваны именно сбоем, а не законом прихода: в отсутствие сбоя система ведёт себя штатно при обоих режимах генератора.

Полевые наблюдения Huang et al. (OSDI ’22) показывают, что большинство метастабильных отказов в проде развивается именно по сценарию «повторы после таймаута»: диагностика по goodput/badput и долям таймаутов в скользящем окне — первый практический шаг.

Промышленные рычаги

Эти механизмы уже реализованы в промышленных прокси и планировщиках; ниже — что именно искать в конфигурации.

  • Envoy / Istio: лимиты пула соединений, outlier ejection, retry budgets, circuit breaking — cool-down вместо бесконечного буфера.

  • Adaptive Concurrency / Adaptive Token Bucket — держать перегрузку на прокси до приложения, нащупывая в реальном времени.

  • CoDel / fq_codel (RFC 8289) — на qdisc ядра; не путать с очередью приложения.

  • Resilience4j (наследник идей Hystrix) — CircuitBreaker, RateLimiter, Bulkhead, Retry в JVM-сервисах.

Задача инженера — не «изобрести backoff», а согласованно включить эти рычаги, измерить и вариативность и в chaos-прогоне проверить, что удаление триггера действительно возвращает систему в норму.

9. Что осталось за рамками

За пределами статьи остаётся ещё несколько важных методов, их стоит хотя бы назвать: формальные методы помогают не только в конкретных SRE-задачах, но и формируют способ мышления инженера.

Формальная верификация контракта восстановления (TLA, Timed CCS, model checking) — отдельная тема: на конечных моделях проверяют контракт \Phi_{\mathrm{rec}}(T) — «после снятия триггера система за время возвращается в здоровый режим». Эти методы не заменяют эмпирическую проверку, но без них легко чинить симптомы вместо причины; с ними — целенаправленно устранять метастабильные контуры.

Без моделирования — или хотя бы без ответа на вопрос «что произойдёт с системой при изменении одного или нескольких параметров?» — инженер превращается в шамана.

Чек-лист архитектора

  • Собирайте логи и регулярно вычисляйте и .

  • Мониторьте и через формулу Кингмана.

  • Внедряйте exponential backoff с full jitter на всех клиентах.

  • Ставьте лимит попыток (retry budget) — один, общий, на всех уровнях стека.

  • Ставьте circuit breaker на все межсервисные вызовы (Istio / Envoy / Resilience4j).

  • Настраивайте load shedding с .

  • Настраивайте CoDel (RFC 8289) для внутренних очередей.

  • Применяйте адаптивные лимиты (adaptive concurrency) на входе.

  • Проводите chaos-эксперименты: инъекция задержки \to снятие триггера проверка контракта восстановления.

И главное — проверяйте контракт восстановления: удаление триггера должно автоматически возвращать систему в здоровое состояние. Если этого не происходит, вы попали в ловушку метапетли и должны действовать.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.