Системное мышление Эмерджентность: свойства целого, которых нет у частей
0%

Эмерджентность: свойства целого, которых нет у частей

Эмерджентность: свойства целого, которых нет у частей

Дежурного будят в 03:40. Он открывает дашборд и видит картину, которая встречается чаще, чем хотелось бы: все компоненты в норме, продукт не работает. Каждый из ста узлов отвечает быстрее своего SLO. Ни одна база не перегружена. Ни один под не в CrashLoopBackOff. При этом пользовательский p99 — восемь секунд, конверсия просела вдвое, а поддержка уже собирает жалобы.

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

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

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

Минимальное определение

Эмерджентное свойство — свойство системы, которое:

  1. не определено для отдельной части — вопрос «какая пропускная способность у одного болта» бессмыслен, а «какая пропускная способность у конвейера» — осмыслен;
  2. порождается взаимодействием частей, а не их индивидуальными характеристиками;
  3. не получается простым агрегированием — суммой, средним или максимумом свойств частей.

Все три условия обязательны. Уберите первое — получите обычную аддитивную величину. Уберите второе — получите совпадение. Уберите третье — получите арифметику, а не эмерджентность.

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

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

Три теста: эмерджентность или просто сумма

Прежде чем произносить слово, прогоните свойство через три вопроса. Они дешёвые и отсеивают 80% ложных срабатываний.

Тест 1. Выразимо ли свойство для одной части

Возьмите одну часть и попробуйте задать вопрос про неё. «Какой у этого узла throughput?» — осмысленно. «Какая у этого узла консистентность реплик?» — бессмысленно: консистентность определена для множества реплик. «Сколько у этого потока взаимных блокировок?» — бессмысленно: взаимная блокировка определена для набора потоков и набора ресурсов.

Если вопрос не формулируется для части — это кандидат в эмерджентные свойства.

Тест 2. Переживает ли свойство декомпозицию

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

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

Тест 3. Есть ли короткий путь к ответу

Самый содержательный тест. Спросите: можно ли предсказать свойство целого формулой, или единственный способ — прогнать динамику шаг за шагом?

  • Общий вес сервера = сумма весов деталей. Формула есть, эмерджентности нет.
  • Вероятность, что запрос с fan-out на 100 узлов попадёт в чей-то хвост, = $1 - (1-p)^{100}$. Формула есть, но она не сумма и не среднее: свойство есть у запроса и отсутствует у узла. Это эмерджентность с аналитическим решением — самый удобный случай.
  • Момент, когда сервис перейдёт в метастабильный отказ при данном профиле нагрузки, аналитически не считается: нужна симуляция или эксперимент. Это эмерджентность без короткого пути.

Марк Бедо предложил называть третий случай слабой эмерджентностью: свойство выводимо из правил взаимодействия, но только «прогоном» — никакого сокращения не существует (M. Bedau, «Weak Emergence», Philosophical Perspectives 11, 1997). Для инженера это принципиальная граница: слабо эмерджентное свойство нельзя доказать чтением кода, его можно только воспроизвести.

Три градации, из которых инженеру нужны две

Градация Что означает Пример Инженерная ценность
Номинальная свойство просто не определено для части «пропускная способность конвейера», «форма молекулы» нулевая: это про словарь, не про поведение
Слабая выводима из взаимодействий, но только симуляцией/экспериментом метастабильный отказ, фантомная пробка, самосинхронизация клиентов максимальная: почти вся практика тут
Сильная принципиально невыводима из свойств частей ни при каком вычислении предмет спора в философии сознания нулевая: неопровержимо, значит бесполезно

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

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

Откуда берётся: три ингредиента

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

  1. Много элементов — достаточно, чтобы взаимодействий было больше, чем элементов. При $n$ элементах потенциальных парных связей $n(n-1)/2$: рост числа связей квадратичный, и с какого-то момента поведение определяется именно ими.
  2. Взаимодействие через ограниченный или общий ресурс — сеть, пул соединений, диск, лок, кэш, внимание дежурного, время код-ревью. Именно общий ресурс делает элементы зависимыми и превращает независимость в корреляцию.
  3. Обратная связь с задержкой — реакция элемента на состояние целого возвращается в целое. Без петли получается статистика; с петлёй — динамика. Про задержку как отдельный источник сюрпризов — глава 05.

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

Считаемая эмерджентность: хвост латентности при fan-out

Начнём с самого удобного случая — свойство целого, которого нет у частей, но которое считается на салфетке.

Запрос пользователя раскладывается на $k$ обращений к независимым узлам (шарды, реплики, микросервисы, партиции индекса). Ответ отдаётся, когда вернулись все. Пусть каждый узел с вероятностью $p$ отвечает медленно — попадает в собственный честный хвост.

$$P(\text{запрос медленный}) = 1 - (1-p)^{k}$$

Хвост латентности при fan-out: свойство запроса, а не узла

При $p = 0.01$ (то есть у узла ровно p99, всё «в рамках SLO»):

$k$ — узлов в запросе 1 10 20 50 100 200
Доля медленных запросов 1% 9.6% 18.2% 39.5% 63.4% 86.6%

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

$$k_{1/2} = \frac{\ln 2}{-\ln(1-p)} \approx \frac{0.693}{p}$$

Для $p = 0.01$ это $k \approx 69$. Не сотни тысяч, не «когда-нибудь при масштабировании» — семьдесят узлов. Типичная страница крупного сервиса собирается из большего числа обращений.

Что здесь важно методологически:

  • Свойство «запрос медленный» не определено для узла (тест 1). Узел не знает, в каком запросе он участвует.
  • Оно исчезает при декомпозиции (тест 2): померьте узлы по отдельности — все зелёные.
  • У него есть аналитическая формула (тест 3), то есть это самый мягкий вид эмерджентности. Именно поэтому с ним умеют работать: сокращение $k$, hedged requests, «ответ по кворуму вместо ответа всех» — стандартный набор из «The Tail at Scale» (J. Dean, L. A. Barroso, CACM 56(2), 2013).

Немедленное следствие для SLO. Требование «p99 каждого сервиса ≤ 100 мс» не даёт «p99 запроса ≤ 100 мс» — оно даёт нечто на порядок хуже, и разрыв растёт с $k$. Бюджет надёжности надо резать сверху вниз: сначала цель для пользовательского пути, потом её распределение по узлам с учётом $k$. Обратный порядок — самая частая причина того, что «все команды выполняют SLO, а продукт не работает». Это же и первый пример локальной оптимизации, которой посвящена глава 10.

Проверяемая модель на сорок строк

Формула выше предполагает независимость узлов. Это допущение, и его надо либо проверить, либо честно пометить. Симуляция полезна тем, что позволяет посчитать не только вероятность, но и распределение — и сравнить стратегии.

import random
import statistics

def node_latency(rng: random.Random) -> float:
    """Латентность одного узла, мс. Бимодально: быстрый режим и честный хвост."""
    if rng.random() < 0.01:          # 1% запросов — хвост (GC, промах кэша, ретрай диска)
        return 100.0 + rng.expovariate(1 / 50.0)
    return rng.expovariate(1 / 5.0)   # обычный режим, среднее 5 мс

def plain(rng: random.Random, k: int) -> float:
    """Fan-out на k узлов, ждём всех: латентность запроса — максимум по узлам."""
    return max(node_latency(rng) for _ in range(k))

def hedged(rng: random.Random, k: int, tie: float = 20.0) -> float:
    """То же, но через tie мс отправляем дубль и берём первый вернувшийся ответ."""
    def one() -> float:
        first = node_latency(rng)
        if first <= tie:
            return first
        return min(first, tie + node_latency(rng))
    return max(one() for _ in range(k))

def percentiles(samples: list[float]) -> tuple[float, float]:
    s = sorted(samples)
    return s[len(s) // 2], s[int(len(s) * 0.99)]

rng = random.Random(42)
N = 20_000
for k in (1, 10, 100):
    p50_a, p99_a = percentiles([plain(rng, k) for _ in range(N)])
    p50_b, p99_b = percentiles([hedged(rng, k) for _ in range(N)])
    print(f"k={k:>3}  без хеджа: p50={p50_a:6.1f} p99={p99_a:7.1f} | "
          f"с хеджем: p50={p50_b:6.1f} p99={p99_b:7.1f}")

Сложность: $O(N \cdot k)$ по времени и $O(N)$ по памяти на выборку. При N = 20000, k = 100 это пара секунд на ноутбуке.

Что модель показывает надёжно: знак и порядок величины. На этих параметрах при $k = 100$ уже медианный запрос медленнее, чем p99 отдельного узла (около 118 мс против примерно 100 мс), а p99 запроса уходит за 300 мс. Хеджирование срезает хвост втрое-впятеро ценой роста нагрузки: дубль уходит примерно на 3% обращений, и при $k = 100$ это не «пренебрежимо мало», а заметная добавка к трафику на все узлы сразу.

Что она не показывает: реальные числа вашей системы. Распределение node_latency придумано. Если у ваших узлов общий источник задержки — один хост, одна сеть, одновременный GC, одна дисковая полка, — независимость нарушена, и настоящий хвост может быть как хуже (задержки синхронизированы), так и лучше (медленный узел один и тот же, его легко вывести). Именно поэтому следующий шаг — не «поверить симуляции», а достать распределённую трассировку и посмотреть, коррелируют ли медленные узлы внутри одного запроса. Инструментарий — в «Наблюдаемости распределённых систем»; как вообще корректно мерить перцентили — в «Измерении производительности».

Взаимная блокировка: состояние, которого нет ни у одной части

Второй канонический пример — и он ещё чище, потому что здесь нет никакой статистики.

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

Комбинация A3 × B3 — взаимная блокировка. Обратите внимание: каждый поток находится в совершенно нормальном состоянии. Ни один не нарушил протокол, ни один не содержит бага в изоляции. Прочитайте код потока A целиком — вы ничего не найдёте. Прочитайте код потока B — тоже. Дефект живёт в порядке захвата, то есть в отношении между двумя фрагментами кода, которые могут находиться в разных файлах, разных модулях и написаны разными людьми в разные годы.

Классические четыре условия взаимной блокировки (взаимное исключение, удержание с ожиданием, отсутствие вытеснения, циклическое ожидание) сформулированы Коффманом, Элфиком и Шошани в 1971 году, и последнее из них — свойство графа, а не вершины. Отсюда единственное надёжное лечение: не «проверить каждый лок», а навязать глобальный порядок захвата и проверять его инструментами уровня системы (lockdep в ядре Linux, ThreadSanitizer, статические анализаторы порядка). Подробнее про механику конкурентности — в главе о производительности конкурентного кода.

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

Синхронизация из ничего: стадо, которое никто не собирал

Третий класс — самый контринтуитивный. Здесь эмерджентным свойством оказывается не «сколько», а «насколько согласованно».

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

Механизм — усиливающая петля по корреляции, а не по объёму:

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

Как клиенты выравниваются:

  • Общее событие. Деплой, рестарт кластера, истечение единого TTL, полночь по UTC, пятиминутка cron. После общего события все таймеры стартуют в одну секунду и остаются в фазе.
  • Общая реакция. Сервис замедлился → у всех клиентов сработал таймаут → все повторили одновременно. Задержка сама становится синхронизирующим сигналом.
  • Отсутствие джиттера. Ровный период — это не «аккуратно», это условие синхронизации. Классическая работа S. Floyd, V. Jacobson, «The Synchronization of Periodic Routing Messages» (SIGCOMM 1993) показала это на маршрутизаторах с измерениями и моделью: периодические сообщения самопроизвольно входят в фазу, и достаточно добавить случайный разброс порядка периода, чтобы синхронизация не возникала. Это редкий случай, когда эмерджентное свойство и его лечение проверены прямым экспериментом.

Тот же механизм в частном случае кэша — «стадо» (thundering herd / cache stampede):

Лечится структурно, а не наращиванием ёмкости: джиттер к TTL и к периодам опроса, единая блокировка на пересчёт (single-flight / request coalescing), заблаговременное обновление «горячих» ключей, бюджет ретраев с экспоненциальной задержкой и обязательным случайным разбросом. Разбор механик кэша — в «Кэшировании», паттерны устойчивости — в «Resilience patterns»; практическая сводка по ретраям — «Timeouts, retries, and backoff with jitter» в AWS Builders’ Library.

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

Ещё пять эмерджентных свойств, которые вы уже видели

Метастабильный отказ. Система работала годами, всплеск на 40 секунд загнал её в состояние, из которого она не выходит даже после того, как нагрузка вернулась к норме. Эмерджентно здесь само состояние: у отдельного узла нет режима «не восстанавливается», он есть только у контура «нагрузка → отказы → ретраи → нагрузка». Полезно, что это не эссеистика: есть систематизация (N. Bronson et al., «Metastable Failures in Distributed Systems», HotOS 2021) и разбор случаев из продакшна крупных компаний (L. Huang et al., «Metastable Failures in the Wild», OSDI 2022). Динамика самой петли разобрана в главе 03.

Пробуксовка (thrashing). Ни один процесс не запрашивает лишнего, но совокупный рабочий набор перестал помещаться в память — и система тратит почти всё время на подкачку страниц вместо полезной работы. Свойство «доля полезной работы» определено для целого, а не для процесса. Явление описано Питером Деннингом ещё в 1968 году и с тех пор воспроизводится на всём подряд: swap, GC, кэш процессора, пул соединений к базе. Механика памяти — в «Памяти».

Фантомная пробка. Машины стоят, аварии нет, сужения нет. В 2008 году это воспроизвели экспериментально: 22 автомобиля пустили по кольцу в 230 метров с просьбой держать постоянную скорость — примерно через минуту самопроизвольно возникла и поехала назад волна остановки (Y. Sugiyama et al., New Journal of Physics 10:033001, 2008). Ценность примера в том, что это эксперимент с данными, а не мысленная картинка: пробка без препятствия — измеренный факт, а не метафора. Инженерный аналог — волна замедления в конвейере обработки, которая идёт против потока и живёт дольше вызвавшего её всплеска.

Разбухшие буферы (bufferbloat). Каждый производитель добавил «щедрый» буфер, чтобы не терять пакеты. Локально — разумно. Глобально — сломан механизм обратной связи TCP, который узнаёт о перегрузке именно по потерям: задержки выросли на порядки, интерактивность исчезла. Разбор — J. Gettys, K. Nichols, «Bufferbloat: Dark Buffers in the Internet», ACM Queue 9(11), 2011. Это чистейший случай «локально верное решение ломает свойство целого»; сетевая механика — в «Производительности сети».

Перекос нагрузки (hot shard). Ключи распределены равномерно «в среднем», каждая партиция настроена одинаково, а одна из них берёт 40% трафика. Свойство «перекос» определено для распределения по множеству партиций, а не для партиции. Причём перекос часто порождается не данными, а поведением клиентов — то есть возникает во взаимодействии системы с её окружением. Про шардирование — «Партиционирование».

Эмерджентность в организации: то же самое, только медленнее

Инженерные и организационные системы здесь устроены одинаково — меняется только постоянная времени.

Пропускная способность команды — не сумма производительностей инженеров. Она определяется координационными издержками: ревью, согласования, ожидание ответа, переключения контекста. Число каналов взаимодействия растёт как $n(n-1)/2$, и с какого-то момента добавление человека уменьшает выпуск. Классическая формулировка — у Фредерика Брукса («The Mythical Man-Month», 1975); важная оговорка: у Брукса это обобщение опыта разработки OS/360, а не контролируемый эксперимент, и относиться к нему стоит как к хорошо подтверждённой практикой эвристике, а не к закону. Практические следствия — в «Планировании и оценках» и «Team Topologies».

Закон Конвея. Архитектура системы воспроизводит структуру коммуникации организации. Источник — эссе M. Conway, «How Do Committees Invent?», Datamation, 1968. Честно про статус: сам текст — рассуждение с примерами, не исследование. Эмпирическая проверка появилась гораздо позже — гипотеза «зеркалирования» проверялась на парах продуктов с разной организационной структурой и в целом подтвердилась (A. MacCormack, J. Rusnak, C. Baldwin, «Exploring the Duality Between Product and Organizational Architectures», Research Policy 41(8), 2012), но выборки невелики, а причинность двусторонняя: не только структура влияет на архитектуру, но и архитектура закрепляет структуру. Утверждение уровня «сильная корреляция, механизм правдоподобен», а не «доказанный закон». Архитектурные следствия — в «Микросервисах».

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

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

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

Как проверить эмерджентную гипотезу

Утверждение «у нас эмерджентное поведение» становится инженерным, когда выполнены три условия.

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

2. Есть предсказание, отличающееся от аддитивного. Модель обязана говорить нечто, чего не даёт наивная сумма: «при $k > 70$ больше половины запросов будут ловить чей-то хвост», «после добавления джиттера в TTL пиковая нагрузка упадёт втрое при неизменной средней», «после снижения числа ретраев с 3 до 1 система перестанет залипать после всплеска». Число, знак, порядок величины — что-то, что может не сбыться.

3. Есть эксперимент, который может это опровергнуть. Нагрузочный тест с реалистичным профилем, chaos-эксперимент, game day, A/B на части трафика, откат одного параметра. Если ни один мыслимый эксперимент не различает вашу гипотезу и «просто перегрузка» — это не модель, а рассказ.

Правый верхний угол — то, что можно посчитать до обеда. Левый нижний — то, о чём стоит говорить осторожно и с прямой пометкой «гипотеза». Движение по диаграмме — всегда сначала влево-вправо (завести метрику), потом вверх (найти модель).

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

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

Здесь метод ломается чаще, чем в любой другой главе трека, — потому что понятие красивое и заманчиво объясняет всё.

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

Симуляция доказывает достаточность, а не необходимость. Модель воспроизвела наблюдаемое поведение — это значит «такой механизм мог бы дать такой результат», а не «в вашей системе работает именно он». Модель сегрегации Шеллинга (T. Schelling, «Dynamic Models of Segregation», Journal of Mathematical Sociology 1(2), 1971) показывает, что даже слабые индивидуальные предпочтения способны дать резкое разделение — это ценное опровержение интуиции «раз результат сильный, значит и намерения сильные». Но она не свидетельство о том, чем вызвана сегрегация в конкретном городе. Ровно та же оговорка к «боидам» Крейга Рейнольдса (SIGGRAPH 1987): три локальных правила дают убедительную стаю, из чего не следует, что настоящие птицы следуют этим правилам. Для инженера правило простое: симуляция — генератор гипотез, эксперимент — их судья.

Метрика может создать «скачок» там, где его нет. Это не теория: в 2022 году были описаны «эмерджентные способности» больших языковых моделей — резкое появление умений при росте масштаба (J. Wei et al., «Emergent Abilities of Large Language Models», TMLR 2022). В 2023-м вышла работа, показавшая, что значительная часть таких скачков объясняется выбором разрывной метрики (точное совпадение строки, «всё или ничего»): при переходе к непрерывной метрике на тех же данных кривая становится гладкой (R. Schaeffer, B. Miranda, S. Koyejo, «Are Emergent Abilities of Large Language Models a Mirage?», NeurIPS 2023). Спор не закрыт, и это его правильный статус. Но урок переносится дословно: прежде чем объявлять скачок свойством системы, проверьте, не создан ли он вашей шкалой — порогом алерта, бинаризацией «успех/провал», агрегированием по слишком грубым бакетам. Дискуссия о самих моделях — в главе про LLM.

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

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

Модель эмерджентности плохо предсказывает амплитуду и сроки. Как и вся системная динамика: знак, механизм и порядок величины — надёжно; точное значение и точный момент — нет. Никакая модель не скажет вам, что метастабильный отказ случится во вторник в 03:40. Она скажет, что при таком коэффициенте петли и таком бюджете ретраев состояние достижимо, — и этого достаточно, чтобы действовать. Разбор границ моделирования — в главе 13.

Честно про доказательную базу

Понятие эмерджентности старше системной динамики и заметно надёжнее её самой популярной части — но опирается на очень разнородные основания. Разложим по уровням доверия.

Крепко стоит. Эмерджентный хвост при fan-out — прямое следствие теории вероятностей плюс подтверждённая практика индустрии. Взаимная блокировка — формальный результат, а не наблюдение. Пробуксовка воспроизводима на любой машине. Самосинхронизация периодических сообщений измерена, объяснена и вылечена джиттером. Фантомная пробка воспроизведена в контролируемом эксперименте. Метастабильные отказы описаны на десятках реальных инцидентов в OSDI-работе 2022 года.

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

Демонстрации, а не свидетельства. Boids, модель Шеллинга, «Игра жизнь» Конвея, муравьиные алгоритмы. Они показывают, что простые локальные правила способны давать сложное глобальное поведение. Это важный результат — он опровергает интуицию «сложный результат требует сложной причины». Но из него не следует ничего о конкретной вашей системе. Полевые данные по настоящим муравьям (например, многолетние наблюдения Деборы Гордон) как раз показывают, что реальные колонии устроены сложнее, чем красивые модели, и регулируются в основном частотой локальных контактов.

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

За что понятие критикуют по делу. Во-первых, за нефальсифицируемость сильной версии. Во-вторых, за использование как ярлыка вместо механизма — критика Форрестера и «Пределов роста» ровно про это: интересная структура плюс неоценённые по данным параметры дают траектории, которые нельзя проверить (см. W. Nordhaus, «World Dynamics: Measurement Without Data», The Economic Journal, 1973, и споры вокруг сверок World3 с фактическими данными). В-третьих, за то, что понятие часто описывает состояние наших знаний, а не свойство мира: то, что было «эмерджентным» до объяснения, после объяснения становится обычным следствием. Ни одно из этих возражений не отменяет инженерной пользы слабой эмерджентности — но каждое требует, чтобы за словом стоял механизм.

Типовые ловушки

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

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

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

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

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

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

Практика: 40 минут на своей системе

Возьмите последний инцидент или последнюю жалобу «всё тормозит, но всё зелёное».

  1. (5 мин) Назовите свойство целого, которое сломалось. Одной строкой, в терминах пользователя или бизнеса: «доля заказов, оформленных за 3 секунды». Не «CPU реплики».
  2. (5 мин) Прогоните три теста. Определено ли свойство для части? Исчезает ли при декомпозиции? Есть ли для него формула? Если ответы «да / да / нет» — вы в зоне слабой эмерджентности.
  3. (10 мин) Найдите общий ресурс. Что делит спорные части: пул, диск, сеть, лок, кэш, внимание людей? Нарисуйте петлю в три-пять узлов со знаками связей. Ровно одну петлю, не «карту всего».
  4. (5 мин) Сформулируйте предсказание, отличное от аддитивного. Число со знаком: «при снижении fan-out с 120 до 40 доля медленных запросов упадёт с ~70% до ~33%».
  5. (10 мин) Придумайте эксперимент на разрыв связи. Что отключить, изолировать или разнести по времени, чтобы свойство исчезло, если гипотеза верна? Оцените стоимость и обратимость.
  6. (5 мин) Запишите, что вас опровергнет. Одно предложение. Если опровергающего наблюдения не существует — вернитесь к пункту 1: у вас не модель, а формулировка ощущения.

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

Мини-итог

  • Эмерджентное свойство определено для целого, не определено для части, исчезает при декомпозиции и не получается агрегированием. Все четыре признака нужны сразу.
  • Практическая градация одна: слабая эмерджентность — свойство выводимо из взаимодействий, но часто только прогоном. Сильная эмерджентность неопровержима и потому вне инженерии.
  • Три ингредиента: много элементов, общий ограниченный ресурс, обратная связь с задержкой. Нет общего ресурса — ищите обычный баг в компоненте.
  • Самый практичный пример — хвост латентности при fan-out: $1-(1-p)^k$. При p99 у каждого узла и $k \approx 70$ половина запросов ловит чей-то хвост. SLO надо резать сверху вниз, а не собирать снизу вверх.
  • Взаимная блокировка — свойство графа ожидания, а не мьютекса. Лечится глобальным порядком, а не проверкой каждого лока.
  • Самосинхронизация меняет не среднюю нагрузку, а корреляцию. На графике объёма её не видно вплоть до аварии; лечение — джиттер, coalescing, разнесение общих событий.
  • Утверждение об эмерджентности инженерно, если есть метрика уровня целого, предсказание, отличное от суммы, и эксперимент, способный его опровергнуть. Иначе это переименованное незнание.
  • Симуляция показывает достаточность механизма, не его наличие. Скачок на графике может быть создан вашей метрикой, а не системой.
  • Порядок расследования: сначала одна причина, потом система. Ложная эмерджентность останавливает расследование ровно там, где его надо продолжать.

Источники

Что дальше

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

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

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

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

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

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