Эмерджентность: свойства целого, которых нет у частей
Дежурного будят в 03:40. Он открывает дашборд и видит картину, которая встречается чаще, чем хотелось бы: все компоненты в норме, продукт не работает. Каждый из ста узлов отвечает быстрее своего SLO. Ни одна база не перегружена. Ни один под не в CrashLoopBackOff. При этом пользовательский p99 — восемь секунд, конверсия просела вдвое, а поддержка уже собирает жалобы.
Инстинкт говорит: «где-то есть виноватый компонент, я его просто не нашёл». Часто это верно — и тогда его находят за двадцать минут. Но существует класс ситуаций, где виноватого компонента нет по построению: свойство, которое сломалось, вообще не принадлежит ни одному компоненту. Оно принадлежит их совокупности.
Это и есть эмерджентность. В прошлой главе мы разбирали, как система скачком меняет режим при небольшом изменении параметра. Теперь другой вопрос: какие свойства вообще существуют только на уровне целого — и что с ними можно делать инженерно.
Сразу о жанре. Слово «эмерджентность» — рекордсмен по злоупотреблению: им затыкают дыры в понимании, им оправдывают отсутствие расследования, вокруг него выросла целая индустрия красивых слов про «холистичность» и «синергию». Всего этого здесь не будет. Эмерджентность — либо проверяемое утверждение о том, какое взаимодействие породило какое измеримое свойство, либо признание, что механизм неизвестен. Третьего варианта нет, и раздел «Пределы метода» ниже — не формальность, а треть главы.
Минимальное определение
Эмерджентное свойство — свойство системы, которое:
- не определено для отдельной части — вопрос «какая пропускная способность у одного болта» бессмыслен, а «какая пропускная способность у конвейера» — осмыслен;
- порождается взаимодействием частей, а не их индивидуальными характеристиками;
- не получается простым агрегированием — суммой, средним или максимумом свойств частей.
Все три условия обязательны. Уберите первое — получите обычную аддитивную величину. Уберите второе — получите совпадение. Уберите третье — получите арифметику, а не эмерджентность.
Полезная контрольная формулировка: эмерджентность — это утверждение об уровне описания, а не о мистике. Она говорит, что язык, на котором описаны части, недостаточен, чтобы записать свойство целого. Нужен другой словарь: не «латентность узла», а «латентность запроса»; не «состояние мьютекса», а «цикл ожидания»; не «производительность инженера», а «пропускная способность команды».
Обратная сторона того же утверждения: эмерджентность зависит от выбранной границы. То, что эмерджентно относительно границы «наши сервисы», может быть тривиально аддитивно относительно границы «наши сервисы плюс клиентские ретраи». Если вы не зафиксировали границу — вы не сказали ничего проверяемого. Механика выбора границы разобрана в главе о границах.
Три теста: эмерджентность или просто сумма
Прежде чем произносить слово, прогоните свойство через три вопроса. Они дешёвые и отсеивают 80% ложных срабатываний.
Тест 1. Выразимо ли свойство для одной части
Возьмите одну часть и попробуйте задать вопрос про неё. «Какой у этого узла throughput?» — осмысленно. «Какая у этого узла консистентность реплик?» — бессмысленно: консистентность определена для множества реплик. «Сколько у этого потока взаимных блокировок?» — бессмысленно: взаимная блокировка определена для набора потоков и набора ресурсов.
Если вопрос не формулируется для части — это кандидат в эмерджентные свойства.
Тест 2. Переживает ли свойство декомпозицию
Разберите систему и измерьте части по отдельности. Если свойство исчезло — оно жило во взаимодействии. Это ровно то, что происходит на разборе инцидента: изолированный нагрузочный тест каждого сервиса проходит зелёным, а в связке система падает. Проблема не в том, что «тесты плохие», а в том, что тестировали части, а свойство принадлежит целому.
Практическое следствие: интеграционный и нагрузочный тест — не «более дорогой юнит-тест», а единственный инструмент, который вообще видит этот класс свойств. Подробнее про уровни тестирования — в треке тестирования, про специфику распределённых систем — в «Тестировании распределённых систем».
Тест 3. Есть ли короткий путь к ответу
Самый содержательный тест. Спросите: можно ли предсказать свойство целого формулой, или единственный способ — прогнать динамику шаг за шагом?
- Общий вес сервера = сумма весов деталей. Формула есть, эмерджентности нет.
- Вероятность, что запрос с fan-out на 100 узлов попадёт в чей-то хвост, = $1 - (1-p)^{100}$. Формула есть, но она не сумма и не среднее: свойство есть у запроса и отсутствует у узла. Это эмерджентность с аналитическим решением — самый удобный случай.
- Момент, когда сервис перейдёт в метастабильный отказ при данном профиле нагрузки, аналитически не считается: нужна симуляция или эксперимент. Это эмерджентность без короткого пути.
Марк Бедо предложил называть третий случай слабой эмерджентностью: свойство выводимо из правил взаимодействия, но только «прогоном» — никакого сокращения не существует (M. Bedau, «Weak Emergence», Philosophical Perspectives 11, 1997). Для инженера это принципиальная граница: слабо эмерджентное свойство нельзя доказать чтением кода, его можно только воспроизвести.
Три градации, из которых инженеру нужны две
| Градация | Что означает | Пример | Инженерная ценность |
|---|---|---|---|
| Номинальная | свойство просто не определено для части | «пропускная способность конвейера», «форма молекулы» | нулевая: это про словарь, не про поведение |
| Слабая | выводима из взаимодействий, но только симуляцией/экспериментом | метастабильный отказ, фантомная пробка, самосинхронизация клиентов | максимальная: почти вся практика тут |
| Сильная | принципиально невыводима из свойств частей ни при каком вычислении | предмет спора в философии сознания | нулевая: неопровержимо, значит бесполезно |
Позиция трека: работаем со слабой эмерджентностью, номинальную отмечаем и идём дальше, сильную не используем вообще. Не потому, что «её нет» — вопрос открытый и лежит вне инженерии, — а потому что утверждение «это невыводимо в принципе» не порождает ни одного действия и не может быть проверено ни одним экспериментом. В инженерном разговоре оно функционально эквивалентно отказу расследовать.
Та же оговорка касается «нисходящей причинности» (downward causation) — идеи, что целое каузально воздействует на части. Как метафора («SLO команды меняет поведение отдельного инженера») она безобидна и даже полезна. Как механизм — спорна и не нужна: всё, что нужно объяснить, объясняется тем, что часть реагирует на наблюдаемый ею сигнал, который зависит от состояния целого. Это обычная обратная связь, разобранная в главе о петлях, и она проверяема.
Откуда берётся: три ингредиента
Эмерджентность не появляется от одного лишь количества элементов. Миллион независимых, не взаимодействующих узлов даёт ровно сумму. Нужны три вещи одновременно:
- Много элементов — достаточно, чтобы взаимодействий было больше, чем элементов. При $n$ элементах потенциальных парных связей $n(n-1)/2$: рост числа связей квадратичный, и с какого-то момента поведение определяется именно ими.
- Взаимодействие через ограниченный или общий ресурс — сеть, пул соединений, диск, лок, кэш, внимание дежурного, время код-ревью. Именно общий ресурс делает элементы зависимыми и превращает независимость в корреляцию.
- Обратная связь с задержкой — реакция элемента на состояние целого возвращается в целое. Без петли получается статистика; с петлёй — динамика. Про задержку как отдельный источник сюрпризов — глава 05.
Проверочный вопрос при разборе: какой ресурс здесь общий и через сколько времени элемент узнаёт о состоянии целого? Если ответ «ничего общего нет» — вы не найдёте эмерджентности, ищите обычную ошибку в компоненте.
Считаемая эмерджентность: хвост латентности при fan-out
Начнём с самого удобного случая — свойство целого, которого нет у частей, но которое считается на салфетке.
Запрос пользователя раскладывается на $k$ обращений к независимым узлам (шарды, реплики, микросервисы, партиции индекса). Ответ отдаётся, когда вернулись все. Пусть каждый узел с вероятностью $p$ отвечает медленно — попадает в собственный честный хвост.
$$P(\text{запрос медленный}) = 1 - (1-p)^{k}$$
При $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, статические анализаторы порядка). Подробнее про механику конкурентности — в главе о производительности конкурентного кода.
Тот же приём годится далеко за пределами локов. «Циклическая зависимость сервисов при холодном старте», «дедлок в согласовании релиза между тремя командами», «оба дежурных ждут решения друг друга» — одна и та же структура: цикл в графе ожидания. Ищите цикл, а не виноватого.
Синхронизация из ничего: стадо, которое никто не собирал
Третий класс — самый контринтуитивный. Здесь эмерджентным свойством оказывается не «сколько», а «насколько согласованно».
Тысяча клиентов обновляют кэш раз в минуту. Каждый в отдельности ведёт себя безупречно, средняя нагрузка — постоянная и низкая. Через несколько недель работы возникает то, чего никто не программировал: клиенты начинают ходить одновременно. Средняя нагрузка та же, пиковая — в десятки раз выше, и в момент пика сервис ложится.
Механизм — усиливающая петля по корреляции, а не по объёму:
фазовая синхронизация] PEAK[Мгновенный пик нагрузки] LAT[Латентность и таймауты] ALIGN[Общий момент
перезапуска таймера] LOAD[Средняя нагрузка] CAP[Ёмкость сервиса] JITTER -->|"−"| CORR CORR -->|"+"| PEAK PEAK -->|"+"| LAT LAT -->|"+"| ALIGN ALIGN -->|"+ R1: клиенты выравниваются по общему событию"| CORR PEAK -->|"+"| CAP CAP -->|"− B1: отказы и шеддинг разгоняют фазы обратно"| CORR LOAD -.->|"не меняется"| PEAK
Ключевая деталь на схеме — пунктирная стрелка. Средняя нагрузка не участвует в петле. Она может быть постоянной годами, пока растёт корреляция. Именно поэтому на графике «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 минут на своей системе
Возьмите последний инцидент или последнюю жалобу «всё тормозит, но всё зелёное».
- (5 мин) Назовите свойство целого, которое сломалось. Одной строкой, в терминах пользователя или бизнеса: «доля заказов, оформленных за 3 секунды». Не «CPU реплики».
- (5 мин) Прогоните три теста. Определено ли свойство для части? Исчезает ли при декомпозиции? Есть ли для него формула? Если ответы «да / да / нет» — вы в зоне слабой эмерджентности.
- (10 мин) Найдите общий ресурс. Что делит спорные части: пул, диск, сеть, лок, кэш, внимание людей? Нарисуйте петлю в три-пять узлов со знаками связей. Ровно одну петлю, не «карту всего».
- (5 мин) Сформулируйте предсказание, отличное от аддитивного. Число со знаком: «при снижении fan-out с 120 до 40 доля медленных запросов упадёт с ~70% до ~33%».
- (10 мин) Придумайте эксперимент на разрыв связи. Что отключить, изолировать или разнести по времени, чтобы свойство исчезло, если гипотеза верна? Оцените стоимость и обратимость.
- (5 мин) Запишите, что вас опровергнет. Одно предложение. Если опровергающего наблюдения не существует — вернитесь к пункту 1: у вас не модель, а формулировка ощущения.
Если за 40 минут получилось меньше половины — это нормальный результат: он точно показывает, какой метрики уровня целого вам не хватает. Это и есть первая задача.
Мини-итог
- Эмерджентное свойство определено для целого, не определено для части, исчезает при декомпозиции и не получается агрегированием. Все четыре признака нужны сразу.
- Практическая градация одна: слабая эмерджентность — свойство выводимо из взаимодействий, но часто только прогоном. Сильная эмерджентность неопровержима и потому вне инженерии.
- Три ингредиента: много элементов, общий ограниченный ресурс, обратная связь с задержкой. Нет общего ресурса — ищите обычный баг в компоненте.
- Самый практичный пример — хвост латентности при fan-out: $1-(1-p)^k$. При p99 у каждого узла и $k \approx 70$ половина запросов ловит чей-то хвост. SLO надо резать сверху вниз, а не собирать снизу вверх.
- Взаимная блокировка — свойство графа ожидания, а не мьютекса. Лечится глобальным порядком, а не проверкой каждого лока.
- Самосинхронизация меняет не среднюю нагрузку, а корреляцию. На графике объёма её не видно вплоть до аварии; лечение — джиттер, coalescing, разнесение общих событий.
- Утверждение об эмерджентности инженерно, если есть метрика уровня целого, предсказание, отличное от суммы, и эксперимент, способный его опровергнуть. Иначе это переименованное незнание.
- Симуляция показывает достаточность механизма, не его наличие. Скачок на графике может быть создан вашей метрикой, а не системой.
- Порядок расследования: сначала одна причина, потом система. Ложная эмерджентность останавливает расследование ровно там, где его надо продолжать.
Источники
- P. W. Anderson. More Is Different. Science 177(4047), 1972 — программный текст об уровнях описания.
- M. Bedau. Weak Emergence. Philosophical Perspectives 11, 1997 — определение через «выводимо только симуляцией».
- J. Holland. Emergence: From Chaos to Order. Addison-Wesley, 1998.
- J. Dean, L. A. Barroso. The Tail at Scale. CACM 56(2), 2013.
- N. Bronson et al. Metastable Failures in Distributed Systems. HotOS 2021.
- L. Huang et al. Metastable Failures in the Wild. OSDI 2022.
- S. Floyd, V. Jacobson. The Synchronization of Periodic Routing Messages. SIGCOMM 1993.
- Y. Sugiyama et al. Traffic jams without bottlenecks. New Journal of Physics 10:033001, 2008.
- J. Gettys, K. Nichols. Bufferbloat: Dark Buffers in the Internet. ACM Queue 9(11), 2011.
- T. Schelling. Dynamic Models of Segregation. Journal of Mathematical Sociology 1(2), 1971.
- C. Reynolds. Flocks, Herds, and Schools: A Distributed Behavioral Model. SIGGRAPH 1987.
- E. Coffman, M. Elphick, A. Shoshani. System Deadlocks. ACM Computing Surveys 3(2), 1971.
- P. Denning. Thrashing: Its Causes and Prevention. AFIPS 1968.
- 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.
- J. Wei et al. Emergent Abilities of Large Language Models. TMLR, 2022.
- R. Schaeffer, B. Miranda, S. Koyejo. Are Emergent Abilities of Large Language Models a Mirage? NeurIPS 2023.
- D. Gordon. Ant Encounters: Interaction Networks and Colony Behavior. Princeton University Press, 2010.
- R. Cook. How Complex Systems Fail — короткое эссе, клинические наблюдения, не исследование; читать именно с этой поправкой.
- Addressing Cascading Failures — Google SRE Book.
- Timeouts, retries, and backoff with jitter — AWS Builders’ Library.
Что дальше
Мы разбирали свойства, которые есть у системы независимо от того, кто на неё смотрит. Но у одной и той же системы разные участники видят разные структуры — и действуют исходя из своих. Это не «непонимание», а закономерное следствие того, что каждый наблюдает свой срез. Следующая глава — про то, откуда берутся эти расхождения и как с ними работать.