18+
реклама
18+
Бургер менюБургер меню

Ранас Мукминов – Оркестрация ИИ-агентов. Claude Opus 4.7 (страница 87)

18

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

У роя агентов эта проблема острее, чем в обычных системах, по двум причинам. Первая — динамическое порождение работы: агент-обработчик, реагируя на событие, может породить несколько новых событий (см. главу 34 о взрыве работы). Если коэффициент размножения больше единицы, очередь растёт экспоненциально без всякого внешнего наплыва. Вторая — стоимость. Каждое сообщение в очереди роя агентов — это потенциальный вызов модели, то есть деньги. Неограниченная очередь — это неограниченный счёт. Поэтому backpressure в рое агентов — не только защита от перегрузки, но и защита бюджета: ограничение глубины очереди и числа одновременно обрабатываемых сообщений напрямую ограничивает расход (см. главу 59 о стоимости токенов на масштабе).

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

Режим | Характерный отказ | Механизм | Защита

Синхронный | Каскадная блокировка | Недоступность звена замораживает всю цепочку вверх | Тайм-аут на каждом вызове + circuit breaker (см. главу 70)

Синхронный | Истощение слотов ожидания | Много отправителей, заблокированных в ожидании, исчерпывают ёмкость | Bulkhead, ограничение числа одновременных синхронных вызовов (см. главу 71)

Синхронный | Сложение латентностей | Глубокая или последовательная цепочка копит задержку | Развязать веер в асинхронный; ограничить глубину

Асинхронный | Потерянный ответ | Отправитель ждёт сообщение, которое не придёт никогда | Тайм-аут ожидания + переход в явное состояние «ответ не получен»

Асинхронный | Переполнение очереди | Производство быстрее потребления | Backpressure, ограничение глубины, отбрасывание по политике

Асинхронный | Действие по устаревшему контексту | Реакция на событие после изменения мира | Версионирование состояния, проверка актуальности перед действием

Асинхронный | Дубликат-реакция | Сообщение доставлено дважды, обработчик сработал дважды | Идемпотентность обработчика, ключи дедупликации (см. главу 43)

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

В асинхронном режиме отправитель выпустил запрос, пошёл дальше и где-то держит состояние «жду ответ на запрос X». Если ответ не придёт — обработчик упал, сообщение потерялось, получателя не существовало, — отправитель будет ждать вечно, и, в отличие от синхронного зависания, это может не проявляться как явная остановка: отправитель формально жив, просто одна из его ожидаемых веток никогда не замкнётся. Задача зависнет в частично выполненном состоянии. Защита — тайм-аут ожидания (а не только вызова) на стороне отправителя плюс механизм переподхвата (см. главу 72): если ответ не пришёл за отведённое время, ожидание должно завершиться явным исходом — повтор, эскалация или частичный результат (см. главу 73), — но не молчаливым бесконечным ожиданием. Асинхронность не отменяет необходимости тайм-аутов; она делает их менее очевидными и потому более забываемыми.

Выбор режима — не глобальное решение для всей системы, а решение для каждого ребра взаимодействия. В одном рое одни связи синхронны, другие асинхронны, и это нормально. Критерий выбора сводится к нескольким вопросам о природе зависимости.

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

Есть ли веер? Любой fan-out к независимым исполнителям должен быть асинхронным по выпуску — иначе латентности складываются. Это почти безусловное правило (см. главу 8).

Какова латентность и дисперсия получателя? Чем дольше и непредсказуемее работает получатель, тем дороже синхронное ожидание и тем больше выгода развязки. Вызов быстрого детерминированного инструмента можно ждать синхронно; вызов цепочки рассуждений другого агента с большой дисперсией времени — кандидат на асинхронность.

Критична ли доступность связи? Если получатель может быть временно недоступен (перегружен, перезапускается, удалён за эгресс-границей), асинхронный буфер делает связь устойчивой к этому. Синхронная связь с ненадёжным получателем — источник каскадных блокировок.

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

Идемпотентен ли получатель? Асинхронный режим почти всегда подразумевает повторы (тайм-аут ожидания приводит к повторной отправке), а повтор у недетерминированного агента означает, что та же задача будет выполнена ещё раз — возможно, с другим результатом и с повторным побочным эффектом. Если обработчик не идемпотентен (см. главу 43), асинхронность с повторами небезопасна: повторная отправка способна продублировать действие. Синхронный режим маскирует эту проблему (один вызов — один ответ), но не устраняет её: при синхронном повторе после тайм-аута возникает та же опасность дубля. Поэтому вопрос идемпотентности предшествует выбору режима: без неё ни тот ни другой режим не дают безопасного повтора.

Характеристика ребра | Склоняет к синхронному | Склоняет к асинхронному

Зависимость следующего шага от результата | Жёсткая, немедленная | Отложенная или условная

Веер | Нет, один получатель | Да, много получателей

Латентность получателя | Низкая, предсказуемая | Высокая, с большой дисперсией

Доступность получателя | Высокая, стабильная | Переменная, возможна недоступность

Частота ребра | Редкое, холодный путь | Частое, горячий путь

Допустимость отставания (буферизация) | Недопустимо ждать накопления | Буфер сглаживает всплески

Цена удержания контекста отправителем | Низкая | Высокая

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

Два симметричных антипаттерна встречаются чаще всего.

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

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

Оба антипаттерна — следствие глобального выбора режима вместо выбора по ребру. Здоровый рой почти всегда гибриден.

Выбор режима не свободен от топологии (см. часть II): топология задаёт, какие рёбра вообще существуют, а тем самым подсказывает естественный режим для каждого. Оркестратор-воркеры (см. главу 8) тяготеет к асинхронному вееру на выпуске и синхронной по итогу сборке: оркестратор развязанно рассылает подзадачи и собирает результаты по готовности. Конвейер (см. главу 10) естественно асинхронен между стадиями — каждая стадия выпускает результат следующей и не ждёт, пока та завершит, что и позволяет стадиям работать на разных элементах одновременно. Blackboard (см. главу 11) асинхронен по построению: запись на доску — это fire-and-forget, а чтение развязано с записью во времени, у сообщения нет адресата и нет ожидающего ответа отправителя. Сети равноправных агентов (см. главу 13) смешивают режимы и потому несут наибольшую сложность согласования во времени — именно там размытая причинность и неупорядоченность проявляются сильнее всего. Практический вывод: выбирая топологию, проектировщик уже наполовину выбирает временной режим её рёбер, и навязывать топологии чуждый ей режим (например, делать blackboard синхронным или конвейер блокирующим на каждой стадии) — значит терять её главное преимущество.