Планировщик задач

1 июля 2026 · ~13 мин чтения

концепция автоматизация расписание cron инфраструктура

Планировщик задач

Планировщик задач (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)

Из этой простой петли растёт вся сложность реальных систем: логирование, повторный запуск при ошибке, обработка «пропущенных» запусков, часовые пояса, зависимости между задачами.

Важно отличать планировщик задач от соседних понятий, которые часто путают.

Планировщик всегда живёт как демон — процесс, который стартует один раз и работает всё время, пока работает система. Если демон упал — задачи не запустятся. Отсюда следующее правило: сам планировщик должен рестартовать себя. На 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 даёт три метода:

Ключевое отличие такого «встроенного» планировщика от cron/launchd: он живёт внутри процесса бота. Умер процесс — умерли задачи. Поэтому производственные системы часто держат бота под systemd/launchd с автоперезапуском, а критичное расписание всё равно резервируют через внешний cron.

Где встречается в обычной жизни

Где встречается в IT и бизнесе

Кто пользуется

Планировщиками пользуются буквально все системы, где есть повторяющиеся задачи. Оценки масштабов:

Альтернативы и конкуренты

Когда НЕ стоит использовать

Главная грабля — идемпотентность

Планировщик может по разным причинам запустить твою задачу два раза подряд (перезапуск демона, ручной трigger, догон после сбоя). Если задача пишет в БД или отправляет уведомление, каждый её запуск должен уметь безопасно повториться: проверить «а не сделано ли уже сегодня», использовать идемпотентный ключ. Наш daily-digest, например, перед генерацией проверяет — есть ли файл за эту дату; если есть — тихо выходит.

Связанные понятия

Литература и источники

Где встретилось у меня

Вчера в двух проектах одновременно всплывали планировщики. В боте ТЕНЬ (обзвон номеров под кампании застройщиков) переделывал job_queue python-telegram-bot: снял дневной watch номеров, добавил новую run_daily-задачу с утренней сводкой по остаткам заданий и тегированием ответственных. И параллельно наш собственный daily-digest — тот, что ты сейчас читаешь — сам ежедневно стартует через launchd в 9:00 МСК. Оба случая — про одно и то же понятие с разной реализацией.

Планировщик и очередь — разные вещи

Легко путать: и то и то «где-то там сзади дёргает мой код». Но триггер разный. Планировщик срабатывает по часам («в 9:00»). Очередь — по появлению работы («пришло сообщение»). Часто они работают в паре: планировщик кладёт задание в очередь по расписанию, воркеры очереди его забирают. Это разделение — отдельный процесс планирует, отдельный исполняет — стандарт для больших систем.

Краткое резюме