Теневые модули и постепенный переход на новый вариант
Теневой модуль = новая версия того же модуля, которая параллельно с уже запущенной старой версией работает в тестовом режиме под собственным владельцем (например, roll_shadow). Он получает копии реальных событий, а его исходящие вызовы перехватываются и фиксируются, вместо того чтобы быть реально отправленными.
Сравнивая поведение двух версий в shadow_diff, можно убедиться, что новая версия безопасна, после чего с помощью promote можно одним действием перевести её в статус основной, а с помощью dismiss — в любой момент отказаться от теневого режима. Код модуля не изменяется, вся работа происходит с помощью API в режиме выполнения: подобно операциям load / unload / reload, такие действия можно вызывать через Dashboard или пользовательские модули управления, без необходимости писать какие-либо конфигурационные параметры.
{!--< tips >!--}
- Запуск:
await sdk.module.shadow_start("roll", source="путь/к/v2")— новая версия кода работает параллельно со старой под собственным владельцем (по умолчанию имя владельца берётся из пути) - Теневой модуль не участвует в реальном распределении и не влияет на граф зависимостей: команды с таким же именем выполняются в теневом каталоге, маршрутизация регистрируется, но не подключается, события жизненного цикла тихо игнорируются, а
module.callи разрешение зависимостей по-прежнему указывают на v1 - Переход на новую версию всегда подтверждается человеком:
await sdk.module.promote_shadow("roll"), в случае неудачи автоматически происходит откат к старому экземпляру, который продолжает обслуживать запросы;dismiss_shadowпозволяет в любой момент отказаться от теневого режима {!--< /tips >!--}
Быстрый старт
# Код v2: обычный модуль, без теневого режима (любой каталог, например downloads/roll_v2/)
# В онлайн-боте (через Dashboard / модуль управления), запуск грейд-теста одной строкой:
await sdk.module.shadow_start("roll", source="downloads/roll_v2")
# → Теневой модуль с отдельным owner "roll_v2" параллельно работает с v1, исходящие сообщения перехватываются и записываются
# Сравнение поведения во время тестирования:
report = sdk.module.shadow_diff("roll")
# {"shadow_owner": "roll_v2", "count": 3, "aligned": [...]}
- v1 фактическая отправка: из бот-таймлайна в инбоксе (transcript)
- v2 предполагаемая отправка: теневой отчёт (запись в исходящем трафике о "что хотели отправить")
- Оба сопоставляются по
trace_id— для одного и того же сообщения, почему один и другой вариант сработали/не сработали, что хотели отправить/что отправили — всё видно
После проверки без ошибок:
await sdk.module.promote_shadow("roll") # Промоутить, при ошибке автоматически откатить к v1
await sdk.module.dismiss_shadow("roll") # Или отменить теневой режим
Пять изолирующих барьеров
| Барьер | Механизм |
|---|---|
| Копия события | Теневой обработчик получает независимую копию события (с меткой shadow) — изменения / признание / остановка распространения теневого события влияют только на копию, не затрагивая оригинальную цепочку событий |
| Выходная дверь | Все вызовы DSL Send и Api теневого события перехватываются и регистрируются (возвращается фиктивный ответ), событие не отправляется наружу — теневой обработчик не будет повторно отвечать |
| Слой перекрытия хранилища | Запись KV теневого события происходит в слое перекрытия в памяти и не сохраняется в базу данных; чтение сначала проверяет слой перекрытия, при неудаче — обращается к реальной базе данных (灰度 процесс работает с реальными данными); удаление помечается как "могильный камень" |
| Блокировка маршрутизации | HTTP/WS/SSE маршруты теневого события регистрируются, но не подключаются; команды с тем же именем записываются в каталог теневых команд, методы платформенных событий запрещены к вставке |
| Тихий жизненный цикл | Теневой обработчик не транслирует собственные события жизненного цикла и не участвует в экосистемной зависимости (вызовы module.call и разрешение зависимостей по-прежнему указывают на v1, чтобы избежать зависимости от незавершённого продукта) |
Наследование конфигурации: теневой обработчик по умолчанию наследует конфигурационные разделы оригинального модуля (иначе серая версия будет отличаться от оригинала), после перевода в основной режим конфигурация применяется на месте.
Честные границы (не остановить)
- Отправка / API / хранилище KV / единый HTTP-клиент через фреймворк всё можно остановить;
модули, обходящие фреймворк, запускают
aiohttpнапрямую, создают потоки и пишут во внешние системы — фреймворк не может остановить - Чтение и запись ORM не входят в область покрытия (по строкам overlay невозможно реализовать чисто на уровне SQL) — в период теневого режима рекомендуется избегать зависимости от ORM для обеспечения изоляции
- Источник теневого режима — локальный путь: новая версия кода импортируется по пути, загружается отдельным владельцем; один и тот же пакет PyPI
в одном и том же интерпретаторе ограничен ключом
sys.modules, не позволяя одновременно использовать две версии - Аудитор утечек (
sdk.module.audit) видит принадлежность теневых ресурсов; побочные эффекты от обхода фреймворка по крайней мере не будут бесшумными
Промотирование и откат
Процесс promote: создание снимка текущей версии (включая все зависимые модули) → полное удаление → регистрация теневого экземпляра с настоящим именем → загрузка → при сбое на любом этапе автоматический откат, старый экземпляр продолжает обслуживать запросы (семантика "по возможности": эффекты, вызванные уже выполненным on_unload, не могут быть отменены, после отката старый экземпляр находится в состоянии завершения). После успешного промотирования теневые ресурсы утилизируются, и связь снимается; конфигурация оригинального модуля применяется на месте.
Примечание о сохранении изменений: промотирование происходит в режиме выполнения — после перезапуска будет продолжать работать версия v2. Необходимо сохранить новую версию (установить новую версию с помощью pip install -U или заменить файлы плагина). Переключение в режиме выполнения не заменяет пакетный менеджер.
Связанные документы
- Система собственности (owner) — основа механизма изоляции теней с независимым собственником
- Взаимодействие сессии —
trace_idи ящик для входящих сообщений (источник данных с выравниванием по разнице) - Область видимости (scope) — контрольный интерфейс для входа и выхода событий