Skip to content
PПромтбук
RUEN
04Архитектура

Дизайн cron jobs: scheduling, overlap, observability

Cron vs scheduler service, что делать с overlap'ами, как избежать missed runs, observability и monitoring, failure handling.

Действуй как senior infra инженер. Спроектируй систему scheduled jobs: {{jobs}}. Инфраструктура: {{stack}}.

1. Выбор механизма

ОпцияКогда братьПодвох
crontab на VMОдин сервер, < 5 jobs, без HAСервер упал → missed runs, не знаешь
k8s CronJobk8s уже есть, нужно HAСложно с long-running, missed-run policy надо настраивать
Cloud scheduler (EventBridge / Cloud Scheduler)Serverless / managedVendor lock-in, debug сложнее
Sidekiq-cron / Celery beat / app-level schedulerУже есть job queueТолько один scheduler instance (single point of failure если без leader election)
Temporal / AirflowDAG, retry chain, > 20 jobs с зависимостямиOperational overhead, не для одиночных tasks

Дефолт для production: managed cloud scheduler или k8s CronJob + leader election (если бизнес-логика в коде).

2. Overlap protection

Что делать если предыдущий run ещё идёт, а пришло время следующего?

  • forbid — пропустить новый запуск. Дефолт для idempotent jobs где главное не перегрузить.
  • replace — убить старый, запустить новый. Только если новый запуск делает то же, что старый, но "actual".
  • allow — параллельно. Только если jobs действительно независимы (по data partition).

Реализация:

  • k8s: concurrencyPolicy: Forbid.
  • Application-level: distributed lock (Redis SETNX с TTL = expected duration × 2). Lock per job name.
  • TTL обязателен — иначе зависший job блокирует все будущие runs forever.

3. Missed runs

Что если scheduler был down 2 часа и пропустил 4 запуска "каждые 30 минут"?

  • Catch-up policy явно прописана. Запускать всё пропущенное сразу — может перегрузить систему. Запускать только последний — потерять данные. По умолчанию — запустить один catch-up + alert.
  • startingDeadlineSeconds в k8s CronJob: если scheduler опоздал > N секунд, missed run считается потерянным, не запускается.
  • Для critical jobs — отдельный monitor (см. п. 5) который алертит если job не запускался > expected interval × 1.5.

4. Failure handling

  • Идемпотентность. Каждый job спроектирован так, что повторный запуск с теми же входами безопасен. Без этого retry — рулетка.
  • Retry policy: k8s CronJob backoffLimit: 3 + exponential backoff в коде job'а на внешние вызовы (network/DB).
  • Permanent failure: после exhausted retries — alert + state failed_at в БД. Не молчаливо.
  • Partial failure для batch job: обрабатываемые записи помечать индивидуально как processed/failed, чтобы следующий run забрал только failed без повтора processed.

5. Observability

  • Heartbeat / dead man's switch. Сервис (Healthchecks.io, Cronitor, или свой): job в начале делает POST /start, в конце — POST /finish. Если /finish не пришёл за expected window — алерт. Если /start не пришёл к ожидаемому моменту — алерт (missed run).
  • Metrics per job: last_started_at, last_finished_at, last_duration_seconds, last_status, consecutive_failures.
  • Structured logs: job_name, run_id (UUID), started_at, status в каждой строке. Чтобы grep'ом построить timeline одного run'а.
  • Dashboard: грид всех jobs со статусом последнего run + время с последнего успеха. На красный — Pager.

6. Resource limits

  • CPU/memory limits в k8s/ECS. Чтобы один сошедший с ума job не уронил соседей.
  • Timeout на сам job: activeDeadlineSeconds в k8s. Лучше упасть на 30-й минуте с ошибкой, чем висеть 6 часов.
  • Stagger time для тяжёлых jobs: не запускать 10 jobs ровно в 00:00 UTC — DB / external API не справится. Размазать на 0/5/10/15/...

Формат вывода

## Recommended mechanism
<выбор + почему>

## Per-job spec
| Name | Schedule | Concurrency | Timeout | Idempotent? | Monitor |
| daily-cleanup | 0 3 * * * | Forbid | 30m | yes | hc-cleanup |
...

## Distributed lock pseudocode (если app-level)

Anti-patterns

  • ❌ Cron на одной VM для бизнес-критичных jobs → сервер упал → ничего не запустилось → молча.
  • ❌ Без overlap protection → два инстанса делают одно и то же → дублирование данных / коррупция.
  • ❌ Lock без TTL → один зависший job → все future runs заблокированы навсегда.
  • ❌ Без heartbeat monitor → "почему отчёты не приходят неделю?" обнаруживается через клиента.
  • ❌ Не идемпотентный job + retry → каждый retry удваивает damage.
  • ❌ 10 jobs в 00:00 UTC → thundering herd → DB колом.
  • ❌ activeDeadline > expected duration × 10 → зависший job висит часами, никто не знает.
  • ❌ Catch-up "выполнить всё пропущенное сразу" без лимита → если scheduler стоял сутки → 48 запусков параллельно → overload.
  • ❌ Cron-выражение без комментария что оно значит → */13 * * * * через год никто не вспомнит зачем.
К подразделу «Архитектура»
Похожие промты