Агенты по событиям или по расписанию: что выбрать
Cron запускает задачу раз в пять минут и в 99% случаев дергает пустую очередь. Событийная модель реагирует за миллисекунды, но платит за это сложностью. Разбираем, где что уместно.

Классический сценарий: бэкенд-агент раз в минуту опрашивает базу — не появилось ли новых заказов. При тысяче запросов в сутки это 1440 запусков в день, из которых полезными оказываются десятки. Остальное — холостой ход, который жрёт коннекты к БД и место в логах. Альтернатива — подписаться на событие «создан заказ» и реагировать только когда оно реально произошло. Обе модели работают, но выбор между ними определяет архитектуру, счёт за облако и то, насколько тяжело будет отлаживать систему через полгода.
Две модели в двух словах
Агент по расписанию (scheduled, polling) просыпается по таймеру — cron, планировщик очереди, systemd timer — и выполняет работу вне зависимости от того, есть она или нет. Он опрашивает источник: новые строки, файлы в папке, статусы во внешнем API.
Событийно-управляемый агент (event-driven) ничего не опрашивает. Он подписан на поток событий — сообщения в брокере (Kafka, RabbitMQ, NATS), вебхуки, изменения в БД через CDC¹ — и запускается только тогда, когда событие приходит.
Разница не в том, «какая технология лучше». Разница в том, кто инициирует работу: часы или сама система.
Где по расписанию честно выигрывает
- Задача сама по себе периодическая. Ночной отчёт в 3:00, ротация логов, пересчёт агрегатов раз в час — тут событие «наступило время» и есть суть задачи.
- Источник не умеет отдавать события. Внешний API без вебхуков, legacy-база без CDC, папка на FTP. Опрос — единственный вариант.
- Батчинг дешевле. Обработать 10 000 записей одним запросом раз в час выгоднее, чем дёргать обработчик 10 000 раз по событию.
- Допустима задержка. Если бизнес переживёт лаг в минуту-две, polling проще в эксплуатации и его легче понять новому человеку в команде.
Где выигрывает событийная модель
- Реакция нужна сейчас. Списание с карты, уведомление о доставке, антифрод — задержка в минуту здесь стоит денег или клиента.
- События редкие и непредсказуемые. Опрашивать раз в секунду ради двух срабатываний в день — трата ресурсов на пустоту.
- Нужна слабая связанность. Продюсер публикует событие и не знает, сколько подписчиков его обработают. Добавить новый обработчик можно без правки источника.
Цена вопроса: где утекают ресурсы и нервы
Polling кажется бесплатным, пока не посчитаешь. Агент, опрашивающий БД раз в 5 секунд, — это 17 280 запросов в сутки на один инстанс. При десяти инстансах и managed-базе с оплатой за нагрузку холостые селекты превращаются во вполне реальную строку в счёте.
Событийная модель платит другим — сложностью. Появляется брокер, который надо мониторить, масштабировать и бэкапить. Появляются проблемы, которых у cron нет в принципе.
Cron ломается предсказуемо: не сработал — не сработал. Событийная система ломается хитро: сработала дважды, сработала не в том порядке, потеряла сообщение под нагрузкой. Отлаживать это дороже, чем писать.
Три ловушки event-driven, о которых узнают в проде
- Доставка «хотя бы один раз». Большинство брокеров гарантируют at-least-once, а не exactly-once. Значит, обработчик получит одно и то же событие дважды, и он обязан быть идемпотентным² — иначе спишете деньги дважды.
- Порядок сообщений. Событие «заказ отменён» может прийти раньше «заказ создан», если они летят по разным партициям. Порядок гарантируется только внутри одного ключа партиционирования, и это надо проектировать заранее.
- Обратное давление. Если продюсер выдаёт 10 000 событий в секунду, а обработчик тянет 1000, очередь растёт, пока не кончится память или диск. Нужны лимиты, dead-letter-очередь и алерты.
Сравнение по ключевым параметрам
| Параметр | По расписанию | Событийно |
|---|---|---|
| Задержка реакции | от интервала опроса | миллисекунды |
| Нагрузка при простое | постоянная (холостые опросы) | близка к нулю |
| Сложность инфраструктуры | низкая (cron/timer) | высокая (брокер, мониторинг) |
| Отладка | простая, воспроизводимая | сложная (гонки, дубли, порядок) |
| Батчинг | естественный | требует агрегации |
| Масштабирование | вертикальное или шардирование по времени | горизонтальное по партициям |
Гибрид как норма, а не компромисс
В реальных системах два подхода живут вместе, и это нормально. Событийный конвейер обрабатывает то, что должно реагировать быстро. Планировщик поверх него подчищает то, что могло потеряться, и делает периодические сверки.
Типичный паттерн — reconciliation job: событийный обработчик делает основную работу, а раз в 15 минут по расписанию запускается сверка, которая ищет расхождения между «что должно было произойти» и «что реально в базе». Она ловит потерянные и недоставленные события — те самые случаи, которые at-least-once не покрывает при полном отказе консьюмера.
Мини-пример: планировщик как страховка
Псевдо-логика сверочного агента, который дополняет событийный поток, а не заменяет его:
# запускается по cron раз в 15 минут
def reconcile():
# заказы, оплаченные, но без запущенной доставки
stuck = db.query("""
SELECT id FROM orders
WHERE status = 'paid'
AND shipment_id IS NULL
AND paid_at < now() - interval '10 minutes'
""")
for order in stuck:
# то же событие, что должен был отправить основной поток
publish_event("order.paid", order_id=order.id, source="reconcile")
Обработчик события идемпотентен, поэтому повторная публикация из сверки безопасна: если доставка уже запущена, событие ничего не сломает. Так гибрид даёт скорость событийной модели и надёжность периодической проверки.
Как выбрать под конкретную задачу
- Спросите про допустимую задержку. Секунды — почти наверняка события. Минуты и больше — расписание проще и дешевле.
- Оцените частоту. Редкие и непредсказуемые события — за event-driven. Плотный равномерный поток — polling с батчингом может выиграть по стоимости.
- Проверьте источник. Нет вебхуков и CDC — выбора нет, только опрос.
- Посчитайте команду. Если некому держать брокер в проде, cron, который просто работает, честнее модной архитектуры, которую никто не понимает.
¹ CDC (Change Data Capture) — механизм, который превращает изменения в БД (INSERT, UPDATE, DELETE) в поток событий, обычно читая журнал транзакций. Инструменты: Debezium, встроенные логические слоты PostgreSQL.
² Идемпотентность — свойство операции давать одинаковый результат при повторном выполнении. Обработчик с идемпотентностью можно безопасно вызвать дважды одним и тем же событием.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: Apache Kafka Documentation — Delivery semantics, Debezium Documentation — Change Data Capture
Частые вопросы
Cron устарел? Стоит ли всё переводить на события?
Что такое at-least-once и почему это моя проблема?
Можно ли обойтись без брокера сообщений в событийной модели?
Как понять, что polling создаёт лишнюю нагрузку?
Что такое reconciliation job и обязательно ли он нужен?
Как гарантировать порядок событий?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.