Системное мышление Локальная оптимизация: как улучшение части ломает целое
0%

Локальная оптимизация: как улучшение части ломает целое

Локальная оптимизация: как улучшение части ломает целое

Команда сервиса корзины закрыла квартальную цель: p99 их эндпоинта упал с 240 мс до 90 мс. Способ честный — убрали синхронный вызов сервиса скидок, перенесли его в фоновую задачу, ответ отдают из локального кэша. Метрика на дашборде улучшилась в два с половиной раза, в ретро написали «успех», инженера повысили.

Через шесть недель продуктовая аналитика приносит другое число: доля заказов, доведённых до оплаты, упала на 1,8 %. Разбираются месяц. Оказывается, скидка теперь применяется асинхронно и иногда не успевает к моменту показа итоговой суммы; пользователь видит цену без промокода, закрывает вкладку и не возвращается. Сервис корзины при этом здоров: p99 90 мс, ошибок нет, SLO зелёное.

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

Что именно называется локальной оптимизацией

Формулировка через целевые функции удобна тем, что сразу видно источник проблемы.

Система разбита на подсистемы 1..n.
У целого есть цель F — то, ради чего система существует.
У каждой подсистемы есть измеримая цель f_i — то, за что с неё спрашивают.

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

Три следствия:

  1. Проблема не в мотивации. Даже идеально добросовестный участник, максимизирующий f_i, будет вредить F, если связь между ними немонотонна. Разговоры про «надо думать о продукте» ничего не меняют, пока структура измерений и обратных связей прежняя.
  2. Проблема не всегда есть. Если в рабочем диапазоне F растёт вместе с f_i и действие не меняет потоки на границе подсистемы — локальная оптимизация просто полезна. Ниже будет отдельный раздел о том, как отличить один случай от другого; смешивать их — самая частая ошибка начинающих «системщиков».
  3. Виновата структура, а не схема. Правильный вопрос не «кто виноват», а «какая связь между f_i и F отсутствует, слаба или запаздывает».

Три структурные причины

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

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

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

Локальная метрика улучшилась вдвое, сквозная деградировала с задержкой в шесть недель

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

Почему сумма локальных оптимумов не равна оптимуму целого

Это не философское наблюдение — у него есть проверяемая математическая часть.

Ограничение задаёт пропускную способность, и оно одно

Самое простое и самое игнорируемое утверждение. Пропускная способность последовательного конвейера равна пропускной способности самого слабого этапа. Ускорение любого другого этапа не меняет выход системы вообще — оно только перемещает запас ближе к ограничению.

Ускорение не-узкого этапа не меняет ни выход, ни суммарный запас — оно перемещает запас за спину ускоренного этапа

Проверить это можно за двадцать строк. Дискретная модель конвейера: на каждом такте каждый этап пропускает не больше своей ёмкости, остальное копится в буфере перед ним.

Псевдокод:

на каждом такте t:
    буфер[0] += приток
    перенос = 0
    для каждого этапа i от первого к последнему:
        буфер[i] += перенос
        сдвиг = min(ёмкость[i], буфер[i])
        буфер[i] -= сдвиг
        перенос = сдвиг
    выход += перенос

Реализация:

from dataclasses import dataclass

@dataclass
class Stage:
    name: str
    capacity: int   # единиц за такт — максимум, который этап может пропустить
    buffer: int = 0 # запас ПЕРЕД этапом (единиц)

def simulate(stages: list[Stage], inflow: int, ticks: int) -> dict:
    """Детерминированный конвейер: приток постоянен, ёмкости фиксированы."""
    done = 0
    for _ in range(ticks):
        stages[0].buffer += inflow
        carry = 0
        for st in stages:
            st.buffer += carry           # к нам пришло то, что сдвинул предыдущий
            carry = min(st.capacity, st.buffer)
            st.buffer -= carry           # непереваренное осталось в буфере
        done += carry                    # то, что вышло из последнего этапа
    wip = sum(st.buffer for st in stages)
    return {"выход": done, "WIP": wip, "буферы": {st.name: st.buffer for st in stages}}

def pipeline(b_capacity: int) -> list[Stage]:
    return [Stage("A", 120), Stage("B", b_capacity), Stage("C", 60), Stage("D", 150)]

# случай 1: приток 90 — этап B и так не был ограничением
print(simulate(pipeline(90),  inflow=90,  ticks=60))
print(simulate(pipeline(180), inflow=90,  ticks=60))
# оба: {'выход': 3600, 'WIP': 1800, 'буферы': {'A': 0, 'B': 0, 'C': 1800, 'D': 0}}

# случай 2: приток 120 — этап B был вторым по узости
print(simulate(pipeline(90),  inflow=120, ticks=60))
# {'выход': 3600, 'WIP': 3600, 'буферы': {'A': 0, 'B': 1800, 'C': 1800, 'D': 0}}
print(simulate(pipeline(180), inflow=120, ticks=60))
# {'выход': 3600, 'WIP': 3600, 'буферы': {'A': 0, 'B': 0,    'C': 3600, 'D': 0}}

Сложность: O(T · S) по времени и O(S) по памяти, где T — число тактов, S — число этапов.

Случай 1 — чистая потеря. При притоке 90 этап B с ёмкостью 90 не был ограничением: он просто был вторым по загруженности. Ускорение вдвое не изменило ни одного числа на выходе модели. Деньги и время инженеров потрачены ровно ни на что; локальная метрика при этом улучшилась вдвое и попала в отчёт.

Случай 2 — интереснее и обычно понимается неправильно. При притоке 120 этап B действительно был перегружен, и после ускорения перестал им быть. Что изменилось: выход — нет (3600 в обоих запусках), суммарный запас в системе — тоже нет (3600 в обоих). Это не совпадение, а арифметика сохранения: за 60 тактов вошло 7200, вышло 3600, разница обязана где-то лежать независимо от того, как распределены ёмкости внутри. Из этого по закону Литтла следует, что и среднее сквозное время не изменилось: и WIP, и пропускная способность те же.

Изменилось единственное — где именно лежит запас: B: 1800, C: 1800 превратилось в B: 0, C: 3600. Работа переехала за спину этапа B, и это не нейтрально:

  • её уже нельзя дёшево отменить или переприоритизировать — она обработана и ждёт только у ограничения;
  • буфер перед C в реальности конечен: раньше переполнение случилось бы на входе в B (мягкий отказ, backpressure к источнику), теперь — прямо перед узким местом, часто в виде дропов и потерянных задач;
  • у команды B закрыта квартальная цель, а у системы ничего не изменилось.

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

Это ядро теории ограничений Голдратта: «час, потерянный на узком месте, — час, потерянный всей системой; час, сэкономленный на не-узком месте, — мираж». Формулировка эвристическая, но конкретно это утверждение про последовательный поток с фиксированными ёмкостями — арифметика, а не менеджерская мудрость.

Амдал: оценка потолка до начала работы

Тот же аргумент в другой форме, применимый к одному запросу вместо конвейера.

ускорение сквозного времени = 1 / ((1 − f) + f / s)

f — доля сквозного времени, приходящаяся на оптимизируемый компонент
s — во сколько раз вы ускорили этот компонент

Компонент занимает 8 % сквозного времени. Ускорили его в бесконечное число раз — получили 1 / 0.92 = 1.087, то есть 8,7 % сквозного выигрыша, потолок; ускорили вдвое — 4,2 %. Если бизнесу обещано «ускорим оформление заказа вдвое», а f = 0.08, обещание невыполнимо арифметически, и это видно до написания первой строки кода. Практическое правило: прежде чем оптимизировать компонент, измерьте его долю в сквозном времени и посчитайте потолок. Это одна строка в тикете, и она отсекает значительную часть работы, которая иначе была бы сделана и оказалась бы невидимой снаружи. Подробности профилирования — в измерении производительности и рабочем процессе оптимизации; закон Амдала сформулирован в статье Джина Амдала «Validity of the single processor approach to achieving large scale computing capabilities», AFIPS 1967.

Утилизация: локально «эффективно», глобально катастрофа

Загрузка ресурса — классическая локальная метрика, за которую спрашивают отдельно от результата. «Воркеры простаивают 40 % времени», «у команды нет работы в спринте», «сервер загружен всего на 50 %». Все три фразы означают одно: у системы есть запас на всплеск.

Приближение Кингмана для одноканальной очереди даёт связь загрузки и ожидания:

W_q ≈ ( ρ / (1 − ρ) ) · ( (c_a² + c_s²) / 2 ) · τ

ρ — загрузка, c_a и c_s — коэффициенты вариации интервалов прихода и времени обслуживания,
τ — среднее время обслуживания

Множитель ρ / (1 − ρ) при загрузке 0,5 равен 1, при 0,8 — 4, при 0,95 — 19. Локальное «поднимем утилизацию с 80 % до 95 %» умножает ожидание почти в пять раз при том же объёме работы. Форма кривой и её колено разбирались в главе о нелинейности; первоисточник — J. F. C. Kingman, «The single server queue in heavy traffic», 1961.

Тот же множитель управляет людьми и процессами: команда, загруженная «под завязку», имеет длинную очередь незавершённой работы и большое время прохождения задачи. Это основная линия аргументации Дональда Райнертсена в «The Principles of Product Development Flow» (2009) и практическое обоснование WIP-лимитов, о которых подробно в канбане и потоке.

Важная оговорка про доказательность: формула Кингмана — асимптотическое приближение для тяжёлой загрузки в модели GI/G/1. Для многоканальных систем, приоритетных дисциплин, батчинга и автоскейлинга множитель другой. Порядок эффекта («растёт быстрее линейного, взрывается у единицы») переносится; конкретное число — нет. Считать по ней SLA нельзя, использовать как аргумент против цели «утилизация 95 %» — можно.

Парадокс Браесса: добавление ресурса ухудшает результат

Самый неприятный для интуиции случай. Дитрих Браесс в 1968 году показал: в дорожной сети, где каждый водитель выбирает кратчайший для себя маршрут, добавление новой дороги может увеличить время в пути для всех — потому что новая дорога перераспределяет поток так, что равновесие смещается в худшую точку. Английский перевод оригинала: Braess, Nagurney, Wakolbinger, «On a paradox of traffic planning», Transportation Science 39(4), 2005.

Это теорема о равновесии, а не наблюдение. Тим Рафгарден и Ева Тардош в работе «How bad is selfish routing?» (JACM 49(2), 2002) ограничили масштаб потерь: для сетей с линейными функциями задержки эгоистичное равновесие хуже оптимума не более чем в 4/3 раза. Для нелинейных функций граница хуже и может быть неограниченной. Оценки на реальных городских сетях — Youn, Gastner, Jeong, «Price of Anarchy in Transportation Networks», PRL 101, 2008 — дают потери порядка 5–30 % в зависимости от города и часа.

Инженерный аналог прямой: добавили в кластер быструю реплику — балансировщик начал слать на неё больше запросов — она стала горлышком для нового класса запросов — сквозная латентность выросла. Или: добавили быстрый путь в обход очереди — трафик перетёк туда — быстрый путь перестал быть быстрым. Что здесь проверяемо: утверждение «добавление ёмкости не обязано улучшить сквозную метрику, потому что маршрутизация адаптивна» — да. Утверждение «наш конкретный инцидент был парадоксом Браесса» — нет, пока вы не показали, что маршрутизация перераспределилась, и не измерили обе конфигурации. Городские «подтверждения» парадокса (закрытие 42-й улицы в Нью-Йорке, снос эстакады Чхонгечхон в Сеуле) — наблюдательные данные без контроля, в них смешаны наведённый спрос, изменения общественного транспорта и сезонность. Как красивая иллюстрация они работают, как доказательство — нет.

Индуцированный спрос и эффект Джевонса

Сделали часть системы дешевле — потребление этой части выросло, и совокупный расход не упал. Уильям Джевонс описал это для угля в 1865 году в «The Coal Question».

Инженерные проявления встречаются постоянно:

  • Ускорили сборку с 20 минут до 4 — разработчики стали пушить в пять раз чаще, очередь на раннеры не сократилась, счёт за CI вырос.
  • Дали командам возможность создавать сервисы за один клик — число сервисов утроилось, эксплуатационная нагрузка на платформенную команду выросла быстрее, чем упала стоимость одного сервиса.
  • Добавили кэш, снизили нагрузку на базу — продукт разрешил более тяжёлые фильтры, нагрузка вернулась к прежней, но теперь с кэшем в критическом пути.

Это не аргумент против ускорения сборки. Это аргумент за то, чтобы предсказывать реакцию спроса до изменения и измерять её после: «ожидаем рост числа сборок на 2–4x в течение месяца; если раннеров хватит, выигрыш сохранится, иначе понадобится +N раннеров».

Механизм переноса: как локальное улучшение приезжает к соседу

Самая частая инженерная форма — не «ускорили не то», а «переложили».

Читается так. Команда клиента снижает таймаут с 2 с до 200 мс и добавляет два повтора: их p99 по успешным ответам действительно улучшается, потому что долгие ответы теперь отбрасываются и заменяются повторами. Локальная метрика — вниз. Но каждый отброшенный запрос продолжает выполняться на стороне получателя: работа сделана, результат выкинут. При двух повторах и трёх уровнях вложенности вызовов множитель нагрузки на нижний слой достигает (1 + 2)³ = 27.

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

  • Бюджет повторов вместо «повторить N раз»: клиент не отправляет больше, чем 10 % повторов от базового потока. Описано в Google SRE Book, глава Handling Overload.
  • Экспоненциальная задержка с джиттеромTimeouts, retries and backoff with jitter, AWS Builders’ Library.
  • Отмена работы на стороне сервера при отмене клиентом (дедлайны, распространяемые по цепочке) — иначе выброшенные ответы продолжают потреблять ограничение.
  • Идемпотентность — чтобы повтор не создавал дубль заказа; см. идемпотентность и доставку.
  • Сброс нагрузки на входе, а не в глубине: Using load shedding to avoid overload. Паттерны — в устойчивости.

Метрика-заместитель: где локальная оптимизация начинается

Почти всегда локальная оптимизация начинается с введения метрики, которая заместила цель. Это закон Гудхарта в инженерной форме.

Оригинал Чарльза Гудхарта (1975) касался денежной политики; общеизвестная формулировка принадлежит Мэрилин Стрэтерн (1997): «когда мера становится целью, она перестаёт быть хорошей мерой». Независимо и раньше то же сформулировал Дональд Кэмпбелл (1979): чем сильнее количественный показатель используется для принятия решений, тем больше он подвержен искажению и тем сильнее искажает процесс, который призван измерять. Полезная систематизация механизмов — Manheim, Garrabrant, «Categorizing Variants of Goodhart’s Law» (2018): регрессионный, экстремальный, причинный и состязательный варианты — это разные явления с разными лечениями.

Как это выглядит в инженерии:

Метрика-заместитель Что оптимизируют на самом деле Побочный эффект
покрытие тестами, % число строк, задетых тестом тесты без ассертов, покрытие геттеров
число закрытых тикетов дробление задач, закрытие как «не воспроизводится» реальные дефекты живут дольше
p99 по успешным ответам отбрасывание медленных запросов нагрузка на соседа, дубли, ретраи
MTTR быстрое «закрыл инцидент», не устранив причину повторные инциденты, рост числа обходных решений
velocity в story points инфляция оценок оценки перестают быть оценками
число алертов у команды перевод алертов на другую команду или в почту сигнал теряется, инциденты дольше не замечают
uptime сервиса «жив, но ничего полезного не делает» зелёный дашборд при сломанном продукте

Ни одна из этих метрик не плохая сама по себе — плохой становится конструкция «метрика + давление + отсутствие парного ограничения». Три работающих приёма:

  1. Парные метрики (guardrails). К каждой метрике скорости — метрика качества, которую нельзя ухудшать. Так устроены DORA-метрики: частота деплоя и время выполнения изменения парны к доле неудачных изменений и времени восстановления. Оптимизировать первые в ущерб вторым бессмысленно, потому что смотрят на все четыре сразу. Разбор — в DORA и инженерных метриках и в метриках продукта.
  2. Сквозная метрика, определённая на границе пользователя. Не «p99 сервиса», а «доля заказов, оформленных за 3 с». Одна на поток ценности, а не по одной на команду.
  3. Метрика с явным сроком годности. «Мы измеряем X до конца квартала, потому что проверяем гипотезу Y; после проверки метрика снимается». Метрики без срока годности живут вечно и обрастают ритуалами.

Стоит честно сказать про доказательную базу: литература по искажению целей — в основном наблюдательная и кейсовая. Ordóñez, Schweitzer, Galinsky, Bazerman, «Goals Gone Wild» (2009) собрали побочные эффекты целеполагания — и получили развёрнутое возражение от Локка и Лэтэма в том же выпуске, которые указали, что эффекты воспроизводятся в лабораторных условиях с искусственными стимулами. Джерри Мюллер в «The Tyranny of Metrics» (2018) собрал сильные кейсы (медицинские рейтинги, полицейская статистика, школьные тесты), но это именно кейсы. Крупные корпоративные скандалы вроде квот на кросс-продажи в Wells Fargo (2016) — тоже единичные наблюдения. Механизм правдоподобен и хорошо согласуется со здравым смыслом; строгого количественного закона тут нет, и выдавать эвристику за закон не надо.

Ловушки, которые на самом деле являются локальной оптимизацией

Каталог системных архетипов с карточками и сигнатурами — в отдельной главе; пересказывать его здесь незачем. Полезно другое: увидеть, что часть каталога — это один и тот же механизм в разных декорациях, и что у каждого случая есть конкретная локальная метрика, ради которой всё и делается.

Ловушка Локальная метрика, которую улучшают Что переносится и куда Чем проверить за один день
Оптимизация не-узкого места время своей стадии ничего; запас переезжает ближе к ограничению доля f в сквозном профиле, потолок по Амдалу
Перенос проблемы, зависимость от обхода число открытых инцидентов, MTTR боль уходит в будущее, растёт зависимая от обхода инфраструктура доля инцидентов, закрытых обходом, за 90 дней
Трагедия общего ресурса своя латентность, свой throughput деградация делится на всех потребителей ресурса атрибуция потребления общего ресурса по клиентам
Эскалация своя метрика относительно чужой стоимость ответного хода — соседу цепочка изменений «ход — ответный ход» в истории релизов
Успех успешному сравнимость команд по дашбордам ресурсы уходят от тех, кто держит ограничение ранговая таблица «ресурсы за год» против «вклад в сквозную»

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

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

Трагедия общего ресурса — единственная ловушка, где полезно знать не только исходную работу, но и возражение на неё. Гаррет Хардин, «The Tragedy of the Commons» (Science, 1968), описал механизм; Элинор Остром в «Governing the Commons» (1990, Нобелевская премия 2009) на десятках реальных сообществ эмпирически показала, что «трагедия» не неизбежна: при наблюдаемости потребления, чётких границах и локальных правилах общий ресурс управляется устойчиво десятилетиями. Инженерный вывод ровно этот — лечится не призывами, а атрибуцией потребления и квотами: лимиты конкурентности на клиента, отдельные пулы (bulkhead), приоритеты трафика, счёт за потребление, видимость «кто съел ресурс» в реальном времени.

Как это разворачивается во времени

Локальная оптимизация редко бывает разовым событием — обычно это последовательность с предсказуемыми стадиями.

Практическое следствие: переход S1 → S2 надо ловить явно. Это момент, когда команда честно говорит «дальше только за счёт кого-то», и это нормальный, ожидаемый исход, а не признак слабости. Если организация не умеет принимать такой ответ, она гарантированно получает S3.

Когда локальная оптимизация безопасна

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

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

Три условия безопасности, каждое проверяемо:

  1. Изолированность по границе. Изменение не меняет потоки, пересекающие границу подсистемы, — ни объём, ни момент, ни состав, ни потребление общих ресурсов. Внутренняя переработка кода без изменения контракта и профиля нагрузки почти всегда сюда попадает.
  2. Отсутствие напряжённого общего ресурса. Если ресурс загружен на 30 %, ваша дополнительная доля почти ничего не меняет. Если на 85 % — вы находитесь за коленом кривой, и любое перераспределение бьёт по всем.
  3. Монотонность связи в рабочем диапазоне. Измерено, а не предположено: у вас есть наблюдения, где локальная метрика менялась, и вы видели соответствующее движение сквозной.

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

# Пример пары «локальная цель + ограничитель» в Prometheus.
# Локальная метрика оптимизируется, guardrail запрещает переложить проблему на соседа.
groups:
  - name: local-optimization-guardrails
    rules:
      # то, что улучшает команда: время своей стадии
      - record: checkout:stage_latency:p99
        expr: histogram_quantile(0.99, sum by (le) (rate(checkout_stage_seconds_bucket[5m])))

      # сквозная метрика того же потока — определена на границе пользователя
      - record: checkout:end_to_end:p99
        expr: histogram_quantile(0.99, sum by (le) (rate(order_total_seconds_bucket[5m])))

      # guardrail 1: нагрузка, которую мы создаём на общий ресурс
      - record: checkout:db_calls_per_order
        expr: rate(db_queries_total{caller="checkout"}[5m]) / rate(orders_started_total[5m])

      - alert: LocalWinGlobalLoss
        # локальная метрика улучшилась более чем на 20 % за неделю,
        # а сквозная за то же время ухудшилась — сигнал переноса проблемы
        expr: |
          (checkout:stage_latency:p99 < 0.8 * (checkout:stage_latency:p99 offset 7d))
          and
          (checkout:end_to_end:p99 > 1.05 * (checkout:end_to_end:p99 offset 7d))          
        for: 6h
        annotations:
          summary: "Стадия ускорилась, сквозной путь замедлился — проверьте, куда переехала работа"

      - alert: HiddenAmplification
        expr: checkout:db_calls_per_order > 1.5 * (checkout:db_calls_per_order offset 7d)
        for: 30m
        annotations:
          summary: "Вырос множитель обращений к общей базе на один заказ"

Алерт LocalWinGlobalLoss — не детектор истины, а детектор ситуации, где стоит посмотреть: он даёт ложные срабатывания при сезонности и смене состава трафика, поэтому ставить его страничным не надо, а тикетом в очередь разбора — полезно.

Пределы метода

Здесь нужно быть предельно честным, иначе глава превратится в набор красивых схем.

Причинные диаграммы не предсказывают — они организуют гипотезы. Схема с петлями и знаками — это компактная запись предположений о структуре. Она не содержит величин, задержек и функциональных форм, поэтому из неё нельзя вывести, вырастет очередь на 10 % или в 10 раз, через час или через квартал. Диаграмма, которая ничего не запрещает, ничего и не утверждает. Подробнее — в главах о диаграммах причинных связей и моделировании и его пределах.

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

  • измеримая сквозная величина с определением («доля заказов, оформленных за 3 с, по всем регионам»);
  • знак и порядок эффекта («вырастет на 5–15 %, не на 0,5 % и не в два раза»);
  • окно наблюдения («эффект проявится за 2–6 недель — это время оборота очереди»);
  • механизм, который можно разорвать («если отключить агрессивный таймаут, эффект исчезнет»);
  • заранее записанное опровергающее наблюдение («если после отключения сквозная не изменится за 2 недели — версия неверна»).

Если пятого пункта нет — у вас не модель, а нарратив.

Что здесь имеет какой статус

Утверждение Статус Чем проверяется
Пропускная способность последовательного конвейера равна минимальной ёмкости арифметика прямым расчётом и симуляцией
Закон Амдала ограничивает сквозной выигрыш теорема измерением доли f в профиле
Закон Литтла связывает запас, поток и время теорема при выполнении условий тремя независимыми замерами
Ожидание растёт как ρ/(1−ρ) асимптотическое приближение нагрузочным тестом на своей системе
Добавление ёмкости может ухудшить равновесие (Браесс) теорема о модели сравнением конфигураций A/B
Метрика под давлением искажается (Гудхарт, Кэмпбелл) эмпирическая закономерность, кейсы наблюдением за поведением после введения цели
Ускорение части провоцирует рост спроса (Джевонс) эмпирическая закономерность измерением спроса до и после
«У нас конверсия упала из-за оптимизации корзины» гипотеза откатом изменения или экспериментом
«Локальная оптимизация — главная проблема нашей компании» нарратив ничем; переформулируйте в проверяемое

Про первоисточники — без благоговения

Системная динамика как дисциплина выросла из работ Джея Форрестера (MIT, 1950–60-е). Его модели дали язык — запасы, потоки, петли, задержки, — но конкретные модели критиковались по существу. «Urban Dynamics» (1969) разбирали Gray, Pessel и Varaiya в критическом анализе (IEEE Trans. SMC, 1972): выводы модели оказались чувствительны к произвольно выбранным параметрам. «Пределы роста» (1972) получили жёсткую рецензию Уильяма Нордхауса — «World Dynamics: Measurement Without Data» (Economic Journal, 1973): претензия ровно в названии — структура модели богатая, эмпирическая калибровка слабая. Позднее Грэм Тёрнер сопоставил сценарии с фактическими данными за 30 лет (Global Environmental Change, 2008) и нашёл согласие со сценарием «business as usual» — но сопоставление тренда с трендом на горизонте в 30 лет не является сильной проверкой модели, и это признаёт сам автор.

Вывод для инженера не «Форрестер и Медоуз ошибались», а более полезный: язык системной динамики ценен, конкретная модель ценна ровно настолько, насколько она откалибрована и проверена на данных. Джон Стерман, главный современный систематизатор дисциплины, посвятил этому статью с говорящим названием «All models are wrong: reflections on becoming a systems scientist» (System Dynamics Review, 2002). Его же «Business Dynamics» (2000) — учебник, где половина объёма посвящена процедурам валидации модели, а не рисованию петель.

То же касается теории ограничений. Голдратт дал сильную операционную эвристику, но «The Goal» — производственный роман, а не исследование; сравнительная эмпирика по внедрениям ToC разрозненна и страдает от публикационного смещения (об успешных внедрениях пишут, о неуспешных — нет). Пользуйтесь механикой узкого места — она арифметична; относитесь к обещаниям кратного роста как к маркетингу.

Практика: 45 минут перед тем, как коммитить улучшение

Возьмите ближайшее планируемое улучшение — своё, не чужое.

  1. (5 мин) Запишите локальную метрику и ожидаемое изменение. Число со знаком: «время стадии сборки образа: 210 с → 70 с».
  2. (10 мин) Найдите сквозную метрику того же потока. Определённую на границе пользователя или бизнеса. Если её не существует — остановитесь: это и есть главная находка упражнения, и её надо чинить первой.
  3. (10 мин) Посчитайте потолок по Амдалу и реакцию спроса. Доля компонента в сквозном времени даёт максимум выигрыша; если меньше 3 % — проговорите вслух, зачем вы это делаете (бывают честные ответы: стоимость, читаемость, риск). Отдельно: если этим начнут пользоваться в N раз чаще — что станет ограничением?
  4. (10 мин) Перечислите потоки через границу. Что вы начнёте отправлять чаще, крупнее, раньше или позже? Какой общий ресурс начнёте потреблять сильнее? Кто это заметит и через сколько?
  5. (5 мин) Выберите один guardrail. Метрику, которую вы обязуетесь не ухудшить, с порогом. Не три — одну, иначе не будете следить.
  6. (5 мин) Запишите опровергающее наблюдение и дату проверки. «Через 4 недели смотрю сквозной p99 и число сборок; если сквозная не улучшилась, а сборок стало вдвое больше — гипотеза о выигрыше неверна, разбираемся». Пункты 2 и 6 — те, ради которых упражнение существует; остальные помогают их заполнить.

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

Использовать системный аргумент как вето. «Это локальная оптимизация» стало универсальным способом заблокировать любое изменение, не предъявляя механизма. Требуйте от себя и других конкретики: какой поток через границу изменится, какой общий ресурс пострадает, какое наблюдение это подтвердит.

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

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

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

Искать виноватого и строить карту вместо эксперимента. Локальная оптимизация — свойство структуры измерений и обратных связей: замена людей при неизменной структуре воспроизводит то же поведение (о том, почему участники искренне видят разные системы, — глава о ментальных моделях). А схема на 30 узлов выглядит убедительно и не проверяется — одна петля из четырёх узлов с числами и предсказанием полезнее.

Мини-итог

  • Локальная оптимизация — это максимизация f_i там, где F не монотонна по f_i. Это свойство структуры, а не мотивации людей.
  • Три структурные причины: граница измерения не совпадает с границей потока ценности; обратная связь от целого запаздывает или разомкнута; ресурс общий, а метрика частная.
  • Пропускная способность конвейера равна минимальной ёмкости. Ускорение не-узкого места не меняет ни выход, ни суммарный запас, ни сквозное время — оно перемещает запас за спину ускоренного этапа, туда, где работу уже не отменить и где буфер конечен.
  • Закон Амдала даёт потолок сквозного выигрыша до начала работы. Одна строка расчёта отсекает часть заведомо невидимой работы.
  • Локальная утилизация — опасная цель: множитель ρ/(1−ρ) превращает «повысим загрузку с 80 % до 95 %» в пятикратный рост ожидания.
  • Добавление ёмкости не обязано улучшать сквозную метрику: при адаптивной маршрутизации возможен эффект Браесса, при эластичном спросе — эффект Джевонса.
  • Самая частая инженерная форма — перенос нагрузки: агрессивный таймаут улучшает p99 по успешным ответам и умножает нагрузку на соседа как (1 + r)^k.
  • Метрика-заместитель под давлением искажается (Гудхарт, Кэмпбелл). Лечение — парные метрики, сквозная цель, срок годности метрики.
  • Локальная оптимизация безопасна при трёх условиях: потоки через границу не меняются, общий ресурс не напряжён, связь со сквозной метрикой измерена в рабочем диапазоне.
  • Причинная схема не предсказывает. Гипотеза требует сквозной величины, знака и порядка эффекта, окна наблюдения, разрываемого механизма и заранее записанного опровергающего наблюдения.
  • Классики дали язык, а не готовые ответы: модели Форрестера и «Пределы роста» разбирались десятилетиями, и главный урок разбора — про калибровку и валидацию, а не про красоту петель.

Источники

Что дальше

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

Точки воздействия: где вмешательство даёт результат

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

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

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

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