Ракету не отправляют в космос только потому, что её двигатель и насос успешно прошли стендовые испытания по отдельности. Перед стартом инженеры рассчитывают траекторию, моделируют режимы работы и анализируют сценарии отказов. Расчёт не заменяет реальные тесты, но задаёт для них осмысленную рамку.
В софте всё обычно иначе. Распределённый пользовательский путь — например, оформление заказа — собирается из десятков микросервисов, баз и очередей. Разработчики добавляют новую зависимость, видят зелёные тесты, проверяют локальные метрики и выкатывают релиз. Считается, что если при сбое что-то пойдёт не так, настроенная система наблюдаемости обязательно это покажет.
Она, конечно, покажет. Но почему при проектировании микросервисов мы так спокойно относимся к тому, что узнаём о хрупкости архитектуры в основном по факту инцидента?
Эта статья о том, как получить грубый расчёт деградации системы ещё до релиза. Без отказа от хаос-инжиниринга или мониторинга, а как шаг перед ними. Я расскажу о двух экспериментах, в которых топологическая модель автоматически извлекалась из распределённых трейсов, после чего на ней просчитывались сценарии отказов методом Монте-Карло. Результаты моделирования я затем сравнивал с реальными инъекциями отказов на стендах DeathStarBench и OpenTelemetry Demo.
Почему микросервисы не моделируют
Основная проблема предварительного моделирования в IT — это цена поддержки актуальности.
Монолитную систему было относительно легко удержать в голове. С микросервисами пользовательский путь начал проходить через несколько команд и сетевых зон. Новая связь между двумя сервисами может ничего не сломать в локальных тестах, но при этом кардинально изменить картину устойчивости для всего процесса оплаты.
Поддерживать ручную модель в такой среде почти невозможно. Диаграмма в корпоративной wiki устаревает сразу после следующего коммита. Как только кто-то выносит часть логики в очередь или подключает стороннее API, схема превращается в бесполезную историческую справку. Заставлять команды рисовать актуальные схемы руками — значит просто добавить им бюрократии.
Поэтому индустрия логично сделала ставку на логи, метрики, трейсы и алерты. Это критически важные вещи, но они отвечают на вопрос о том, что сломалось сейчас, а не о том, что сломается завтра.
Если схемы рисовать долго и дорого, возникает резонный вопрос: можно ли автоматически собирать рабочую модель прямо из эксплуатационных артефактов, которые система и так производит? Распределённая трассировка, конфигурации service mesh и манифесты Kubernetes уже содержат фактическую топологию. В своих работах (и далее в статье) я буду называть процесс автоматического восстановления графа по этим данным model discovery (по-русски ближе всего «автоматическое извлечение модели»).
Суть подхода
Алгоритм выглядит довольно просто. Из распределённых трейсов восстанавливается направленный граф зависимостей. Для каждого целевого эндпоинта мы задаём условия успеха — то есть перечисляем, какие компоненты обязательно должны быть доступны для выполнения бизнес-операции. Дальше запускается Монте-Карло. В ходе симуляции часть сервисов случайным образом «выводится из строя», и для каждого сценария проверяется, остался ли жив нужный пользовательский путь. Процент успешных прогонов даёт расчётную вероятность доступности.
В псевдокоде ядро выглядит так:
for each failure_fraction: repeat N times: failed = sample_services(failure_fraction) alive_graph = graph - failed for each endpoint: success = reachable(entrypoint, required_targets, alive_graph) R_model = successful_trials / N
Я намеренно начал с предельно грубой структурной модели. Если сразу пытаться закладывать в неё штормы ретраев, размер пула соединений, таймауты и накопление очередей, инструмент усложнится настолько, что потеряет практический смысл. Задача первого слоя моделирования — проверить, даёт ли простая графовая связность полезный сигнал. Для этого результаты расчёта надо было сравнить с натурными хаос-экспериментами.
Кажется, что сотни тысяч графовых симуляций — это долго, но вычисления получаются дешёвыми. В моих замерах на обычных раннерах GitHub Actions (ubuntu-22.04, 4 CPU, 16 GB RAM) прогонов графа занимали около 30 секунд. Причём это был исследовательский неоптимизированный код на Python с использованием
networkx. То есть математика Монте-Карло не является узким местом и спокойно встраивается в обычный CI-пайплайн.
Гораздо сложнее правильно определить границы системы. В модели узлом считается любой сервис или зависимость, от которых зависит целевой путь. Всю инфраструктуру наблюдаемости (коллекторы, Jaeger) мы из зоны отказов исключаем. С базами данных и брокерами сообщений сложнее. Если трейс обрывается на клиенте (например, внутри библиотеки драйвера PostgreSQL) и база не видна как независимый узел, структурная модель по трейсам её отказ просто не заметит. Учитывать stateful-компоненты можно только тогда, когда они явно представлены в графе.
Эксперимент 1. DeathStarBench Social Network
Первый тест я проводил на академическом микросервисном бенчмарке DeathStarBench Social Network.
Из Jaeger экспортировался граф зависимостей, преобразовывался в модель, после чего рассчитывалась доступность операций. Результаты сравнивались с реальной остановкой контейнеров на стенде. Нагрузка подавалась утилитой wrk2 со стандартным профилем запросов 1:3:6.
Здесь есть важная деталь про подсчёт метрики на живом стенде. Ориентироваться только на коды HTTP 200 и 500 некорректно, потому что реальный отказ сервиса часто выражается в разрыве соединения или срабатывании таймаута. Поэтому для оценки доступности () использовалась формула, учитывающая сетевые сбои:
Стенд гонялся в двух режимах — без репликации (norepl) и с ней (repl). Доля искусственно выводимых из строя узлов варьировалась от 0.1 до 0.9. В GitHub Actions крутилась матрица из 250 запусков, каждый из которых включал симуляцию и реальные окна измерений.
Сводные результаты:
|
Режим |
|
|
|
|---|---|---|---|
|
|
0.1 |
0.4182 ± 0.0005 |
0.5533 ± 0.0031 |
|
|
0.3 |
0.1613 ± 0.0005 |
0.1775 ± 0.0021 |
|
|
0.5 |
0.0454 ± 0.0002 |
0.0376 ± 0.0006 |
|
|
0.7 |
0.0014 ± 0.0000 |
0.0067 ± 0.0000 |
|
|
0.9 |
0.0000 ± 0.0000 |
0.0000 ± 0.0000 |
|
|
0.1 |
0.6281 ± 0.0005 |
0.6969 ± 0.0026 |
|
|
0.3 |
0.3054 ± 0.0007 |
0.3054 ± 0.0017 |
|
|
0.5 |
0.1145 ± 0.0004 |
0.0958 ± 0.0011 |
|
|
0.7 |
0.0132 ± 0.0001 |
0.0155 ± 0.0003 |
На средних значениях отказов (например, repl, p_fail=0.3) прогноз совпал с реальностью до четвертого знака, а общая корреляция по точкам составила 0.992. Но куда важнее, что структурная модель корректно уловила само поведение системы. При включении репликации предсказанная доступность ожидаемо выросла.
Расхождения между моделью-стендом тоже показательны. На малых долях отказов () модель пессимистична. Она считает путь полностью недоступным при обрыве связи, хотя живой стенд, вероятно, вытягивает часть ситуаций за счёт внутренних механизмов восстановления (например, встроенных ретраев или резервных путей). На высоких долях отказов наблюдается обратная картина: модель слишком оптимистична, так как ничего не знает о вторичных эффектах перегруженной системы вроде переполнения пулов соединений или каскадных таймаутов.
Эксперимент 2. OpenTelemetry Demo и асинхронность
После этого я проверил подход на микросервисном приложении Astronomy Shop из OpenTelemetry Demo. Здесь граф строился уже из сырых спанов, а в топологии присутствовали асинхронные связи через Apache Kafka.
Наличие очередей заставляет чётко определиться с тем, что именно мы считаем успешным ответом. Одно дело — немедленный успех, когда пользователь получает корректный HTTP-ответ от фронтенда в рамках синхронного запроса. Совсем другое — успех отложенной обработки, когда заказ гарантированно проходит весь асинхронный пайплайн через консьюмеров.
В рамках этого теста я проверял только первый вариант (немедленный успех HTTP-запросов, например POST /api/checkout). Модель тестировалась в двух вариантах. В режиме all-blocking все зависимости, включая Kafka, считались критичными для ответа. В режиме async связи через очередь исключались из графа при оценке синхронной доступности.
Результат агрегированной симуляции на прогонов и натурных тестов в 5000 окон показал, что разница между этими двумя версиями модели на уровне эндпоинтов проявляется лишь в пятом знаке после запятой.
Из этого нельзя сделать вывод, что Kafka не влияет на надёжность. Вывод гораздо более узкий: для конкретного синхронного SLO потребители сообщений из очереди просто не лежат на критическом пути. Если же команда определяет успех как завершение всего флоу обработки за N секунд, асинхронные связи сразу станут центральным узлом расчёта.
Выводы и границы применимости
Оба эксперимента показали примерно одно и то же. Автоматическое извлечение графа не предсказывает сложную динамику — деградацию задержек, серые отказы, штормы ретраев и исчерпание пулов соединений.
Но свою главную задачу этот метод выполняет. Он даёт дешёвый, быстрый и автоматизированный сигнал об уязвимостях архитектуры. Инженер получает список хрупких путей, пострадавших из-за изменения связей в последних релизах. Это отличная отправная точка для прицельного хаос-инжиниринга, чтобы не ломать инфраструктуру вслепую.
Чтобы подход не превратился в набор эвристик, мы сейчас формализуем рамки применения модели. Её можно описать как , где
— извлечённый граф,
— репликация,
— условия успеха, а
— семантика связей.
При таком описании общую ошибку прогноза можно разложить на три компонента:
Первое слагаемое () отвечает за алгоритм извлечения. Трейсы могли потерять часть спанов из-за сэмплирования или добавить фоновый шум. Второе слагаемое (
) — это семантическая ошибка, то есть цена наших допущений вроде игнорирования таймаутов или очередей. И, наконец,
— это численная погрешность самого метода Монте-Карло. Такое разделение позволяет улучшать модель точечно, добавляя в неё ровно те механизмы, отсутствие которых даёт наибольшую дельту с живым стендом.
Понятно, что бенчмарки на 15–30 сервисов не гарантируют, что подход мгновенно заведётся в enterprise-ландшафте на сотни узлов. На больших объёмах главной проблемой станет не скорость симуляции, а качество исходных данных: рваные трейсы, неучтённые базы и сложные механизмы резервирования.
Если кто-то готов проверить эту механику на своей инфраструктуре, я буду рад совместному эксперименту. Доступы к проду или развёртывание сторонних агентов не требуются. Достаточно выгрузить обезличенные трейсы за определённый период и сопоставить их с историей реальных деградаций за то же время. Мы сможем проанализировать данные и проверить, подсветила бы графовая модель те узлы, которые в итоге стали причиной инцидентов. Если интересно — пишите в личку, в комменты, whatever else.
Артефакты, код и ссылки
Описанный пайплайн сейчас собирается в набор open-source инструментов MB3R Lab. Утилита Bering отвечает за извлечение топологии, а Sheaft производит симуляции и оценку устойчивости (обе написаны на Go).
Все результаты, исходный код тестов и бенчмарков лежат на GitHub:
-
Скрипты эксперимента на DeathStarBench:github.com/a-a-k/socialnet-resilience
-
Скрипты эксперимента на OpenTelemetry Demo:github.com/a-a-k/otel-demo-resilience
-
Тулсет Bering и Sheaft: github.com/MB3R-Lab
Академическое обоснование этих экспериментов можно найти в профильных работах:
-
Model Discovery and Graph Simulation: A Lightweight Gateway to Chaos Engineering, ICSE-NIER 2026 (работа получила Distinguished Paper Award).
-
Refining Resilience Model Discovery: A Case Study on the Limited Role of Asynchronous Edges in the OpenTelemetry Demo, AINA 2026 (если статья ещё не вышла, вот препринт на arXiv).
Можем ли мы построить мир, где не будет ночных дежурств? Распределённые системы стали слишком сложными, чтобы предсказывать их поведение по метрикам отдельных сервисов. Пользовательский путь живёт на графе зависимостей, и перед деплоем есть смысл заранее просчитывать, как этот граф переживёт отказ узлов. Извлечение модели по трейсам — это попытка сделать такой расчёт без рутинного рисования схем.
Дополнительные заметки по формальным методам, системной инженерии, моделированию и всякому другому я публикую в канале MB3R Lab.
ссылка на оригинал статьи https://habr.com/ru/articles/1033570/