Нелинейность и пороги: когда «чуть больше» меняет режим
Трафик рос по 2 % в неделю четыре месяца. Латентность держалась ровно, дежурства были скучными. На пятнадцатой неделе трафик вырос ещё на 2 % — и сервис лёг. Нагрузку срезали обратно до уровня прошлой недели, при котором всё работало. Сервис не поднялся.
Обе части этой истории противоречат линейной интуиции. Первая — потому что причина («ещё 2 %») несоразмерна следствию (полный отказ). Вторая — потому что мы привыкли считать, что если убрать причину, следствие исчезнет. В нелинейных системах это неверно дважды.
В предыдущих главах мы разобрали, что запас накапливает разность потоков, а задержка раскачивает уравновешивающий контур. Обе модели по сути линейные: реакция пропорциональна воздействию, эффекты складываются. Эта глава про то, что происходит, когда пропорциональность и складываемость перестают работать — а в реальных системах они перестают работать регулярно и предсказуемо.
Сразу оговорка, без которой глава превратится в набор красивых слов. Фраза «система нелинейна» сама по себе не несёт информации: нелинейно почти всё. Полезное утверждение выглядит иначе — какая переменная, при каком значении, с каким наблюдаемым признаком и с каким проверяемым следствием. Всё, что ниже, я стараюсь держать в этой форме, а где не получается — помечаю как гипотезу.
Что такое нелинейность и что ею не является
Формально зависимость y = f(x) линейна, если выполняются однородность (удвоили вход — удвоился выход, f(2x) = 2f(x)) и аддитивность (f(a + b) = f(a) + f(b)). Второе свойство называют суперпозицией, и именно оно даёт всю привычную инженерную арифметику: можно чинить узкие места по одному, оценивать вклад каждой оптимизации отдельно, складывать бюджеты. Как только суперпозиция нарушена, эта арифметика перестаёт работать — а вместе с ней и половина стандартных приёмов планирования.
Практический тест на нелинейность занимает один эксперимент: двойная доза.
процедура тест_двойной_дозы(система, воздействие):
1. измерить отклик на воздействие величины X, затем — на 2X
2. если отклик(2X) заметно отличается от 2 * отклик(X) — зависимость нелинейна
3. повторить на 5-7 разных уровнях: одна пара точек не отличает
прямую от гиперболы, ступеньки или насыщения
Пункт 3 важнее первых двух. Самая частая ошибка нагрузочного тестирования — снять одну точку («при 1000 rps латентность 40 мс, значит запас есть») и мысленно провести через неё прямую. Одна точка совместима с любой формой кривой.
Чего нелинейность не означает: не «сложно» и не «непредсказуемо» (гипербола 1/(1 − ρ) нелинейна и предсказывается с точностью до процентов); не «хаотично» (хаос — узкий класс нелинейных систем с чувствительностью к начальным условиям, и большинство инженерных нелинейностей к нему не относятся — матчасть в главе про хаос и динамические системы трека «Математика»); не «нельзя моделировать» — можно, и часто у нелинейности всего один параметр, положение порога.
Откуда нелинейность берётся в инженерных системах:
Ветка «Люди» здесь не для полноты. Пороговые эффекты в человеческой части системы — самые сильные и самые плохо измеряемые: дежурный, который перестал читать алерты, ведёт себя ровно как пул соединений, исчерпавший ёмкость, только без метрики.
Четыре формы нелинейности, которые надо различать
Слово одно, механизмы разные, и лечатся они по-разному. Смешивать их — верный способ применить неработающее средство.
| Форма | Как выглядит | Инженерный пример | Что предсказывает | Обратимость |
|---|---|---|---|---|
| Насыщение | растёт, потом упирается в потолок | пул соединений, лимит CPU, полоса | выше потолка отклик не улучшится ничем, кроме роста потолка | обратима сразу |
| Колено | гладкая кривая, круто растущая после некоторой точки | время ожидания в очереди от загрузки | небольшой рост входа около колена даёт кратный рост отклика | обратима сразу |
| Порог (переключение) | скачок значения при переходе границы | таймаут, OOM-killer, лимит потоков, дискретное число инстансов | до границы эффекта нет вообще, после — качественно другое поведение | обратима, если нет усиливающего контура |
| Смена режима с гистерезисом | система перескакивает в другое состояние и не возвращается | метастабильный отказ через ретраи, деградация кэша | чтобы вернуться, воздействие надо снять сильнее, чем понадобилось для входа | необратима без вмешательства |
Разница между третьей и четвёртой строкой — практически самая дорогая в этом списке. Порог без усиливающего контура — это неприятность: перешагнули, вернулись, живём дальше. Порог, за которым включается усиливающий контур, — это инцидент, который не лечится возвратом нагрузки.
Дальше разберём формы по очереди, начиная с самой измеримой.
Колено: нелинейность, которую можно посчитать заранее
Самая полезная нелинейность для инженера — та, что описывается формулой из теории массового обслуживания. Для простейшей модели с одним обслуживающим прибором и пуассоновским потоком среднее время в системе равно
W = S / (1 − ρ)
где S — среднее время обслуживания, а ρ — загрузка (доля времени, когда прибор занят). Модель грубая, реальные системы ей не подчиняются буквально, но форма кривой воспроизводится почти везде, где есть конкуренция за ресурс.
| Загрузка ρ | W / S | Что даёт рост загрузки ещё на 5 п.п. |
|---|---|---|
| 0.50 | 2.0 | +0.2 (плюс 10 %) |
| 0.70 | 3.3 | +0.7 (плюс 20 %) |
| 0.80 | 5.0 | +1.7 (плюс 33 %) |
| 0.90 | 10.0 | +10.0 (плюс 100 %) |
| 0.95 | 20.0 | +20.0 (плюс 100 %) |
Читать таблицу нужно так: одно и то же изменение входа даёт принципиально разный эффект в зависимости от того, где вы находитесь. Пять процентных пунктов загрузки на уровне 50 % — это шум. Те же пять пунктов на уровне 90 % — удвоение времени ответа.
Отсюда три вывода, каждый из которых проверяем на вашем стенде:
- Запас ёмкости — не роскошь, а расстояние до колена. Спор «зачем нам 40 % свободного CPU, это же деньги на ветер» решается не идеологически: постройте кривую отклика и покажите, во что превращается p99 при загрузке 0.85. Аргумент «средняя утилизация всего 55 %» разбирается ниже, в разделе про усреднение.
- Планировать рост надо в единицах загрузки, а не в процентах трафика. «Трафик вырастет на 20 %» — бесполезная формулировка. «Загрузка вырастет с 0.75 до 0.90» — рабочая.
- Линейная экстраполяция метрик около колена систематически врёт в оптимистичную сторону. Функция
predict_linearв Prometheus честно делает то, что написано в названии, и отлично работает для заполнения диска (там процесс действительно почти линеен). Применять её к латентности или глубине очереди около колена — значит получать спокойный прогноз ровно до момента отказа. Документация функции — в описании функций PromQL.
Проверяемая версия: закон универсальной масштабируемости
Гипербола очереди — не единственная измеримая нелинейность. Для масштабирования по числу узлов или потоков есть модель с двумя параметрами, которые подгоняются по вашим данным, — Universal Scalability Law Нила Гюнтера:
X(N) = λ·N / (1 + σ·(N − 1) + κ·N·(N − 1))
σ — доля сериализации: то, что нельзя распараллелить (коэффициент из закона Амдала)
κ — стоимость когерентности: согласование между узлами, растёт квадратично
N* = sqrt((1 − σ) / κ) — точка разворота, после которой добавление узлов
СНИЖАЕТ пропускную способность
Ценность модели в том, что N* — число, которое можно опровергнуть. Процедура честная: снять пропускную способность на 6–10 разных уровнях конкурентности, подогнать σ и κ регрессией, получить N*, поставить эксперимент на N* и посмотреть, действительно ли там разворот. Если модель промахнулась — она опровергнута, и это нормальный результат. Модель, которую нельзя промахнуть, ничего не стоит. Практика измерений — в главе про нагрузочное тестирование и бенчмаркинг.
Кэш: нелинейность, которую видно в арифметике
Нагрузка на бэкенд равна RPS · (1 − h), где h — доля попаданий в кэш. Зависимость выглядит безобидно линейной по h, но в интересующей нас области значений ведёт себя иначе:
| Hit rate | Доля промахов | Нагрузка на бэкенд относительно h = 0.99 |
|---|---|---|
| 0.99 | 1 % | 1x |
| 0.98 | 2 % | 2x |
| 0.95 | 5 % | 5x |
| 0.90 | 10 % | 10x |
| 0.50 | 50 % | 50x |
Падение hit rate на один процентный пункт с 99 % до 98 % удваивает нагрузку на базу. На дашборде это выглядит как незаметное шевеление зелёной линии. Именно поэтому холодный кэш после рестарта или инвалидации способен положить бэкенд, который в обычном режиме загружен на 20 %: множитель 50x не покрывается никаким разумным запасом.
Это же объясняет, почему деградация кэша так легко превращается в самоподдерживающийся отказ: перегруженный бэкенд отвечает медленнее, кэш не успевает наполняться, hit rate падает дальше. Механику самих кэшей разбирает глава про кэширование, а про кэш как элемент архитектуры — кэширование и масштабирование.
Порог: когда эффекта нет вообще, а потом он есть весь
Колено — это гладкая кривая. Порог — разрыв: до границы поведение одно, после — качественно другое, и промежуточных состояний нет.
Пороги почти всегда живут в лимитах, и главная беда в том, что лимит обычно не ваш. Он в конфиге ядра, в драйвере, в клиентской библиотеке, в квоте облака — то есть там, куда никто не смотрит, пока не упрётся.
| Лимит | Где посмотреть | Что происходит при пересечении |
|---|---|---|
| Файловые дескрипторы | ulimit -n, /proc/<pid>/limits |
accept начинает возвращать ошибку, сервис перестаёт принимать соединения целиком |
| Потоки ОС на процесс | /proc/sys/kernel/threads-max, pids.max в cgroup |
не создаётся новый обработчик; добавление мощности не помогает |
| Соединения к БД | max_connections, размер пула |
новые запросы не встают в очередь, а отбиваются; ретраи усиливают |
| Память контейнера | memory.max в cgroup |
OOM-killer убивает процесс целиком, а не притормаживает его |
| Место на диске | df, watermark файловой системы |
у CoW-систем производительность записи падает задолго до 100 % |
| Порт-рейндж и TIME_WAIT | net.ipv4.ip_local_port_range |
исходящие соединения перестают устанавливаться |
| Квота API провайдера | панель облака | throttling вместо ошибки — деградация без явного отказа |
Публичный разбор ровно такого сценария — отчёт AWS об инциденте Kinesis в ноябре 2020 года: при добавлении мощности во фронтенд-флот число потоков на сервер превысило лимит операционной системы, и сервис перешёл в отказ. Причина не в нагрузке и не в баге логики — в пересечении порога, о существовании которого команда не знала (aws.amazon.com/message/11201). Урок конкретный: инвентаризация лимитов — обычная инженерная работа, а не паранойя. Для каждой строки таблицы должно быть известно текущее значение, потолок и расстояние между ними; это и есть первая практическая метрика главы.
Отдельная категория порогов — те, которые вы создали сами и забыли. Таймаут — это порог: 1990 мс и 2010 мс дают качественно разный результат при одинаковой нагрузке. Порог алерта — тоже: система с загрузкой 0.79 и 0.81 ведёт себя одинаково, а вот дежурный — по-разному.
Гистерезис: почему выйти труднее, чем войти
Самая дорогая форма. Система переходит в другой режим при одном значении воздействия, а возвращается — при заметно меньшем. Разница между двумя порогами и называется гистерезисом.
На картинке видно главное: между порогом возврата и порогом срыва существуют два устойчивых состояния при одной и той же нагрузке. В каком из них окажется система, определяется не текущей нагрузкой, а историей — тем, откуда она в эту точку пришла. Это прямое опровержение интуиции «состояние системы определяется её входами».
Механизм: усиливающий контур, включающийся за порогом
Ключевая деталь схемы — условие включения контура R. До тех пор пока время ожидания меньше таймаута, повторов почти нет, и контур неактивен: система живёт под управлением уравновешивающих связей. Как только время ожидания пересекает таймаут, контур включается целиком и сразу, и дальше он питает сам себя. Работа, которую сервер продолжает выполнять для клиентов, уже ушедших по таймауту, не создаёт полезного goodput, но полностью расходует ёмкость.
Именно эта конструкция описана как метастабильный отказ: устойчивое состояние отказа, которое поддерживается контуром, а не исходной причиной. Триггер давно исчез, а система в отказе. Основные работы — Bronson et al., «Metastable Failures in Distributed Systems» (HotOS ‘21) и Huang et al., «Metastable Failures in the Wild» (OSDI ‘22), где собраны и классифицированы публичные постмортемы с этим механизмом. У явления есть предок постарше — congestion collapse в сетях, описанный Нейглом в RFC 896 (1984) и разобранный Ван Джекобсоном в «Congestion Avoidance and Control» (SIGCOMM 1988).
Симуляция: где пороги и от чего они зависят
Модель на сорок строк, которая воспроизводит обе половины истории из начала главы.
def ramp(loads, capacity=100.0, timeout=2.0, retry_p=0.6,
queue_limit=1000.0, dt=0.05, hold=2000):
"""Сервис с FIFO-очередью, клиентским таймаутом и повторами.
capacity — сколько запросов в секунду сервер физически успевает обработать;
timeout — сколько секунд клиент ждёт ответа, потом уходит;
retry_p — доля неудачных попыток, которые клиент повторяет;
queue_limit — размер буфера, всё сверх него отбрасывается сразу.
Сервер не знает, что клиент уже ушёл, и обрабатывает очередь целиком —
именно эта «потерянная работа» делает отказ самоподдерживающимся.
Возвращает для каждой нагрузки установившийся полезный goodput.
"""
q, retry, trace = 0.0, 0.0, []
for lam in loads:
for _ in range(hold): # держим нагрузку до установления
offered = (lam + retry) * dt
accepted = min(offered, queue_limit - q)
q += accepted
served = min(capacity * dt, q)
q -= served
# голова очереди прождала q/capacity; если это больше таймаута,
# клиента там уже нет и вся обработка уходит в никуда
good = 0.0 if q / capacity > timeout else served
failed = offered - good
retry = max(0.0, failed / dt) * retry_p
trace.append((lam, good / dt))
return trace
def thresholds(retry_p):
"""Порог срыва (нагрузку поднимаем) и порог возврата (опускаем)."""
up = list(range(60, 161, 2))
down = list(reversed(range(4, 161, 2)))
trace_up = ramp(up, retry_p=retry_p)
trace_down = ramp(up + down, retry_p=retry_p)[len(up):]
collapse = next(l for l, g in trace_up if g < 0.5 * l)
recover = next(l for l, g in trace_down if g > 0.9 * l)
return collapse, recover
Сложность: O(len(loads) · hold) по времени, O(1) по памяти — состояние системы это два числа, очередь и поток повторов.
Результаты прогона (воспроизводятся на приведённом коде, ёмкость 100 rps):
Доля повторов retry_p |
Порог срыва | Порог возврата | Аналитика: C·(1 − p) |
|---|---|---|---|
| 0.0 | 104 | 94 | 100 |
| 0.2 | 102 | 74 | 80 |
| 0.4 | 102 | 56 | 60 |
| 0.6 | 102 | 36 | 40 |
| 0.8 | 102 | 18 | 20 |
Из таблицы читается результат, который стоит выучить наизусть.
Политика повторов почти не влияет на то, когда система сорвётся, но определяет, насколько глубоко придётся снять нагрузку, чтобы она поднялась. Срыв происходит около ёмкости при любой политике. Порог возврата равен примерно
C · (1 − p), то есть падает в1/(1 − p)раз.
Вывод получается из простой арифметики, а не из симуляции: в режиме отказа каждая попытка проваливается, значит на один исходный запрос приходится 1/(1 − p) попыток. Очередь начнёт рассасываться только когда λ/(1 − p) < C. Симуляция подтверждает предсказание с точностью до шага рампы (измеренный порог систематически на 2–4 rps ниже расчётного — это цена конечного времени удержания каждой ступени).
Три практических следствия, каждое проверяемо у вас. Ретраи не приближают отказ — они удорожают выход из него, поэтому спор «ретраи это хорошо или плохо» поставлен неверно: они хороши до порога и катастрофичны за ним, отсюда обязательность бюджета повторов и circuit breaker, отключающих контур именно при переходе границы (паттерны — в resilience patterns, эталонный разбор — «Timeouts, retries, and backoff with jitter» в AWS Builders’ Library). План восстановления должен содержать число: не «снизим нагрузку», а «снизим до 40 % от номинала», иначе восстановление превращается в метод проб, где каждая неудачная проба — ещё один цикл с полной очередью. И дешевле всего не входить: ширина гистерезиса известна заранее, до инцидента, а лимит конкурентности стоит несопоставимо дешевле процедуры вывода из метастабильного состояния.
Режимы и переходы
Переход Meta --> Rec через сброс очереди — это не «грубый хак», а прямое следствие структуры: чтобы разорвать контур, достаточно убрать любое его звено. Обнулить очередь, отключить ретраи, отрезать часть трафика — три разных способа сделать одно и то же; мощность в этот список не входит. Тот же механизм работает и в человеческой части системы: очередь инцидентов, дежурный с ограниченной ёмкостью, «таймаут» как момент, после которого разбор уже не имеет смысла. Практика дежурств — в главе про дежурства и инциденты и в наблюдаемости и дежурстве; разбор инцидента как системы — в главе 14.
Что нелинейность ломает в привычных рассуждениях
Эффекты перестают складываться
Раз суперпозиции нет, вклад двух изменений не равен сумме вкладов. Два независимо протестированных улучшения вместе могут дать меньше, чем каждое по отдельности: одно перевело систему через порог, где второе уже не работает. Ускорение этапа перед узким местом прямо ухудшает систему — очередь перед узким местом заполняется быстрее и раньше пересекает порог; тема развёрнута в главе про локальную оптимизацию. А атрибуция эффекта после нескольких одновременных изменений в нелинейной области ненадёжна в принципе: это не проблема дисциплины экспериментатора, а свойство системы.
Средние перестают значить то, что кажется
Здесь работает неравенство Йенсена: для выпуклой функции f среднее значение функции не меньше функции от среднего, E[f(X)] ≥ f(E[X]). Кривая отклика около колена выпукла, значит средняя латентность всегда хуже, чем латентность при средней загрузке.
Численно: пусть половину времени загрузка 0.3, половину — 0.9. Средняя загрузка 0.6, и по формуле 1/(1 − ρ) это даёт W/S = 2.5. Фактическое среднее: (1/0.7 + 1/0.1)/2 = (1.43 + 10)/2 = 5.7. Ошибка в 2.3 раза, и она всегда в оптимистичную сторону.
Отсюда правило: аргумент «средняя утилизация всего 60 %» ничего не доказывает, пока не показано распределение. Сэм Сэвидж назвал это «изъяном средних» (The Flaw of Averages, 2009) и собрал каталог того, во что он обходится в планировании.
Опасны все три оси усреднения. По времени: окно шире пика делает пик невидимым. По инстансам: среднее по кластеру при перекошенной балансировке скрывает узлы, постоянно сидящие за коленом, — нужен максимум или высокая квантиль по узлам. По запросам: среднее время ответа при бимодальном распределении не описывает ни одну реальную группу, разбор — в главе про измерения.
Опыт перестаёт быть доказательством запаса
«Мы работали на этой нагрузке год, всё было нормально» — корректное наблюдение и неверный вывод. Оно доказывает только то, что система не пересекала порог, но ничего не говорит о расстоянии до него. В докритической области системы с большим и с нулевым запасом выглядят одинаково — в этом вся проблема.
Правый верхний квадрант — то, ради чего вообще существует эта глава. Работа с ним состоит не в «повышении внимательности», а в двух конкретных действиях: сделать порог видимым заранее (сдвинуть точку влево) и разорвать усиливающий контур (сдвинуть вниз). Общий разговор о том, где вмешательство даёт результат, — в главе про точки воздействия.
Как измерять пороги: четыре процедуры
1. Рампа вверх-вниз — основной инструмент
Обычный нагрузочный тест поднимает нагрузку до отказа и останавливается. Для нелинейной системы этого мало: он измеряет один порог из двух.
процедура рампа_вверх_вниз(система, шаг, выдержка):
1. поднимать нагрузку ступенями по «шаг», на каждой ступени держать
«выдержка» (заведомо больше времени установления контура)
2. на каждой ступени писать: goodput, глубину очереди, долю ошибок,
долю повторов, время ожидания
3. зафиксировать нагрузку L_срыва — первую ступень, где goodput упал
4. НЕ останавливать тест и НЕ перезапускать систему
5. опускать нагрузку теми же ступенями
6. зафиксировать L_возврата — первую ступень, где goodput восстановился
7. гистерезис = L_срыва − L_возврата
Пункт 4 — тот, из-за которого процедуру обычно проваливают. Перезапуск сервиса после срыва стирает ровно ту информацию, ради которой ставился тест.
Что даёт результат:
L_срыва— граница безопасной эксплуатации; лимит конкурентности ставится ниже неё, а не «на глаз».L_возврата— число, которое пишется в ранбук восстановления.- Ширина гистерезиса — прямая оценка того, насколько ваша политика повторов и таймаутов усиливает отказ. Ноль означает, что усиливающего контура нет.
Тест дешёвый, повторяемый и опровергающий: он либо находит гистерезис, либо показывает, что его нет.
2. Расстояние до порога как метрика
Для каждого известного лимита заводится метрика не уровня, а запаса:
groups:
- name: headroom
rules:
# Запас, а не уровень: доля свободного до жёсткого лимита.
- record: job:fd_headroom:ratio
expr: 1 - (process_open_fds / process_max_fds)
# Алерт по СКОРОСТИ приближения, а не по уровню: если текущего запаса
# хватит меньше чем на час — реагировать уже сейчас.
- alert: HeadroomClosingFast
expr: |
job:fd_headroom:ratio
/ clamp_min(-deriv(job:fd_headroom:ratio[30m]) * 3600, 1e-9) < 1
for: 10m
annotations:
summary: "Запас по файловым дескрипторам закончится в течение часа"
Идея важнее конкретного выражения: алерт на уровень срабатывает тем позже, чем ближе порог, потому что около порога процессы ускоряются. Алерт на время до исчерпания сохраняет одинаковое предупредительное окно.
Честная оговорка: производная запаса — линейная экстраполяция, то есть ровно то, что эта глава критикует. Она пригодна для медленных, действительно почти линейных процессов (диск, дескрипторы, рост числа объектов) и непригодна для латентности и глубины очереди. Инструмент правильный для одного класса и неправильный для другого — это надо просто помнить.
3. Снять кривую, а не точку
Минимальный набор для утверждения о форме зависимости — 5–7 точек, распределённых по диапазону, с повторами. Три вопроса, на которые кривая отвечает, а точка нет: где колено, где потолок, есть ли участок разворота (падение пропускной способности при росте нагрузки).
Ключевой практический признак: если пропускная способность при росте нагрузки падает, а не выходит на плато — в системе есть усиливающий контур. Плато означает насыщение и обратимость. Падение означает потерянную работу и, скорее всего, гистерезис.
4. Ранние признаки перехода — и почему на них нельзя полагаться
В литературе по критическим переходам описан эффект критического замедления: при приближении к точке перехода система всё медленнее возвращается к равновесию после малых возмущений, а её дисперсия и автокорреляция растут. Обзор — Scheffer et al., «Early-warning signals for critical transitions» (Nature, 2009).
Инженерный перенос звучит соблазнительно: следить за дисперсией времени ответа и автокорреляцией глубины очереди, чтобы увидеть приближение к порогу до самого перехода. Это разумная гипотеза, и её стоит проверять на своих данных ретроспективно, по историям инцидентов.
Полагаться на неё как на инструмент — нельзя, и об этом честно пишут в самой этой литературе:
- Boettiger и Hastings, «Early warning signals and the prosecutor’s fallacy» (Proc. R. Soc. B, 2012): рост дисперсии перед переходом — не то же самое, что переход после роста дисперсии. Базовая частота ложных срабатываний высока.
- Ditlevsen и Johnsen, «Tipping points: Early warning and wishful thinking» (Geophysical Research Letters, 2010): на реальных зашумлённых рядах сигнал обнаруживается заметно хуже, чем на модельных.
Практический вывод для инженера: рост дисперсии — повод посмотреть, а не повод разбудить дежурного. Известное расстояние до известного лимита надёжнее любого статистического предвестника.
Пределы метода
Это раздел, который в популярных изложениях пропускают, а он определяет, будет ли ваша схема работать или украшать стену.
Где нелинейная модель предсказывает хорошо:
- Положение порога, когда порог — это явный лимит. Точность высокая, проверка тривиальная.
- Форма кривой отклика в системах с конкуренцией за ресурс. Гипербола воспроизводится устойчиво.
- Наличие и знак гистерезиса. Тест рампой отвечает однозначно.
- Соотношение порогов:
L_возврата ≈ C · (1 − p). Проверяемо, опровержимо, полезно.
Где предсказывает плохо или не предсказывает совсем:
- Точное значение порога в системах, где он определяется не одним лимитом, а совокупностью (перегрев, конкуренция за память, планировщик). Порядок величины — да, число — нет.
- Поведение сразу после перехода. Амплитуда и траектория чувствительны к деталям, которых в модели нет.
- Пороги в человеческой части системы. Они существуют, но их положение зависит от контекста, состава команды и истории. Любая цифра здесь — гипотеза.
- Момент перехода во времени. Модель говорит, при каком значении произойдёт переход, а не когда это значение будет достигнуто.
Где нелинейность превращается в отговорку. Есть характерный жанр: схема с петлями, порогами и подписью «система нелинейна, поэтому поведение непредсказуемо». Такая схема совместима с любыми данными и потому бесполезна. Рабочий критерий — четыре обязательных элемента: какая переменная, при каком численном значении, по какому наблюдаемому признаку, с каким следствием. Если хотя бы один отсутствует, перед вами иллюстрация.
Честно про доказательную базу. Нелинейность — центральное понятие системной динамики Джея Форрестера, и именно она отвечает за самый известный результат этой школы: режим «перелёт и коллапс» в модели World3 из «Пределов роста» (Meadows et al., 1972). Здесь важно понимать структуру спора.
- Критика Уильяма Нордхауса («World Dynamics: Measurement Without Data», The Economic Journal, 1973) и сборник «Models of Doom» (Cole et al., 1973) указывали не на то, что нелинейностей не бывает, а на то, что конкретные нелинейные функции в модели заданы экспертно, а не оценены по данным. Это самое слабое место любой модели такого типа: нелинейная табличная функция, нарисованная от руки, способна дать почти любой качественный вывод, и модель послушно его воспроизведёт.
- Последующие сверки сценариев World3 с фактическими рядами (Turner, 2008; Herrington, 2021) находят близость к базовым сценариям, но и они критикуются — за выбор рядов и за то, что попадание в широкий сценарий слабо отличается от отсутствия предсказания.
- Джон Стерман, автор основного учебника дисциплины («Business Dynamics», 2000), сам отводит формулировке и тестированию нелинейных зависимостей отдельные главы — именно потому, что это признанное слабое звено, а не потому, что вопрос закрыт.
Ещё поучительнее история теории катастроф Рене Тома, которую в 1970-е активно применяли к социальным и биологическим системам: она даёт красивую классификацию именно тех переходов с гистерезисом, которые мы разбирали выше. Разгромная статья Сассмана и Залера («Catastrophe theory as applied to the social and biological sciences: a critique», Synthese, 1978) показала, что в большинстве приложений математика была корректной, а связь с данными — отсутствующей: модель подгонялась под уже известный результат и ничего нового не предсказывала. Математическое ядро теории живо и используется, а волна приложений схлопнулась.
Практический вывод для инженера ровно один и он отличает эту главу от философии: нелинейную зависимость надо измерять, а не рисовать. У нас, в отличие от климатологов и экономистов, есть роскошь эксперимента: стенд, рампа, повторяемость. Пользоваться ею дешевле, чем спорить о форме кривой.
Типовые ловушки
Локальная оптимизация через порог. Ускорили сервис A, стоящий перед сервисом B. Пропускная способность A выросла на 30 %, B перешёл за колено, общее время ответа выросло. Формально каждая команда улучшила свою метрику. Диагностический признак: улучшение локального показателя совпало по времени с ухудшением сквозного.
Перенос проблемы: поднять лимит вместо разрыва контура. Упёрлись в max_connections — увеличили. Порог отодвинулся, усиливающий контур остался на месте, а вместе с новым лимитом выросла и глубина падения при следующем срыве: очередь теперь больше, потерянной работы больше, порог возврата ниже. Поднятие лимита — правильное действие только тогда, когда за порогом нет усиливающего контура. Иначе это отсрочка, купленная за ухудшение исхода.
Эскалация таймаутов. Команда A видит таймауты, поднимает свой таймаут. Теперь её запросы дольше держат соединения в команде B, у B растёт очередь, B поднимает свой таймаут. Через два квартала таймауты в системе измеряются десятками секунд, а порог срыва не изменился — изменилась только ширина гистерезиса, в худшую сторону: длинный таймаут означает больше потерянной работы на один провал. Признак: параметры устойчивости у всех участников только растут и никогда не возвращаются.
Трагедия общего ресурса с общим порогом. Пул соединений к общей базе, общий кластер, общая квота. Каждая команда добавляет «немного», её собственный вклад в загрузку действительно мал, и локально каждое решение разумно. Порог общий, и пересекается он один раз для всех. Особенность именно нелинейного случая: до порога никто не получает обратной связи вообще, поэтому «договориться» не работает — обратная связь к участникам просто не доходит. Лечится структурно: квоты на команду, атрибуция потребления, отдельные пулы.
Костыль, вокруг которого вырос зависимый процесс. Сервис падал по OOM — поставили автоперезапуск. Пороговый отказ превратился в регулярное невидимое событие, симптом исчез, утечка осталась и продолжила расти. Через год перезапуск случается каждые 40 минут, к нему привязаны прогрев кэша, скрипт очистки и расписание миграций. Теперь выключить его нельзя: от него зависят три процесса, которых при проектировании не было. Проверка простая — спросите, что сломается, если отключить костыль завтра. Ответ длиннее одного предложения означает, что вы уже поддерживаете архитектуру, которую не проектировали.
Экстраполяция прошлого спокойствия. «Мы пережили Чёрную пятницу, значит выдержим». Выдержали при том hit rate, при том размере данных, при той версии зависимости. Любой из этих параметров сдвигает порог. Прошлый успех говорит о расстоянии до порога тогда, а не сейчас. Тот же дефект у аргумента «средняя утилизация кластера 55 %, запас двукратный»: одно число про нелинейную систему почти всегда вводит в заблуждение, требуйте распределение.
Ремонт по симптому в нелинейной зоне. Увидели рост латентности — добавили инстансов. Если система уже в метастабильном отказе, новая мощность тоже уйдёт на обслуживание клиентов, которые давно ушли по таймауту. Действие выглядит разумным, стоит денег и не даёт эффекта, что дополнительно запутывает разбор.
Чеклист
Когда система «внезапно» сломалась или вы планируете рост:
- Выписать все жёсткие лимиты на пути запроса: дескрипторы, потоки, соединения, память, диск, квоты. Для каждого — текущее значение, потолок, запас.
- Снять кривую отклика на 5–7 уровнях нагрузки. Найти колено. Записать, где вы относительно него.
- Проверить, падает ли пропускная способность за пиком или выходит на плато. Падение означает усиливающий контур.
- Прогнать рампу вверх-вниз без перезапуска. Записать порог срыва, порог возврата и ширину гистерезиса. Порог возврата — в ранбук, числом: план восстановления без числа не план.
- Проверить политику повторов: есть ли бюджет, есть ли circuit breaker, отключаются ли повторы при массовых отказах.
- Заменить алерты на уровень алертами на время до исчерпания запаса — там, где процесс действительно близок к линейному.
- Проверить каждое утверждение вида «в среднем» на распределение: по времени, по узлам, по запросам.
- Для каждой нелинейности на вашей схеме ответить: какая переменная, при каком значении, по какому признаку, с каким следствием. Нет ответа — уберите со схемы.
Мини-итог
- Линейность означает пропорциональность и складываемость эффектов. Нелинейность отменяет и то и другое, а вместе с ними — привычную арифметику планирования и атрибуции.
- Различайте четыре формы: насыщение, колено, порог, смену режима с гистерезисом. Первые три обратимы, четвёртая — нет.
- Колено считается заранее:
W = S/(1 − ρ). Одинаковое изменение загрузки даёт принципиально разный эффект в зависимости от того, где вы на кривой. Запас ёмкости — это расстояние до колена. Такая же арифметика у кэша: падение hit rate с 99 % до 98 % удваивает нагрузку на бэкенд — мелкое движение метрики, кратный эффект. - Пороги обычно живут в чужих лимитах: ядро, драйвер, библиотека, квота облака. Инвентаризация лимитов — обычная работа, а не паранойя.
- За порогом может включаться усиливающий контур. Тогда система входит в метастабильный отказ, который поддерживает сам себя, и добавление мощности не помогает: разрывать надо контур.
- Ретраи не приближают срыв, но удорожают выход из него: порог возврата падает примерно до
C · (1 − p). Это проверяемое предсказание, и оно даёт число для ранбука. - Средние через нелинейность систематически врут в оптимистичную сторону (неравенство Йенсена). «Средняя утилизация 60 %» — не аргумент без распределения.
- Нелинейную зависимость надо измерять, а не рисовать: рампа вверх-вниз даёт оба порога за один тест. Схема без четырёх элементов (переменная, значение, признак, следствие) — иллюстрация, а не модель.
Источники
- N. Bronson, A. Aghayev, A. Charapko, T. Zhu. Metastable Failures in Distributed Systems. HotOS ‘21 — постановка задачи и словарь метастабильных отказов.
- L. Huang, M. Garrett, T. Zhu. Metastable Failures in the Wild. OSDI ‘22 — классификация публичных инцидентов с этим механизмом.
- J. Nagle. RFC 896: Congestion Control in IP/TCP Internetworks, 1984; V. Jacobson. Congestion Avoidance and Control. SIGCOMM, 1988 — первое описание коллапса через усиливающий контур.
- Summary of the Amazon Kinesis Event in the Northern Virginia Region, AWS, 2020 — отказ при пересечении лимита потоков ОС.
- Timeouts, retries, and backoff with jitter и Using load shedding to avoid overload — AWS Builders’ Library.
- Handling Overload и Addressing Cascading Failures — Google SRE Book.
- N. Gunther. Guerrilla Capacity Planning. Springer, 2007 — Universal Scalability Law и подгонка параметров по измерениям; L. Kleinrock. Queueing Systems, Vol. 1. Wiley, 1975 — формула отклика и границы её применимости.
- S. Savage. The Flaw of Averages. Wiley, 2009 — каталог ошибок планирования по средним значениям.
- D. Meadows. Thinking in Systems. Chelsea Green, 2008, глава «Why Systems Surprise Us» — введение, но не доказательство; J. Sterman. Business Dynamics. McGraw-Hill, 2000 — главы про формулирование и тестирование нелинейных зависимостей.
- W. Nordhaus. World Dynamics: Measurement Without Data. The Economic Journal 83(332), 1973; H. Cole et al. Models of Doom, 1973 — критика нелинейных функций, заданных экспертно.
- H. Sussmann, R. Zahler. Catastrophe theory as applied to the social and biological sciences: a critique. Synthese 37(2), 1978 — как корректная математика переходов давала неопровержимые и потому бесполезные приложения.
- M. Scheffer et al. Early-warning signals for critical transitions. Nature 461, 2009; C. Boettiger, A. Hastings. Early warning signals and the prosecutor’s fallacy. Proc. R. Soc. B 279, 2012 — заявка и её честная критика.
Что дальше
Нелинейность объясняет, почему количественное изменение даёт качественно другое поведение одного и того же набора элементов. Следующий шаг — свойства, которых у отдельных элементов нет вообще, ни при каких значениях параметров, и которые появляются только у целого.