Планировщик задач
Планировщик задач
Планировщик задач (task scheduler) — программный компонент, который запускает заранее описанные команды в заданные моменты времени или через заданные интервалы, без ручного триггера от человека.
История
Идея запускать программы «по будильнику» появилась почти одновременно с многозадачными операционными системами. Хронология выглядит так.
1975, США, Bell Labs. Кен Томпсон (Ken Thompson) — один из создателей Unix — написал первую версию cron для Unix v7. Демон делал одно: раз в минуту читал файл со списком команд и запускал те, чей час совпадал с текущим. Ни секундной точности, ни логов, ни правил «если система была выключена — догони» — просто цикл.
1987, США, DEC. Пол Викси (Paul Vixie), инженер DEC (позднее — создатель BIND), выпустил Vixie cron. Он ввёл индивидуальные crontab для каждого пользователя, синтаксис из пяти полей (минута, час, день, месяц, день недели), переменные окружения и логирование запусков. Именно Vixie cron стал стандартом де-факто во всех Linux/BSD-дистрибутивах — и практически весь cron, который ты видишь на серверах сегодня, — это его прямой потомок.
1995–2000, Redmond. Microsoft добавила Windows Task Scheduler — сначала как отдельный сервис mstask в Windows 95, потом переработала в Windows Vista (2007) уже с XML-описанием заданий и триггерами не только по времени, но и по событиям системы.
2005, Купертино. Apple выпустила launchd (автор — Dave Zarzycki) в macOS 10.4 Tiger. Идея: одна демон-программа заменяет и cron, и init, и inetd, и SystemStarter. Ключевое отличие от cron — если ноутбук был выключен, launchd запустит задание, когда сможет (если это разрешено в plist).
2010-е. Появились планировщики для распределённых систем: Celery Beat для Python, systemd timers как замена cron в systemd-инфраструктуре (~2010), Airflow (Airbnb, 2014) с DAG-моделью, Kubernetes CronJob (2017), облачные — AWS EventBridge Scheduler, Yandex Cloud Managed Scheduler, Google Cloud Scheduler.
К 2026 году cron по-прежнему работает на миллиардах Unix-хостов. Его синтаксис из пяти полей стал таким же стандартом, как, например, регулярные выражения — им пользуются даже в системах, где сам cron не запущен (Kubernetes CronJob использует cron-выражение как строку).
Что это такое
Планировщик задач держит внутри две вещи: список заданий (команда + расписание + опции) и часы. Он постоянно проверяет: «есть ли сейчас задача, которую пора запустить?» Если есть — запускает.
Устройство минимального планировщика — считанные строки псевдокода:
while True:
now = current_time()
for job in schedule:
if job.should_run_at(now):
fork_and_exec(job.command)
sleep(60)
Из этой простой петли растёт вся сложность реальных систем: логирование, повторный запуск при ошибке, обработка «пропущенных» запусков, часовые пояса, зависимости между задачами.
Важно отличать планировщик задач от соседних понятий, которые часто путают.
- Task queue (очередь задач) — например, Celery, RabbitMQ, Redis Queue. Это про «есть работа — раздай воркерам». Триггер здесь не время, а поступление задания в очередь. Планировщик и очередь часто работают в паре: планировщик кладёт задачу в очередь по расписанию, воркеры её забирают.
- Workflow orchestrator (оркестратор процессов) — Airflow, Prefect, Temporal. Отличие от планировщика: он умеет описывать граф зависимостей задач («сначала выгрузи, потом преобразуй, потом загрузи»), следить за состоянием каждой, ретраить, ждать событий. Планировщик отвечает только за «когда стартовать».
- Event-driven система — реагирует не на часы, а на события (пришёл вебхук, поступило сообщение в брокер). Это ортогональный подход.
- Cron — конкретная реализация планировщика на Unix, но в разговорной речи «крон» часто означает «любой планировщик по расписанию».
Планировщик всегда живёт как демон — процесс, который стартует один раз и работает всё время, пока работает система. Если демон упал — задачи не запустятся. Отсюда следующее правило: сам планировщик должен рестартовать себя. На Linux это делает systemd (Restart=always), на macOS — launchd (по конструкции, он сам — init-система), на Kubernetes — controller следит за подом.
Аналогии из жизни
Будильник в телефоне. Ты вечером ставишь «7:00 — вставать». Ночью телефон где-то в тумбочке следит за часами, а в 7:00 звенит. Работает.
Где ломается: телефон разрядился — не звенит. Будильник — это тот самый цикл while True: check_time(); sleep(), и если процесс умирает — задачи не запустятся. Именно поэтому и cron, и launchd — это отдельные демоны, поднятые системой при загрузке.
Расписание автобусов. «Каждый день в 7:15 автобус №23 выходит из парка». Диспетчер знает расписание, водитель выполняет. Работает.
Где ломается: пробка. Автобус выехал вовремя, но приехал позже — параллельно уже пора отправить следующий рейс, и они «наплывают друг на друга». В планировщике это точный аналог: если задача крутится дольше, чем период между её запусками, планировщик может её запустить второй раз, пока первая ещё не закончилась. Это классическая ошибка — надо либо явно ставить блокировку, либо считать «уже одна работает — пропусти».
Автополив в саду. Контроллер поливает клумбу в 6:00 и 19:00 каждый день. Работает.
Где ломается: датчик влаги сломан, а полив всё равно случается — во время дождя. Планировщик по времени не знает контекста: если в 9:00 надо запустить ETL, а внешняя система ночью упала, cron всё равно запустит скрипт и получит пять минут ошибок в лог. Отсюда правило: любую задачу по расписанию надо делать идемпотентной — то есть безопасной для повторных и «мимошедших» запусков.
Как это работает
Разберём типичный жизненный цикл задачи в планировщике на примере cron.
Шаг 1. Регистрация. Пользователь пишет строку в свой файл crontab:
0 9 * * 1-5 /home/pavel/report.sh
Пять полей — это минута час день_месяца месяц день_недели. Здесь: «в 09:00, любой день месяца, любой месяц, только с понедельника по пятницу». Астериск (*) означает «любое значение». Через запятую — список (1,15), через дефис — диапазон (1-5), через слэш — шаг (*/5 = каждые 5 единиц).
Пользователь сохраняет crontab — специальная утилита проверяет синтаксис и подкладывает файл в /var/spool/cron/crontabs/pavel.
Шаг 2. Демон читает. Демон crond (в некоторых дистрибутивах — cron) запущен как фоновый процесс. Он периодически перечитывает crontab-файлы или подписан на их изменение через inotify. У него в памяти таблица: «в 09:00 понедельника—пятницы запустить /home/pavel/report.sh от имени pavel».
Шаг 3. Матчинг времени. Раз в минуту (это фиксировано в классическом cron — отсюда невозможность точности до секунды) демон берёт текущее время, обходит все задания и проверяет: подходит ли текущий момент под маску расписания?
Шаг 4. Запуск. Если подходит — cron делает fork (создаёт дочерний процесс), в нём setuid на нужного пользователя и exec на команду. Окружение задаётся минимальным: пустой PATH, никаких переменных из shell пользователя. Это одна из главных грабель — если скрипт где-то в шелле работает, а в cron не работает, обычно причина в отсутствующих переменных окружения.
Шаг 5. Логи. По умолчанию cron не сохраняет вывод скрипта. Вместо этого он отправляет stdout/stderr письмом на локальный email пользователя. На современных серверах локальной почты обычно нет — вывод просто теряется. Правильный подход: в самой строке crontab делать редирект: >> /var/log/report.log 2>&1.
Аналогичный жизненный цикл у launchd на macOS, но с двумя ключевыми отличиями. Во-первых, launchd умеет «догонять» пропущенные запуски: если задача должна была стартовать в 9:00, а ноутбук был выключен, launchd запустит её, как только ноут проснётся (если в plist стоит RunAtLoad или ключ поведения при пропуске). Во-вторых, launchd — единый init/scheduler, поэтому он же и следит за живостью процесса: если задача упала, а в plist указан KeepAlive, launchd перезапустит её. Cron этого не умеет.
Для python-telegram-bot планировщик встроен в саму библиотеку как JobQueue. Внутри — это цикл в asyncio, который держит heapq с ближайшими задачами и asyncio.sleep() до момента запуска. API даёт три метода:
run_once(callback, when)— один раз в указанный момент;run_repeating(callback, interval, first=...)— раз в N секунд, повторно;run_daily(callback, time=...)— каждый день в указанное время (с учётом timezone).
Ключевое отличие такого «встроенного» планировщика от cron/launchd: он живёт внутри процесса бота. Умер процесс — умерли задачи. Поэтому производственные системы часто держат бота под systemd/launchd с автоперезапуском, а критичное расписание всё равно резервируют через внешний cron.
Где встречается в обычной жизни
- Резервные копии в облако. Google Photos, iCloud, Яндекс.Диск загружают свежие файлы ночью, когда телефон стоит на зарядке. Внутри — планировщик, привязанный к «домашнему Wi-Fi + зарядка + ночь».
- Автоматические обновления системы. macOS «Установить обновления в 3:00 ночи», Windows Update Tuesday — всё это Task Scheduler под капотом.
- Умный дом. «Включить свет на кухне в 06:45», «выключить обогреватель в 22:00» — планировщик внутри хаба.
- Банковские уведомления. «Ваш платёж по подписке пройдёт 15-го числа» — внутри биллинга крон-задача, которая раз в сутки обходит подписки.
- Ежемесячная синхронизация календаря. «Напоминание за 3 дня до дня рождения». Технически — таймер + периодическая переиндексация базы контактов.
Где встречается в IT и бизнесе
- Ротация логов.
logrotateзапускается раз в сутки и режет накопленные логи, чтобы диск не заполнился. Без него любой продакшн умирает за месяц. - ETL и дата-пайплайны. «В 3:00 ночи выгрузить данные из CRM, преобразовать, залить в BI» — классический Airflow-сценарий.
- Отчёты руководству. «Каждый понедельник в 08:00 директору по продажам приходит письмо с воронкой за неделю». За этим — либо cron, либо продуктовая интеграция вроде Zapier с scheduling.
- Здоровье инфраструктуры. Health-check раз в 30 секунд, cleanup «мёртвых» сессий раз в час, пересборка кэша раз в сутки.
- Продуктовые триггеры. «Напомнить пользователю о брошенной корзине через 24 часа», «выслать анкету через 3 дня после регистрации» — планировщик + очередь.
Кто пользуется
Планировщиками пользуются буквально все системы, где есть повторяющиеся задачи. Оценки масштабов:
- Cron установлен по умолчанию во всех крупных Linux-дистрибутивах (Ubuntu, Debian, RHEL, CentOS, Amazon Linux, Alpine). На конец 2020-х это порядка нескольких миллиардов активных хостов, включая контейнеры, где часто используется
cron-контейнер как sidecar. Точных цифр я не знаю — оценки очень грубые. - launchd — на каждом маке (macOS с 2005 года) и на всех iOS/iPadOS-устройствах в качестве системного менеджера сервисов. То есть ~2 миллиарда устройств Apple.
- Kubernetes CronJob — стандарт де-факто для планировщика в контейнерных кластерах. Все крупные облака (AWS EKS, Google GKE, Azure AKS, Yandex Managed Kubernetes) поддерживают его нативно.
- Google Borg / Batch — внутренний планировщик Google, крутит все batch-джобы поисковика, GMail, YouTube (индексация, пересчёт рекомендаций).
- Airflow — Airbnb (родоначальник), Uber, Slack, Booking.com, Тинькофф, Яндекс, Ozon.
- Celery Beat — Instagram, Mozilla, Robinhood, множество Django-приложений.
- Cron/systemd timers для банков и телекомов — критично: закрытие торгового дня, отправка отчётов регуляторам, ночные сверки. Например, все российские банки по требованию ЦБ отправляют отчёты в фиксированное время — за этим стоят cron-задачи с многослойным ретраем.
Альтернативы и конкуренты
-
Cron / Vixie cron.
Плюсы: везде установлен, простой синтаксис, минимум зависимостей.
Минусы: не логирует, минимальная точность — минута, нет ретраев, «догона» пропущенных. -
systemd timers.
Плюсы: интегрированы в systemd (журнал, зависимости, ретраи), гибкий синтаксис (OnCalendar=Mon..Fri 09:00), поддерживают «догон» черезPersistent=true.
Минусы: Linux-only, требуют двух файлов (service + timer), синтаксис для новичка тяжеловат. -
launchd.
Плюсы: единый интерфейс для сервисов и расписания, «догоняет» пропущенные запуски, следит за живостью процесса.
Минусы: macOS/iOS-only, plist-XML многословный, диагностика черезlaunchctlтребует привычки. -
Kubernetes CronJob.
Плюсы: масштабирование, декларативность (YAML в git), встроенные повторы и failure-политики.
Минусы: нужен кластер, накладные расходы на pod (~секунды старта), плохо подходит для задач «раз в минуту». -
Airflow / Prefect / Dagster.
Плюсы: DAG-модель, UI с историей запусков, ретраи, зависимости, backfill.
Минусы: тяжёлые (нужны БД, воркеры, веб-UI), избыточны для одной ежедневной задачи. -
Celery Beat.
Плюсы: интегрирован в Celery, работает через тот же брокер (Redis/RabbitMQ), удобно для Python-приложений.
Минусы: нужен брокер, без надёжной блокировки одна задача может запуститься на нескольких воркерах. -
AWS EventBridge Scheduler / Cloud Scheduler.
Плюсы: managed, платишь только за запуски, интеграция с очередями и лямбдами облака.
Минусы: vendor lock-in, задержка старта в облаке — от секунд до минут. -
JobQueue (python-telegram-bot).
Плюсы: встроен в бота, без внешних зависимостей.
Минусы: живёт в процессе — процесс упал, задачи умерли.
Когда НЕ стоит использовать
- Реакция на событие в реальном времени. Если задачу надо запустить «когда пришло сообщение в очередь» или «когда пользователь нажал кнопку», планировщик — плохой выбор. Используй webhook, event listener, message queue.
- Задачи чаще, чем раз в минуту. У cron минимальный шаг — минута. Если нужен запуск каждые 5 секунд — это либо цикл в приложении, либо специализированный планировщик (systemd timer с
OnUnitActiveSec=5s, JobQueue). - Сложные зависимости между задачами. Если задача B должна стартовать только после успешного завершения A, а C — только после B и D, — это не про cron. Это про workflow-оркестратор (Airflow, Prefect).
- Разработка без наблюдаемости. Cron по умолчанию тихий. Если ты не готов сразу поставить логирование и алертинг на failure, лучше вообще не автоматизировать — молчаливо ломающийся крон опаснее, чем ручной запуск.
Главная грабля — идемпотентность
Планировщик может по разным причинам запустить твою задачу два раза подряд (перезапуск демона, ручной трigger, догон после сбоя). Если задача пишет в БД или отправляет уведомление, каждый её запуск должен уметь безопасно повториться: проверить «а не сделано ли уже сегодня», использовать идемпотентный ключ. Наш daily-digest, например, перед генерацией проверяет — есть ли файл за эту дату; если есть — тихо выходит.
Связанные понятия
- Демон (daemon) — фоновый процесс, работает всё время без интерактивного пользователя. Планировщик всегда демон.
- Cron — Unix-реализация планировщика (1975, Vixie cron с 1987).
- launchd — macOS/iOS-эквивалент cron + init-система.
- systemd timer — Linux-планировщик как часть systemd; альтернатива cron с богаче логами.
- Kubernetes CronJob — cron внутри кластера, объект Kubernetes API.
- Task queue — очередь задач (Celery, RabbitMQ), другой паттерн: триггер не время, а поступление задания.
- Workflow orchestrator — Airflow, Prefect: планировщик + графы зависимостей + повторы.
- Идемпотентность — свойство, при котором повторный запуск не даёт побочных эффектов. Обязательно для задач по расписанию.
- ETL — Extract-Transform-Load, класс дата-пайплайнов, обычно на планировщике.
- DAG — Directed Acyclic Graph, граф зависимостей задач, модель Airflow.
Литература и источники
- Wikipedia (en): Cron — https://en.wikipedia.org/wiki/Cron. Хорошая справка по истории и синтаксису.
man 5 crontabв любом Linux — официальная документация формата crontab.man 5 systemd.timer— synтаксис systemd-таймеров, лучший справочник.- Apple TN2083 «Daemons and Agents» — техноут Apple о launchd. Искать в архиве developer.apple.com по названию.
- «The UNIX Programming Environment», Brian Kernighan & Rob Pike, 1984 (en). Классика про философию Unix-инструментов, включая cron.
- «Designing Data-Intensive Applications», Martin Kleppmann, 2017 (en; есть перевод «Высоконагруженные приложения», 2018 ru). Разделы про batch-обработку и планирование задач в распределённых системах.
- Kubernetes CronJob docs — https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/. Официальная документация k8s CronJob.
- Vixie cron исходники — на GitHub есть форки под "vixie-cron"; интересно посмотреть, во что укладывается 40-летний стандарт (несколько тысяч строк C).
Где встретилось у меня
Вчера в двух проектах одновременно всплывали планировщики. В боте ТЕНЬ (обзвон номеров под кампании застройщиков) переделывал job_queue python-telegram-bot: снял дневной watch номеров, добавил новую run_daily-задачу с утренней сводкой по остаткам заданий и тегированием ответственных. И параллельно наш собственный daily-digest — тот, что ты сейчас читаешь — сам ежедневно стартует через launchd в 9:00 МСК. Оба случая — про одно и то же понятие с разной реализацией.
Планировщик и очередь — разные вещи
Легко путать: и то и то «где-то там сзади дёргает мой код». Но триггер разный. Планировщик срабатывает по часам («в 9:00»). Очередь — по появлению работы («пришло сообщение»). Часто они работают в паре: планировщик кладёт задание в очередь по расписанию, воркеры очереди его забирают. Это разделение — отдельный процесс планирует, отдельный исполняет — стандарт для больших систем.
Краткое резюме
- Планировщик задач запускает команды в заданное время без человека. Ядро — цикл «читать часы, матчить расписание, форкать процесс».
- Стандарт Unix — cron (Кен Томпсон, 1975; Пол Викси, 1987), пять полей
минута час день месяц день_недели. Живёт на миллиардах серверов. - В macOS — launchd (2005), умнее cron: логирует, догоняет пропущенные запуски, следит за живостью.
- В Linux рядом с cron — systemd timers, у них лучше логи и синтаксис.
- В кластерах — Kubernetes CronJob (тот же cron-синтаксис в YAML), в облаках — managed-планировщики (EventBridge, Cloud Scheduler).
- Главная грабля — идемпотентность: любая задача может запуститься дважды, пиши код так, будто это норма.
- Отличай от очереди задач (триггер — работа) и оркестратора (граф зависимостей). Всё это часто живёт вместе, но решает разные вопросы.