Отсроченные операторы и триггеры в Apache Airflow: архитектура, асинхронная модель и практики внедрения
Аннотация и постановка задачи
Современные конвейеры данных требуют от систем оркестрации не только гибкости, но и экономичности. Классические сенсоры Airflow исторически блокировали рабочие процессы, удерживая воркеры при ожидании внешних событий или временных окон. Появление отсроченных операторов (deferrable operators) и триггеров в Apache Airflow радикально меняет экономику и архитектуру ожидания: ожидание переносится из воркеров на специализированный асинхронный процесс Triggerer, а логика пробуждения задач становится событийной.
Цель статьи - дать системный обзор архитектуры отсроченных операторов и триггеров в Apache Airflow, показать, почему триггеры не могут быть блокирующими, как правильно спроектировать и реализовать собственные deferrable-операторы и триггеры, а также раскрыть эксплуатационные аспекты: тестирование, производительность, безопасность, риски и экономическую эффективность. Материал ориентирован на профессиональную аудиторию: аналитиков, архитекторов, руководителей data-направлений и ИТ-директоров.
Термины и определения: оператор, сенсор, триггер, событие, отсрочка
- Оператор (Operator) - исполняемая единица в Airflow, инкапсулирующая действие (запуск SQL, Spark, Python-функции и др.).
- Сенсор (Sensor) - оператор-ожидание, проверяющий условие готовности (наличие файла, завершение внешней джобы). Исторически реализовывался режимами poke или reschedule.
- Триггер (Trigger) - асинхронный генератор событий, выполняемый в отдельном процессе Triggerer. Сигнализирует о наступлении условия посредством порождения TriggerEvent.
- Событие (TriggerEvent) - полезная нагрузка, созданная триггером; используется для возобновления отсроченных задач.
- Отсрочка (Deferral) - перевод задачи в состояние ожидания, при котором воркер освобождается, а условие ожидания отслеживается триггером в асинхронном процессе Triggerer.
Ключевая идея: операторы и сенсоры могут откладывать своё завершение, передавая наблюдение за условиями асинхронному триггеру, который пробудит их, когда наступит событие.
Теоретическая база: asyncio и неблокирующее выполнение в Apache Airflow
Асинхронная модель в Python реализована библиотекой asyncio, основанной на однопоточном событийном цикле (event loop). В рамках этого цикла множество корутин могут ожидать I/O, не блокируя друг друга: выполнение переключается при операциях await и на таймерах. Такой подход идеален для задач типа «ждать наступления времени», «полить внешнее API» с таймаутами или «дождаться сообщения в очереди».
Airflow использует asyncio в компоненте Triggerer: триггеры - это асинхронные генераторы событий, исполняемые в одном или нескольких процессах Triggerer. Неблокирующий характер корутинпозволяет параллельно вести ожидание тысяч триггеров, потребляя минимальные ресурсы CPU и памяти.
Важно понимать ограничения: блокирующие операции (например, time.sleep, синхронные сетевые вызовы) внутри асинхронного кода недопустимы, поскольку приостанавливают весь event loop и задерживают обработку прочих триггеров.
Архитектура и компоненты: Scheduler, Worker, Triggerer, БД метаданных
Архитектурная схема deferrable-парадигмы добавляет к классической тройке «Scheduler - Worker - БД метаданных» новый сервис Triggerer.
| Компонент | Роль | Взаимодействие |
|---|---|---|
| Scheduler | Планирование и диспетчеризация заданий | Читает/пишет состояния в БД, планирует таски на воркеры, обрабатывает события триггеров |
| Worker | Исполнение операторов | Выполняет Python-код операторов; при defer освобождается |
| Triggerer | Асинхронный исполнитель триггеров | Запускает asyncio event loop, исполняет триггеры, эмитит события |
| БД метаданных | Источник истины о состояниях | Хранит DAGs, задачи, отсрочки, сериализованные триггеры и события |
Поток управления высокоуровнево таков: оператор вызывает defer → его триггер сериализуется в БД → Triggerer подхватывает и исполняет триггер → событие попадает в БД → Scheduler возобновляет задачу на воркере, вызывая указанный метод.
Декомпозиция технических компонентов и их взаимодействие при отсрочке
-
Инициирование отсрочки:
- Оператор вызывает self.defer(trigger=..., method_name=..., kwargs=..., timeout=...).
- Airflow выбрасывает исключение TaskDeferred, останавливая текущее выполнение оператора и освобождая воркер.
- В БД создаётся запись с сериализованным состоянием триггера и метаданными (метод возврата, kwargs, таймаут).
-
Исполнение триггера:
- Triggerer читает из БД набор активных триггеров и исполняет их в своём asyncio event loop.
- Триггер генерирует TriggerEvent, когда наступает условие.
-
Возобновление оператора:
- Событие фиксируется в БД; Scheduler подбирает соответствующую задачу и планирует её повторный запуск на воркере.
- Воркёр создаёт новый экземпляр оператора и вызывает method_name(..., context=..., event=...).
-
Завершение задачи:
- Если метод возвращает управление без новой отсрочки - задача завершается.
- Если задача вновь вызывает defer - цикл повторяется.
Критично: между отсрочками объект оператора не сохраняется, а состояние не сериализуется автоматически. Передача данных возможна через kwargs и содержимое event.
Почему триггеры не могут быть блокирующими: модель процесса Triggerer и event loop
Triggerer - это процесс, исполняющий тысячи асинхронных задач поверх одного event loop. Блокирующий вызов внутри run триггера (например, time.sleep или синхронный HTTP-запрос)замораживает весь event loop и, как следствие, останавливает обработку всех прочих триггеров. Результат - системные задержки, лавинообразный рост времени реакции и нарушение SLA.
Аргументы против блокировки:
- Модель event loop предполагает кооперативную многозадачность на точках await.
- Блокировка одной корутины эквивалентна блокировке всего планировщика триггеров.
- В условиях высокой нагрузки задержки пропорционально накапливаются, что делает поведение системы непредсказуемым.
Отсюда следуют практические выводы: все операции триггера должны быть неблокирующими, выполнять I/O через asyncio-совместимые клиенты и использовать await asyncio.sleep для ожидания.
Принципы проектирования отсроченных операторов в Airflow
- Проектируйте операторы как stateless между отсрочками: состояние передаётся явно через kwargs и полезную нагрузку event.
- Любой оператор может стать отсроченным - не только сенсор. Отсрочку можно размещать до выполнения основной логики, после неё или между шагами.
- Поддерживайте два режима выполнения (deferrable и обычный) там, где это оправдано, чтобы обеспечить совместимость со старыми окружениями.
- Стремитесь к идемпотентности: оператор может возобновляться несколько раз, а триггеры переинициализироваться на других хостах.
- Ограничивайте размер полезной нагрузки event и kwargs до разумного минимума и форматов, сериализуемых в JSON.
Конфигурация режима deferrable и политика выбора режима выполнения
Airflow позволяет задать значение по умолчанию для deferrable-режима операторов и сенсоров, поддерживающих переключение:
from airflow.configuration import conf
DEFAULT_DEFERRABLE = conf.getboolean("operators", "default_deferrable", fallback=False)
Политика выбора:
- Включайте deferrable по умолчанию для всех ожиданий времени/событий в продуктивной среде.
- Отключайте deferrable в локальной разработке, если нет запущенного Triggerer (или нет уверенности в стабильности окружения).
- Поддерживайте параметр deferrable в init оператора, позволяя явно переопределить поведение в DAG.
Механика defer: сериализация триггера, method_name, kwargs, timeout, контекст и event
Метод defer определяет схему возобновления:
- trigger - экземпляр триггера. При отсрочке он сериализуется в БД: класс (classpath) и kwargs должны быть JSON-сериализуемыми.
- method_name - имя метода оператора, который будет вызван при возобновлении.
- kwargs - именованные аргументы, сериализуемые в JSON. Используются для передачи состояния.
- timeout - необязательный таймаут ожидания триггера; по истечении задача переводится в неуспех или срабатывает альтернативная ветвь логики.
- context и event - при возобновлении Airflow добавляет в вызов метода контекст исполнения и событие от триггера. event несёт полезную нагрузку (payload), позволяющую принять решение в операторе.
Семантика возврата: если метод method_name возвращает управление без новой отсрочки, задача считается завершённой.
Управление состоянием между отсрочками: stateless-операторы и передача данных через kwargs
Так как объект оператора уничтожается при defer, состояние необходимо передавать явно:
- Значения, необходимые следующему этапу, следует включать в kwargs defer.
- Результаты работы триггера передаются через event; полезно добавлять в event идентификаторы и версии для дедупликации.
- Для больших данных храните артефакты во внешнем хранилище (S3, GCS, БД), а в kwargs/event передавайте ссылки/ключи.
Такой подход повышает отказоустойчивость и упрощает поддержку высокой доступности.
Исключение TaskDeferred и модель жизненного цикла задачи
Вызов self.defer выбрасывает исключение TaskDeferred, что:
- Немедленно завершает текущее исполнение оператора на воркере.
- Переводит задачу в состояние «отсрочена» в БД.
- Инициирует регистрацию триггера в Triggerer.
Понимание этой семантики важно для правильного управления ресурсами и корректной обработки вложенных вызовов defer.
Семантика execution_timeout для отсроченных задач
Параметр execution_timeout относится к суммарному времени «жизни» задачи, включая периоды ожидания между отсрочками. Это значит, что задача может провалить SLA во время отсрочки или после возобновления, даже если «активное» выполнение заняло секунды. В практической эксплуатации это мотивирует:
- Учитывать суммарное время ожиданий в расчёте таймаутов.
- Использовать отдельный timeout в defer для ограничения ожидания конкретного триггера.
- Разделять задачи на логические шаги, каждому из которых назначать собственные таймауты.
Пример реализации: WaitOneHourSensor - пошаговый разбор
Ниже показан сенсор, ожидающий один час. Он поддерживает два режима: обычный (блокирующий) и отсроченный.
import time
from datetime import timedelta
from typing import Any
from airflow.configuration import conf
from airflow.sensors.base import BaseSensorOperator
from airflow.triggers.temporal import TimeDeltaTrigger
from airflow.utils.context import Context
class WaitOneHourSensor(BaseSensorOperator):
def __init__(
self,
deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False),
**kwargs,
) -> None:
super().__init__(**kwargs)
self.deferrable = deferrable
def execute(self, context: Context) -> None:
if self.deferrable:
self.defer(
trigger=TimeDeltaTrigger(timedelta(hours=1)),
method_name="execute_complete",
)
else:
time.sleep(3600)
def execute_complete(
self,
context: Context,
event: dict[str, Any] | None = None,
) -> None:
return
Ключевые моменты:
- В deferrable-режиме ожидание передаётся триггеру TimeDeltaTrigger.
- В обычном режиме используется блокирующий sleep - допустимо для локальной отладки, но не для продакшена.
- Метод execute_complete вызывается после события от триггера; он может анализировать event и принимать дополнительные решения.
Разработка пользовательских триггеров: BaseTrigger, init, run, serialize, cleanup
Пользовательский триггер наследуется от BaseTrigger и реализует минимум три метода:
- init - инициализация параметров (все должны быть сериализуемы в JSON).
- serialize - возвращает кортеж (classpath, kwargs), достаточный для воссоздания экземпляра.
- run - асинхронный метод, исполняющий неблокирующую логику и генерирующий (yield) один или несколько TriggerEvent.
Рекомендуется также реализовать cleanup - финализацию, которая вызывается после завершения run (успех/ошибка/отмена). Это повышает устойчивость при смене подсетей, рестартах и перемещении триггеров между инстансами Triggerer.
Ограничения и требования к run: async/await, yield событий, идемпотентность и дедупликация полезной нагрузки
- run должен быть объявлен как async def и использовать только неблокирующие операции.
- run обязан генерировать (yield) хотя бы одно событие; преждевременный return приведёт к ошибке и отмене зависимых задач.
- Полезная нагрузка события должна поддерживать дедупликацию при повторном запуске (например, включать уникальный ключ или timestamp).
- Логика должна быть идемпотентной: триггер может исполняться повторно, а его событие - обрабатываться более одного раза.
- Не храните долговременное состояние в полях self; триггер - stateless-компонент.
Пример реализации: DateTimeTrigger - пошаговый разбор
Триггер, ожидающий наступления определённого момента времени.
import asyncio
from airflow.triggers.base import BaseTrigger, TriggerEvent
from airflow.utils import timezone
class DateTimeTrigger(BaseTrigger):
def __init__(self, moment):
super().__init__()
self.moment = moment
def serialize(self):
return ("airflow.triggers.temporal.DateTimeTrigger", {"moment": self.moment})
async def run(self):
while self.moment > timezone.utcnow():
await asyncio.sleep(1)
yield TriggerEvent(self.moment)
Разбор:
- init принимает момент времени и сохраняет его для сериализации.
- serialize возвращает путь до класса и kwargs с JSON-сериализуемым значением.
- run использует await asyncio.sleep(1) как неблокирующее ожидание. При срабатывании триггер генерирует событие, передавая момент времени как полезную нагрузку - это упростит дедупликацию.
Высокая доступность, кэширование классов и перезапуск служб триггеров
Triggerer поддерживает горизонтальное масштабирование: можно запускать несколько процессов/инстансов. Активные триггеры распределяются между ними через БД метаданных; при падении одного инстанса триггеры перераспределяются. Такой дизайн обеспечивает высокую доступность.
Практические аспекты:
- Триггеры загружаются и кэшируются в памяти Triggerer. При изменении кода триггера следует перезапустить процессы Triggerer, чтобы обновить класс.
- Триггеры должны оставаться stateless, чтобы их можно было прозрачно переносить между инстансами без миграции состояния.
- Следите за конфигурацией емкости Triggerer (например, параметр наподобие triggerer.default_capacity), чтобы корректно лимитировать одновременные триггеры и защищать петлю событий от перегрузки.
Интеграция технологических стеков и синергия: asyncio-библиотеки (aiohttp, aioboto3, asyncpg), внешние API и очереди событий
Асинхронные триггеры хорошо интегрируются с неблокирующими клиентами:
- HTTP/REST - aiohttp, httpx (async).
- AWS - aioboto3 (доступ к S3, SQS, Step Functions).
- Базы данных - asyncpg (PostgreSQL), databases (унифицированный слой), motor (MongoDB).
- Очереди и стриминг - aiokafka, asyncio-nats, websockets.
- Файловые системы и облачные хранилища - используйте асинхронные SDK или выносите блокирующие операции в отдельный пул через asyncio.to_thread (осознанно и дозированно).
Главное требование - никакого блокирующего I/O внутри run. Если подходящего async-клиента нет, рассмотрите опрос с минимальной частотой и передачей тяжелых операций в воркерные задачи после пробуждения.
Кейсы применения: ожидание временных окон, готовности сервисов, завершения внешних заданий, доступности данных
- Окна запуска: «не раньше чем X», «окно бизнес-дня закрыто», «дождаться формирования отчётов».
- Готовность сервисов: «endpoint стал доступен», «healthcheck стабилен N минут».
- Завершение внешних джобов: Spark/EMR/Dataproc, Glue, Dataflow - ожидание статуса через async API.
- Доступность данных: появление файла/партиции в S3/HDFS/объектном хранилище, готовность таблицы в DWH.
- Событийный запуск: получение сообщения в SQS/Kafka/NATS для пробуждения задач или целых DAG-цепочек.
Применимость по отраслям: финтех, e-commerce, телеком, здравоохранение, промышленность, госсектор
- Финтех: согласование окон расчётов, ожидание клиринга, поступление регуляторных отчётов.
- E-commerce: синхронизация каталогов и цен, ожидание выгрузок из PIM/ERP, события о закрытии витрин.
- Телеком: готовность CDR/EDR, завершение агрегаций в сетевых системах, окна обслуживания.
- Здравоохранение: временные окна загрузки обезличенных данных, подтверждение интеграций HL7/FHIR.
- Промышленность: поступление пакетов телеметрии, завершение пакетных расчётов, события из MES/SCADA.
- Госсектор: окна сдачи отчётности, появление официальных справочников, события интеграций.
Во всех этих доменах deferrable-подход снижает стоимость ожиданий и улучшает SLA.
Анализ рисков и ограничений: блокирующий I/O, сериализация JSON, потеря состояния, дублирование событий, сетевые сбои; метрики эффективности и надежности
Риски и ограничения:
- Блокирующий I/O в run триггера блокирует event loop Triggerer - критический анти‑паттерн.
- Сериализация JSON: все kwargs и event должны быть сериализуемы; избегайте нестандартных объектов, больших бинарных данных.
- Потеря состояния: объект оператора уничтожается при defer; храните состояние во внешних хранилищах и передавайте ссылки.
- Дублирование событий: при сбоях триггеры и операторы могут обрабатываться повторно. Проектируйте идемпотентные операции и включайте ключи дедупликации в event.
- Сетевые сбои: используйте таймауты, экспоненциальные ретраи и «circuit breaker»-подходы в async-клиентах.
Метрики:
- Нагрузка на Triggerer: число активных триггеров, event loop lag, потребление CPU/памяти, доля ошибок в run.
- Задержки событий: время от срабатывания условия до генерации TriggerEvent и до возобновления задачи.
- Утилизация воркеров: среднее время простоя, занятость воркеров ожиданием (должно стремиться к нулю).
- SLA задач: процент задач, уложившихся в execution_timeout при наличии отсрочек.
Тестирование и отладка: pytest-asyncio, PYTHONASYNCIODEBUG, логирование триггеров и профилирование event loop
- Модульные тесты триггеров: используйте pytest-asyncio для проверки асинхронной логики, контроль временных сценариев и дедупликации.
- Статический анализ: проверьте, что в run отсутствуют вызовы time.sleep и синхронные клиенты I/O.
- PYTHONASYNCIODEBUG=1 - включает расширенные проверки asyncio и предупреждения о блокировках.
- Логирование: добавляйте структурированные логи в run и execute_complete; фиксируйте ключевые поля event, интервалы ожиданий, ретраи.
- Профилирование event loop: оценивайте lag и количество задач в loop; выявляйте узкие места.
Производительность и масштабирование: нагрузка на Triggerer, задержки генерации событий, утилизация воркеров
- Емкость Triggerer: один процесс способен обрабатывать тысячи триггеров при корректном async-коде. Контролируйте параметр емкости и при необходимости масштабируйте горизонтально, добавляя инстансы.
- Задержки: основной фактор** - правильность async-реализаций. Любые блокировки или чрезмерная частота опроса (polling) повышают лаг.
- Утилизация воркеров: деферы высвобождают воркеры. Планируйте пулы и concurrency, исходя из «активной» части исполнения.
- Планировщик: следите за размерами таблиц с событиями и индексами; от этого зависит скорость реакции на TriggerEvent.
Экономическая эффективность и оптимизация затрат при переходе на отсроченные операторы
- Сокращение затрат на воркеры: сенсоры больше не «жгут» CPU/память, простаивая в ожидании.
- Консолидация ожиданий: один или несколько Triggerer-процессов обслуживают тысячи ожиданий на малом числе vCPU.
- Снижение количества «болтающихся» задач и перепланирований уменьшает нагрузку на БД.
- Улучшение SLA: быстрее освобождаются слоты воркеров для вычислительно затратных задач.
- Окупаемость: в средах с Kubernetes/Celery снижение числа одновременно занятых подов/воркеров даёт прямую экономию облачных ресурсов.
Безопасность и соответствие требованиям: валидация входных данных, контроль доступа, безопасная сериализация и хранение
- Валидация: проверяйте входные параметры триггеров и операторов (типы, диапазоны, формат дат) до сериализации.
- Контроль доступа: используйте Airflow Roles/Permissions для ограничения запуска и изменения DAG/триггеров.
- Секреты и PII: не передавайте секреты и персональные данные в kwargs/event. Храните их в Connections/Secret Backends и передавайте только идентификаторы.
- Безопасная сериализация: используйте JSON с валидацией схемы, ограничивайте размер payload.
- Логи: санитизируйте содержимое event и kwargs в логах; исключайте чувствительные поля.
Конкурентный анализ: классические сенсоры (poke/reschedule), Prefect, Dagster; дифференциация подходов
- Классические сенсоры:
- poke - блокирует воркер (наименее экономично).
- reschedule - высвобождает воркер, но полагается на периодические перепланирования; нагрузка на Scheduler/БД выше, чем у deferrable.
- Airflow deferrable - событийная модель с Triggerer и asyncio, минимальная стоимость ожидания, высокая масштабируемость.
- Prefect - natively event-driven/async-friendly, но другая архитектура оркестрации и управления состоянием задач.
- Dagster - сенсоры и даёмон-сервисы для событий, развитая типизация артефактов; концептуально близок к событийности, но несовместим по интерфейсам.
Итог: deferrable в Airflow - оптимальный путь для организаций с существующими DAG’ами и инфраструктурой Airflow, где требуется снизить стоимость ожидания и улучшить SLA.
Шаблоны проектирования, best practices и антипаттерны для отсроченных операторов и триггеров
Best practices:
- Явная модель состояния: минимальный self, максимум** - через kwargs и ссылки на внешние артефакты.
- Идемпотентность и дедупликация: включайте уникальные ключи в event.
- Неблокирующий I/O: используйте asyncio-клиенты, await asyncio.sleep вместо time.sleep.
- Разделение обязанностей: триггер только ждёт и сигнализирует; оператор принимает решение и действует.
- Таймауты и ретраи: задавайте таймауты и политику повторов на обоих уровнях - defer и бизнес-операций.
Антипаттерны:
- Блокирующие вызовы в run (time.sleep, requests).
- Хранение больших объектов в event/kwargs.
- Сохранение критичного состояния в полях триггера/оператора между отсрочками.
- Чрезмерный polling с малым интервалом вместо событийной интеграции.
Миграция существующих задач и сенсоров на отсроченный режим выполнения
Стратегия миграции:
- Инвентаризация сенсоров и задач-ожиданий; приоритизация по длительности и частоте.
- Замена стандартных сенсоров на deferrable-аналоги (если есть) или внедрение параметра deferrable в их конструкторы.
- Переписывание пользовательских сенсоров/операторов: выделение триггеров и перенос ожидания в Triggerer.
- Включение default_deferrable в конфигурации и поэтапное распространение на DAG’и.
- Мониторинг метрик Triggerer и SLA; корректировка интервалов ожиданий и таймаутов.
- Деактивация устаревших poke-сенсоров.
Дорожная карта и будущее: мультисобытийные триггеры и запуск DAG из триггеров
Airflow уже задействует триггеры для пробуждения задач. На горизонте - расширения:
- Мультисобытийные триггеры (поддержка нескольких TriggerEvent до завершения).
- Запуск DAG по событию из триггера - полноценная событийная оркестрация на базе Triggerer.
- Более глубокая интеграция с облачными очередями и шинами событий (SQS, Pub/Sub, EventBridge) в виде стандартных триггеров.
- Расширенные политики шардирования и балансировки Triggerer.
Эти направления усилят позицию Airflow как оркестратора с нативной событийной моделью.
Заключение и рекомендации по внедрению в производственной среде
Отсроченные операторы и триггеры в Apache Airflow - это качественный сдвиг к асинхронной, событийно-ориентированной оркестрации. Архитектура с Triggerer позволяет дешево и масштабно ожидать внешние условия, снижая нагрузку на воркеры и БД метаданных и улучшая SLA.
Рекомендации:
- Стандартизируйте подход к состоянию: stateless + JSON + внешние артефакты.
- Институционализируйте best practices async-кодирования и ревью триггеров.
- Включите метрики Triggerer в наблюдаемость, задайте SLO на задержку событий.
- Проводите целенаправленную миграцию с poke/reschedule на deferrable.
- Обеспечьте HA Triggerer и процедуру его безопасного перезапуска при изменении кода триггеров.
Правильная инженерная дисциплина и эксплуатационная зрелость превращают deferrable-подход в надёжный стандарт для корпоративной оркестрации.
Вопрос-Ответ:
-
Вопрос: Зачем нужны отсроченные операторы в Airflow?
Ответ: Чтобы вынести ожидание условий и времени из воркеров в асинхронный процесс Triggerer, снизив стоимость и улучшив масштабируемость. -
Вопрос: Почему триггеры не могут быть блокирующими?
Ответ: Triggerer использует один event loop; блокирующий вызов замораживает его целиком и задерживает обработку всех триггеров. -
Вопрос: Как передать состояние между отсрочками?
Ответ: Через kwargs в defer и полезную нагрузку event; большие данные хранить во внешнем хранилище и передавать ссылки. -
Вопрос: Что делает self.defer внутри оператора?
Ответ: Сериализует триггер в БД, выбрасывает TaskDeferred, освобождая воркер, и указывает метод для последующего возобновления. -
Вопрос: Как соотносится execution_timeout с отсрочками?
Ответ: Это суммарный таймаут на всю «жизнь» задачи, включая периоды ожиданий между отсрочками. -
Вопрос: Какие требования к методу run у триггера?
Ответ: Он должен быть async, неблокирующим, генерировать (yield) события, быть идемпотентным и сериализуемым через serialize. -
Вопрос: Как обеспечить высокую доступность триггеров?
Ответ: Запускать несколько инстансов Triggerer, хранить триггеры stateless, полагаться на перераспределение через БД и перезапуск при изменении кода. -
Вопрос: Какие основные антипаттерны при разработке триггеров?
Ответ: time.sleep и синхронный I/O в run, хранение состояния в self, большие payload в event, чрезмерный частый polling без нужды.



