Skip to content

필사 모드: Cron & 스케줄된 작업 2026 — systemd timers / Vercel Cron / AWS EventBridge Scheduler / k8s CronJobs / BullMQ / Sidekiq / Temporal 심층 가이드

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

프롤로그 — "그냥 cron 돌리면 되잖아요?" 가 위험해진 시대

2026년 어느 팀의 분석.

신입: "매일 오전 9시에 리포트 메일을 보내야 해요. 그냥 cron 돌리면 되죠?"

시니어: "어디서? 어떻게 모니터링하지? 실패하면? 두 번 돌면? 박스가 죽으면?"

신입: "...아."

이 짧은 대화에 2026년 스케줄링의 모든 게 들어있다. cron은 50년 동안 작동했지만, 클라우드 네이티브 시대에 들어선 뒤 단순한 "정해진 시간에 명령 실행"으로는 더 이상 충분하지 않다. 박스가 죽었을 때, 작업이 두 번 도는 동시 실행 문제, 실패 알림, 재시도, 분산 잠금, 멱등성, 시간대, DST(서머타임) 같은 것이 모두 운영의 함정이 됐다.

2026년 현재, 스케줄된 작업은 **네 개의 큰 카테고리**로 갈라진다. 시스템 레벨(cron, systemd timers, fcron, anacron), 클라우드 매니지드(Vercel Cron, EventBridge Scheduler, Cloudflare Workers, GitHub Actions schedule), 분산 백그라운드 큐(BullMQ, Sidekiq, Celery Beat), 워크플로 엔진(Airflow, Dagster, Prefect, Temporal, Inngest, Trigger.dev, Hatchet). 그리고 그 위에 모니터링(Healthchecks.io, Cronitor.io)이 얹힌다.

이 글은 그 전체 지형을 한 번에 정리한다. 클래식 cron 5필드부터 systemd timers, Vercel Cron, AWS EventBridge Scheduler(2022년 11월 CloudWatch Events Rules 대체), k8s CronJobs, BullMQ, Sidekiq, Celery Beat, Temporal Schedules, Hatchet, Quartz, Hangfire, fcron, anacron, Nomad periodic jobs, 모니터링 SaaS까지. 한국의 토스·카카오, 일본의 메르카리가 무엇을 어떻게 쓰는지도 본다.

1장 · 2026년 cron 지도 — 시스템 / 클라우드 / 큐 / 워크플로 4 분류

먼저 큰 그림. "스케줄된 작업"이라는 한 단어 아래에 매우 다른 도구들이 모여있다.

| 분류 | 대표 도구 | 적합한 케이스 |

| --- | --- | --- |

| 시스템 cron | crond, systemd timers, fcron, anacron | 한 박스의 백그라운드 정리, 로그 로테이션 |

| 클라우드 매니지드 | Vercel Cron, AWS EventBridge Scheduler, Cloudflare Workers Cron Triggers, GitHub Actions schedule, GitLab schedule pipelines | 서버리스, CI/CD, 인프라 자동화 |

| 분산 백그라운드 큐 | BullMQ, Sidekiq, Celery Beat, RQ, dramatiq, APScheduler, Quartz, Spring @Scheduled, Hangfire | 앱 내부의 작업 큐 + 스케줄 |

| 워크플로 엔진 | Airflow, Dagster, Prefect, Temporal Schedules, Inngest Crons, Trigger.dev v3, Hatchet | 다단계 데이터 파이프라인, 신뢰성 워크플로 |

| 컨테이너 | k8s CronJobs, Nomad periodic jobs | 컨테이너 기반 배치 |

| 모니터링 | Healthchecks.io, Cronitor.io | dead man's switch, 미수신 알림 |

2026년의 합의된 베스트 프랙티스는 이렇다.

1. **단일 박스의 한 명령**이면 systemd timers (cron 대신).

2. **서버리스 환경**이면 Vercel Cron, EventBridge Scheduler, Cloudflare Cron 중 인프라에 맞는 것.

3. **앱 안에서 백그라운드 + 주기적 작업**이면 BullMQ / Sidekiq / Celery Beat.

4. **다단계 워크플로**면 Temporal / Inngest / Trigger.dev / Hatchet (현대) 또는 Airflow / Dagster / Prefect (전통).

5. **무엇을 쓰든 Healthchecks.io / Cronitor.io 로 미수신 알림**을 건다.

이 가이드를 따르면 2026년의 99% 케이스가 해결된다.

2장 · 클래식 cron — 5필드 표기법, /etc/crontab

cron은 1975년 Brian Kernighan이 처음 쓴 Unix 도구다. 50년 동안 살아남았고 여전히 거의 모든 Linux 박스에 깔려있다. 핵심 표기법인 **5필드** 부터.

┌───────── 분 (0-59)

│ ┌─────── 시 (0-23)

│ │ ┌───── 일 (1-31)

│ │ │ ┌─── 월 (1-12)

│ │ │ │ ┌─ 요일 (0-7, 0과 7은 일요일)

│ │ │ │ │

* * * * * command-to-execute

자주 쓰는 예시.

매일 자정

0 0 * * * /usr/local/bin/backup.sh

매시간 정각

0 * * * * /usr/local/bin/sync.sh

매 5분

*/5 * * * * /usr/local/bin/healthcheck.sh

평일 오전 9시

0 9 * * 1-5 /usr/local/bin/report.sh

매월 1일 오전 3시

0 3 1 * * /usr/local/bin/monthly-billing.sh

cron은 두 가지 종류의 crontab을 갖는다.

- **사용자 crontab**: `crontab -e`로 편집, `/var/spool/cron/`에 저장. 사용자 권한으로 실행.

- **시스템 crontab**: `/etc/crontab`, `/etc/cron.d/*` 에 두며 실행 사용자를 명시해야 함 (6번째 필드).

/etc/crontab — 시스템용. user 필드가 추가된다.

SHELL=/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin

m h dom mon dow user command

17 * * * * root cd / && run-parts --report /etc/cron.hourly

25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts /etc/cron.daily )

다섯 필드 외에 일부 cron 구현(Vixie cron, anacron, cronie)은 **6필드(초)** 를 지원한다. 그러나 표준 POSIX cron은 5필드만 이해한다는 점을 기억할 것. BullMQ·Quartz·Spring 같은 라이브러리는 6필드 또는 7필드(년) 를 받기도 한다.

cron의 한계.

1. **모니터링이 없음**: 작업이 실패해도 운영자가 모름. 출력은 메일로 전송되는데 2026년 누가 root 메일을 읽는가.

2. **부재 보정 없음**: 박스가 꺼져있다가 켜졌을 때 놓친 작업은 그냥 누락 (anacron이 이 문제를 푼다).

3. **단일 박스**: 분산 잠금 없음 → 같은 cron을 두 박스에 두면 두 번 돈다.

4. **시간대**: 시스템 TZ로 동작. UTC가 아니면 DST(서머타임)에서 한 시간 사라지거나 두 번 도는 일이 생긴다.

5. **표현력 부족**: "매월 마지막 평일" 같은 표현이 어렵다.

그래도 cron은 살아있다. 단순함의 가치 때문이다. 한 줄짜리 작업, 한 박스, 모니터링이 필요 없는 게 분명한 케이스 (예: 로그 로테이션, 임시 파일 정리) 라면 cron이 여전히 최적이다.

3장 · systemd timers — Linux 모던 대체

systemd가 init 시스템을 통일한 뒤(2010년대 중반), **systemd timers** 가 cron의 모던 대체로 자리잡았다. RHEL/CentOS Stream, Ubuntu, Debian, Fedora 모두 systemd가 기본이다.

systemd timer 두 파일.

/etc/systemd/system/backup.service

[Unit]

Description=Daily backup

[Service]

Type=oneshot

ExecStart=/usr/local/bin/backup.sh

User=backup

Nice=10

IOSchedulingClass=idle

/etc/systemd/system/backup.timer

[Unit]

Description=Daily backup timer

[Timer]

OnCalendar=daily

Persistent=true

RandomizedDelaySec=10min

[Install]

WantedBy=timers.target

활성화.

sudo systemctl daemon-reload

sudo systemctl enable --now backup.timer

sudo systemctl list-timers

systemd timers가 cron보다 강한 점.

1. **저널 로깅 통합**: `journalctl -u backup.service` 한 줄로 실행 로그 확인. cron은 메일이거나 syslog.

2. **Persistent**: 박스가 꺼져 있어서 놓친 실행을 켜진 뒤 즉시 한 번 실행. anacron 기능 내장.

3. **RandomizedDelaySec**: 같은 시간에 모든 박스가 동시에 cron을 돌려 부하 폭증하는 문제를 자동 분산.

4. **OnCalendar 표현력**: `Mon..Fri 09:00`, `*-*-1..7 09:00` (매월 첫 주의 평일), `quarterly`, `yearly` 등 사람이 읽을 수 있는 형식.

5. **자원 제한**: Service unit이 `CPUQuota`, `MemoryMax`, `IOSchedulingClass` 같은 cgroup 제한을 직접 갖는다.

6. **의존성**: `Requires=`, `After=` 로 다른 서비스가 살아있을 때만 실행 같은 조건을 표현.

OnCalendar 표기 예시.

매일 03:00

OnCalendar=daily

매주 일요일 04:00

OnCalendar=Sun 04:00

매월 1일과 15일

OnCalendar=*-*-01,15 02:00

평일 매 10분

OnCalendar=Mon..Fri *:0/10

매 6시간 (0, 6, 12, 18)

OnCalendar=*-*-* 0/6:00:00

`systemd-analyze calendar 'Mon..Fri *:0/10'` 명령으로 표기를 검증할 수 있다. 다음 발화 시각도 출력해준다.

cron에서 systemd timer로 이전은 사실상 2026년의 표준 권고다. Ubuntu 24.04 LTS와 RHEL 10 모두 cron 패키지가 옵션이고 systemd timer가 기본 메커니즘이다.

4장 · Vercel Cron — 서버리스 스케줄

Vercel Cron은 2022년 GA가 됐고, 2026년 현재 Next.js·SvelteKit 같은 프레임워크와 통합된 표준 스케줄 메커니즘이다.

설정은 `vercel.json` 한 파일.

{

"crons": [

{

"path": "/api/cron/daily-report",

"schedule": "0 9 * * *"

},

{

"path": "/api/cron/cleanup",

"schedule": "*/15 * * * *"

}

]

}

핸들러 (Next.js App Router 기준).

// app/api/cron/daily-report/route.ts

export async function GET(request: Request) {

// Vercel은 Authorization 헤더로 자체 시크릿을 보낸다

const authHeader = request.headers.get('authorization')

if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {

return new NextResponse('Unauthorized', { status: 401 })

}

// 실제 로직

await sendDailyReport()

return NextResponse.json({ ok: true })

}

Vercel Cron의 특징.

- **Hobby/Free 플랜**: 매일 최대 2개의 cron, 분당 1회. 학습 / 개인 프로젝트용.

- **Pro 플랜**: 40개 cron, 분당 여러 회 가능. 운영 가능.

- **Enterprise**: 사실상 제한 없음.

- **타임아웃**: 함수 자체 타임아웃(Pro 60초, Enterprise 900초) 안에 끝나야 함.

- **HMAC 인증**: CRON_SECRET 환경 변수로 외부에서 임의 호출 막기.

- **모니터링**: Vercel 대시보드에서 마지막 실행 / 상태 확인.

한계도 분명하다. 함수 타임아웃 안에 끝나야 하니 **장시간 작업** (배치, 대량 데이터 처리) 은 안 맞고, 일이 길면 **잡 큐** (Inngest, Trigger.dev, QStash) 에 푸시하고 끝내는 패턴이 표준. Vercel Cron 자체는 "정시에 한 번 깨워주는 알람" 이다.

5장 · AWS EventBridge Scheduler (2022.11) — CloudWatch Events Rules 대체

AWS의 cron은 2022년 11월에 **EventBridge Scheduler**로 세대 교체됐다. 그 이전에는 **CloudWatch Events Rules** (CloudWatch Events rate / cron expression) 이 표준이었지만, EventBridge Scheduler가 1억 스케줄까지 단일 계정에서 가능하다고 발표하면서 권장 옵션이 됐다.

차이의 핵심.

| 항목 | CloudWatch Events Rules (구) | EventBridge Scheduler (신) |

| --- | --- | --- |

| 출시 | 2016 | 2022.11 |

| 스케줄 수 한도 | 계정당 100~300 (서비스 한도) | 계정당 1억 |

| 일회성 스케줄 | 없음 | `at(...)` 표현 지원 |

| 시간대 | UTC 고정 | IANA TZ 명시 가능 |

| 유연한 시간 윈도우 | 없음 | FlexibleTimeWindowMinutes |

| 통합 타깃 | EventBus 통한 간접 | Lambda, SQS, ECS, SageMaker, Step Functions 등 200+ 직접 |

| 권장 | deprecated 권고 | AWS 공식 권장 |

EventBridge Scheduler 만들기 (Terraform).

resource "aws_scheduler_schedule" "daily_report" {

name = "daily-report"

group_name = "default"

flexible_time_window {

mode = "OFF"

}

schedule_expression = "cron(0 9 ? * MON-FRI *)"

schedule_expression_timezone = "Asia/Seoul"

target {

arn = aws_lambda_function.report.arn

role_arn = aws_iam_role.scheduler.arn

retry_policy {

maximum_retry_attempts = 3

maximum_event_age_in_seconds = 3600

}

}

}

AWS의 cron 표현은 6필드 (분 시 일 월 요일 년) 이며, `?` 는 "어느 쪽 신경 쓰지 마라" 라는 의미다. POSIX cron과 미묘하게 다르니 주의해야 한다.

또 하나의 옵션이 **Step Functions의 Wait + EventBridge** 조합이다. 워크플로 안에서 "5분 대기 → 다음 단계" 식의 패턴은 Step Functions 자체 Wait state로 표현하고, 외부에서 깨우는 건 EventBridge Scheduler를 쓴다.

2026년 권고: **새 프로젝트는 EventBridge Scheduler**, 기존 CloudWatch Events Rules는 점진적으로 이전. AWS 자체가 콘솔에서 마이그레이션 도구를 제공한다.

6장 · Cloudflare Workers Cron Triggers

Cloudflare Workers는 엣지 함수 플랫폼이며, **Cron Triggers** 라는 이름으로 cron 표기 기반 스케줄링을 지원한다. 2026년 현재 Workers 무료 플랜도 cron 3개까지 허용한다.

`wrangler.toml` 설정.

name = "my-worker"

main = "src/index.ts"

compatibility_date = "2026-05-01"

[triggers]

crons = [

"0 */6 * * *",

"0 0 * * 0"

]

핸들러.

// src/index.ts

export default {

async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {

console.log(`cron fired: ${event.cron} at ${event.scheduledTime}`)

// 실제 작업

await runCleanup(env)

},

async fetch(req: Request): Promise<Response> {

return new Response('Worker alive')

}

}

Cloudflare의 차별점.

- **엣지 글로벌 실행**: 어디서 cron이 깨어나는지는 Cloudflare가 정한다 (라우팅 최적화). 워크로드에 시간대 의존성이 있다면 코드 안에서 처리해야 한다.

- **무료 플랜 포함**: 100k 요청/일 안에 cron 도 들어간다.

- **30초 CPU 한도**: 표준 Worker는 30초 CPU. 장시간 작업은 Durable Objects 또는 Queues 와 조합.

- **이벤트 메타데이터**: `event.cron` 으로 어느 표현이 발화했는지 알 수 있어 한 함수에서 여러 스케줄 분기 가능.

Workers Cron Triggers는 Vercel Cron과 비슷하지만 **엣지 분산** 과 **무료 한도 후함** 이 강점.

7장 · GitHub Actions schedule / GitLab schedule pipelines

CI/CD 플랫폼 자체가 cron이 되는 패턴은 2020년 이후 폭발적으로 늘었다. 인프라 운영, 데이터 동기화, 정기 점검 같은 일을 **워크플로 파일** 로 관리할 수 있다.

GitHub Actions schedule 예시.

.github/workflows/nightly.yml

name: Nightly Maintenance

on:

schedule:

- cron: '0 17 * * *' # UTC 17:00 = KST 02:00

workflow_dispatch: # 수동 실행도 허용

jobs:

cleanup:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Run cleanup

run: ./scripts/cleanup.sh

GitHub Actions schedule의 주의점.

1. **UTC 고정**: 시간대 옵션 없음. 자체적으로 변환해야 한다.

2. **저활동 저장소 일시정지**: 60일 동안 push가 없으면 schedule 워크플로는 자동 비활성화. 일부러 commit이 필요.

3. **지연 가능**: GitHub 부하에 따라 약속 시각보다 수 분~수십 분 늦게 발화하는 경우 흔함.

4. **무료 한도**: 퍼블릭 저장소는 무료, 프라이빗은 분당 시간 quota에서 차감.

GitLab schedule pipelines.

.gitlab-ci.yml

nightly:

script:

- ./scripts/cleanup.sh

rules:

- if: $CI_PIPELINE_SOURCE == "schedule"

GitLab은 코드 외에 **CI/CD > Schedules** UI에서 시각 / 시간대 / 변수를 설정한다. GitHub Actions와 달리 시간대 설정이 가능한 게 강점.

CI 스케줄의 적합한 케이스.

- 의존성 보안 스캔 / SBOM 갱신.

- 정적 사이트 재생성 (예: 시세, 환율, 날씨).

- 매일 / 매주 보고서 메일 발송.

- 깃 저장소 미러링.

부적합 케이스.

- 분 단위 정확성이 필요한 작업 (CI는 약속 시각보다 늦게 도는 일이 흔함).

- 실시간 인프라 작업 (EventBridge Scheduler 또는 Vercel Cron 권장).

8장 · Kubernetes CronJobs — kind: CronJob

쿠버네티스에는 1.21부터 CronJob 이 안정화됐다. Job 이라는 일회성 컨테이너 실행 리소스 위에 cron 표기를 얹은 것.

apiVersion: batch/v1

kind: CronJob

metadata:

name: daily-cleanup

spec:

schedule: "0 2 * * *"

timeZone: "Asia/Seoul"

concurrencyPolicy: Forbid

startingDeadlineSeconds: 600

successfulJobsHistoryLimit: 3

failedJobsHistoryLimit: 1

jobTemplate:

spec:

backoffLimit: 2

template:

spec:

restartPolicy: OnFailure

containers:

- name: cleanup

image: registry.example.com/cleanup:1.4.2

command: ["/app/cleanup.sh"]

resources:

requests: { cpu: "100m", memory: "128Mi" }

limits: { cpu: "1", memory: "512Mi" }

쿠버네티스 CronJob 핵심 필드.

- **schedule**: 5필드 cron 표현.

- **timeZone**: 1.25부터 정식. IANA TZ (예: `Asia/Tokyo`).

- **concurrencyPolicy**: `Allow` (기본) / `Forbid` (이전 잡이 안 끝나면 새 잡 안 띄움) / `Replace` (새 잡이 이전 잡을 죽임).

- **startingDeadlineSeconds**: 이 시간 안에 못 띄우면 포기 (kube-controller가 잠시 늦으면).

- **successfulJobsHistoryLimit / failedJobsHistoryLimit**: Job 객체를 몇 개 남길지.

- **backoffLimit**: Pod 실패 시 재시도 횟수.

CronJob의 함정.

1. **clock drift**: 노드 시간이 일치하지 않으면 발화 시각이 흔들림. NTP 필수.

2. **컨트롤러 지연**: kube-controller-manager가 부하 받으면 발화가 지연. 평균 1초~수십 초.

3. **타임아웃 미설정**: Job에 activeDeadlineSeconds를 안 두면 무한 실행 가능.

4. **시간대 함정**: 1.25 이전 클러스터는 항상 UTC. DST를 지나는 시각에 두 번 또는 0회 발화하는 클래식 버그가 있다.

운영 팁.

- CronJob 마다 **PodDisruptionBudget**, **PriorityClass** 를 정의해 노드 이슈에 안전하게.

- 로깅: stdout/stderr를 컨테이너 로그로 → Loki / OpenSearch 로 수집.

- 모니터링: Prometheus의 `kube_cronjob_*` 메트릭 + Alertmanager → "마지막 성공 후 X 시간 경과시 알림".

9장 · BullMQ (Node) / Sidekiq (Ruby) / Celery Beat (Python)

앱 안의 백그라운드 작업 + 주기적 작업 패턴은 **Redis 기반 잡 큐 + scheduler** 가 표준이다. 2026년 3대 옵션.

BullMQ — Node.js

BullMQ는 OptimalBits의 Bull 후속작으로, 2026년 Node.js 진영의 표준 큐.

const connection = { host: '127.0.0.1', port: 6379 }

// 큐 생성

const reportsQueue = new Queue('reports', { connection })

// 반복 잡 (cron) 등록

await reportsQueue.add(

'daily-report',

{ type: 'pdf' },

{

repeat: { pattern: '0 9 * * 1-5', tz: 'Asia/Seoul' },

jobId: 'daily-report',

}

)

// 워커

new Worker('reports', async (job) => {

console.log(`processing ${job.name}`)

await generateReport(job.data)

}, { connection })

BullMQ의 강점.

- **TypeScript first**: 타입 안전.

- **Repeatable jobs**: cron 또는 every (밀리초). tz 지정 가능.

- **Flow producer**: 부모-자식 잡 의존성 (워크플로 흉내).

- **Rate limiting / priority / retries**: 1차원 코드.

**BullMQ Pro** 는 유료. 그룹 / 일시정지 / 우선순위 큐 그룹 같은 엔터프라이즈 기능을 추가한다. BullMQ가 운영하는 회사 (Taskforce.sh) 가 메인테이너이며, 무료 OSS의 활발도가 높은 편.

Sidekiq — Ruby

Sidekiq은 Ruby 진영의 압도적 1위. 2012년 Mike Perham이 시작했고 2026년에도 활발하다.

Gemfile

gem 'sidekiq'

gem 'sidekiq-scheduler' # 또는 sidekiq-cron

config/sidekiq.yml

:schedule:

daily_report:

cron: '0 9 * * 1-5'

class: DailyReportJob

queue: reports

app/jobs/daily_report_job.rb

class DailyReportJob

include Sidekiq::Job

sidekiq_options queue: :reports, retry: 3

def perform

ReportMailer.daily.deliver_now

end

end

Sidekiq의 변종.

- **Sidekiq (OSS)**: 무료. Redis. Web UI 기본.

- **Sidekiq Pro**: 유료. 신뢰성 보강 (reliable fetch, batches).

- **Sidekiq Enterprise**: 더 비싼 유료. 유니크 잡, 주기적 잡 내장 (별도 gem 불필요), 멀티 프로세스 / 다중 DC.

Mike Perham의 비즈니스 모델은 OSS와 유료 버전 모두 단일 메인테이너가 운영하는 모범 사례로 자주 인용된다.

Celery Beat — Python

Celery는 Python 백그라운드 큐의 표준. **Celery Beat** 가 그 scheduler 컴포넌트.

celery_app.py

from celery import Celery

from celery.schedules import crontab

app = Celery('myapp', broker='redis://localhost:6379/0')

app.conf.beat_schedule = {

'daily-report': {

'task': 'tasks.generate_report',

'schedule': crontab(hour=9, minute=0, day_of_week='1-5'),

},

'every-15-min-cleanup': {

'task': 'tasks.cleanup',

'schedule': 900.0, # 초 단위

},

}

app.conf.timezone = 'Asia/Seoul'

tasks.py

@app.task

def generate_report():

...

실행.

워커

celery -A celery_app worker -l info

Beat scheduler (단일 인스턴스만 띄울 것)

celery -A celery_app beat -l info

Celery Beat의 함정 — **Beat 는 단일 노드** 만 띄워야 한다. 두 노드에서 띄우면 모든 잡이 두 번 큐에 들어간다. 분산 락이 필요하면 `celery-redbeat` 라는 백엔드를 쓴다 (Redis 분산 락으로 두 Beat가 살아도 한 번만 큐에 들어간다).

10장 · RQ / dramatiq / APScheduler — Python 옵션

Celery 외에 Python에는 더 가벼운 대안이 여럿 있다.

RQ (Redis Queue)

가장 단순한 Python 잡 큐. Celery보다 압도적으로 단순하지만 그만큼 기능도 적다.

from rq import Queue

from rq_scheduler import Scheduler

from redis import Redis

from datetime import datetime

scheduler = Scheduler(connection=Redis())

scheduler.cron(

'0 9 * * 1-5',

func=generate_report,

queue_name='reports',

)

`rq-scheduler` 패키지가 cron 표기를 지원한다. RQ가 적합한 곳: 단일 박스, 단일 워커 단위, 가벼운 백그라운드 작업.

dramatiq

RQ보다 더 robust 하지만 Celery보다 단순한 중간 옵션. RabbitMQ를 권장하지만 Redis도 지원.

from dramatiq.brokers.rabbitmq import RabbitmqBroker

from dramatiq_crontab import cron

dramatiq.set_broker(RabbitmqBroker())

@cron('0 9 * * 1-5')

@dramatiq.actor

def daily_report():

...

dramatiq의 강점: 재시도 backoff, dead letter, middleware 시스템이 깔끔. Celery 가 너무 무겁고 RQ가 너무 가볍다고 느끼는 팀이 선택.

APScheduler

워커가 별도 없는, **앱 안 인프로세스 스케줄러**. Flask · FastAPI 안에서 직접 호출.

from apscheduler.schedulers.asyncio import AsyncIOScheduler

from apscheduler.triggers.cron import CronTrigger

scheduler = AsyncIOScheduler(timezone='Asia/Seoul')

scheduler.add_job(

daily_report,

CronTrigger.from_crontab('0 9 * * 1-5')

)

scheduler.start()

APScheduler의 적합한 곳: 1~2 프로세스의 작은 앱, 외부 Redis/RabbitMQ를 두기 싫은 경우. 한계: 분산 잠금이 약함, 프로세스 죽으면 누락. SQLAlchemy / MongoDB / Redis 잡 저장소가 있지만 모두 강 일관성이 아니다.

선택 가이드.

| 도구 | 백엔드 | 분산 | 복잡도 | 적합 |

| --- | --- | --- | --- | --- |

| Celery + Beat | Redis / RabbitMQ | O (Beat 1개만) | 높음 | 큰 앱, 다양한 워크로드 |

| RQ + rq-scheduler | Redis | 중 | 낮음 | 단일 박스, 가벼운 잡 |

| dramatiq | RabbitMQ / Redis | O | 중 | 신뢰성 + 단순함 |

| APScheduler | 인프로세스 | X | 매우 낮음 | 1~2 프로세스 작은 앱 |

11장 · Airflow / Dagster / Prefect / Temporal / Inngest / Trigger.dev — 워크플로

워크플로 엔진은 cron 의 자연스러운 진화다. **다단계** + **재시도** + **상태 추적** + **시각화** 가 핵심. 이 카테고리는 이미 별도 글로 다뤘으니 여기서는 cron 관점에서만 정리.

Apache Airflow

데이터 엔지니어링의 표준. DAG (Directed Acyclic Graph) 안에 schedule_interval 을 둔다.

from airflow import DAG

from airflow.operators.python import PythonOperator

from datetime import datetime

with DAG(

'daily_etl',

schedule_interval='0 2 * * *',

start_date=datetime(2026, 1, 1),

catchup=False,

tags=['etl'],

) as dag:

extract = PythonOperator(task_id='extract', python_callable=do_extract)

transform = PythonOperator(task_id='transform', python_callable=do_transform)

load = PythonOperator(task_id='load', python_callable=do_load)

extract >> transform >> load

Airflow 의 강점은 시각화와 catchup (놓친 시각을 백필) 이며, 약점은 운영 복잡도와 무거운 스케줄러.

Dagster

asset 기반 모델. cron 만이 아니라 **데이터 자산이 stale 됐을 때** 발화시키는 패턴이 강력.

from dagster import asset, ScheduleDefinition, define_asset_job

@asset

def daily_metrics():

...

daily_job = define_asset_job('daily_metrics_job', selection=[daily_metrics])

daily_schedule = ScheduleDefinition(

job=daily_job,

cron_schedule='0 2 * * *',

execution_timezone='Asia/Seoul',

)

Prefect

Python 함수 데코레이터로 워크플로를 정의. cron 외에 interval, rrule (반복) 도 지원.

from prefect import flow, task

from prefect.deployments import Deployment

from prefect.client.schemas.schedules import CronSchedule

@task

def fetch(): ...

@flow

def daily_pipeline():

fetch()

Deployment.build_from_flow(

flow=daily_pipeline,

name='daily',

schedule=CronSchedule(cron='0 2 * * *', timezone='Asia/Seoul'),

).apply()

Temporal Schedules

Temporal 은 워크플로 엔진의 신성. 2023년 Schedules API 가 정식 출시되면서 cron + 워크플로 신뢰성을 합쳤다.

const client = new Client()

await client.schedule.create({

scheduleId: 'daily-report',

spec: {

cronExpressions: ['0 9 * * 1-5'],

timezoneName: 'Asia/Seoul',

},

action: {

type: 'startWorkflow',

workflowType: 'DailyReportWorkflow',

workflowId: 'daily-report',

taskQueue: 'reports',

},

policies: {

overlap: 'SKIP', // 이전 워크플로가 안 끝났으면 새 거 건너뜀

catchupWindow: '1h',

},

})

Temporal Schedules 의 강점은 **워크플로 자체가 영속적**이라는 점. cron 이 발화하고 워크플로가 시작되면, 그 워크플로는 박스가 죽어도 다른 박스에서 이어 실행된다. 진정한 "fire and forget".

Inngest Crons

서버리스 함수 / 이벤트 드리븐 워크플로. cron 표기 외에도 이벤트 기반 트리거.

export const dailyReport = inngest.createFunction(

{ id: 'daily-report' },

{ cron: 'TZ=Asia/Seoul 0 9 * * 1-5' },

async ({ step }) => {

const data = await step.run('fetch', fetchData)

await step.run('send', () => sendReport(data))

}

)

Trigger.dev v3

React-like 워크플로 코드. cron 외에 `cron.list`, `schedules.create()` 같은 동적 스케줄 API 도 제공.

export const dailyReport = schedules.task({

id: 'daily-report',

cron: { pattern: '0 9 * * 1-5', timezone: 'Asia/Seoul' },

run: async (payload) => {

// payload.timestamp 로 발화 시각 받음

}

})

12장 · Hatchet — 신예 워크플로

Hatchet 은 2024년 등장한 워크플로 엔진. PostgreSQL 위에 잡 큐 + 워크플로 + 스케줄을 통합한 것이 특징.

const hatchet = Hatchet.init()

hatchet.workflow({

id: 'daily-report',

on: { cron: '0 9 * * 1-5' },

steps: [

{

name: 'fetch-data',

run: async (ctx) => fetchData(),

},

{

name: 'send-email',

parents: ['fetch-data'],

run: async (ctx) => {

const data = ctx.stepOutput('fetch-data')

await sendEmail(data)

},

}

]

})

Hatchet 의 차별점.

- **PostgreSQL 단일 의존성**: Redis · Kafka · NATS 같은 별도 의존성 없음. Postgres 한 인스턴스가 큐 + 잡 저장 + 워크플로 상태 모두.

- **Self-host 친화**: 한 단일 Docker 이미지로 풀 스택 실행.

- **OpenTelemetry 내장**: 모든 잡 / 워크플로가 trace 로 나옴.

- **유료 / 무료**: OSS는 무료, Hatchet Cloud는 매니지드.

2026년 Hatchet 의 적합한 곳: PostgreSQL을 이미 운영하는 작은~중간 팀이 Temporal 보다 가벼운 옵션을 찾을 때.

13장 · Quartz (Java) / Spring @Scheduled / Hangfire (.NET)

JVM과 .NET 진영의 cron.

Quartz Scheduler

JVM 진영의 고전적 스케줄링 라이브러리. 1999년부터 있었고 2026년에도 활발하다.

public class DailyReportJob implements Job {

public void execute(JobExecutionContext ctx) {

// 작업

}

}

JobDetail job = newJob(DailyReportJob.class)

.withIdentity("dailyReport", "reports")

.build();

CronTrigger trigger = (CronTrigger) newTrigger()

.withIdentity("dailyReportTrigger", "reports")

.withSchedule(cronSchedule("0 0 9 ? * MON-FRI").inTimeZone(TimeZone.getTimeZone("Asia/Seoul")))

.build();

scheduler.scheduleJob(job, trigger);

Quartz는 **JDBC JobStore** 로 잡 상태를 RDB 에 영속화하며, 클러스터 모드(여러 스케줄러 인스턴스가 같은 DB 공유) 도 지원한다. AWS EventBridge나 BullMQ 같은 매니지드보다 더 정교한 제어가 필요할 때.

Spring @Scheduled

Spring Boot 안에서는 어노테이션 하나로 cron 잡을 만든다.

@Component

public class ScheduledTasks {

@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Asia/Seoul")

public void dailyReport() {

// 작업

}

@Scheduled(fixedRate = 60000)

public void healthcheck() {

// 매 60초

}

}

`@EnableScheduling` 을 main 클래스에 두면 활성화. Spring 의 cron 표기는 **6필드(초 분 시 일 월 요일)** 임에 유의. POSIX cron 과 다르다.

분산 환경에서는 `ShedLock` 라이브러리로 분산 잠금을 더해 두 인스턴스가 같은 잡을 두 번 돌리지 않게 한다.

Hangfire — .NET

.NET 진영의 백그라운드 잡 + 스케줄 라이브러리.

// Startup.cs

services.AddHangfire(c => c.UseSqlServerStorage(connStr));

services.AddHangfireServer();

// Program / Controller

RecurringJob.AddOrUpdate<IDailyReportService>(

"daily-report",

svc => svc.SendReport(),

"0 9 * * 1-5",

TimeZoneInfo.FindSystemTimeZoneById("Korea Standard Time")

);

Hangfire 의 강점.

- **SQL Server / PostgreSQL / Redis** 백엔드 선택 가능.

- **대시보드**: 잡 상태, 실패, 재시도를 브라우저에서 본다.

- **자동 retry**: 실패 시 지수 backoff.

- **OSS 무료 + Hangfire Pro 유료**.

Hangfire 는 .NET 진영의 사실상 표준이다.

`Castle Windsor` 같은 IoC 컨테이너와 통합되어 의존성 주입이 깔끔하다.

14장 · fcron / anacron — Linux 대안

cron 의 두 가지 대안이 Linux 진영에 있다.

anacron

박스가 항상 켜져있지 않은 환경 (랩톱, 데스크톱) 에서 cron 보완재. 잡이 약속 시각에 박스가 꺼져있었으면, 다음에 켜졌을 때 한 번 실행해준다.

/etc/anacrontab

period delay job-identifier command

1 5 cron.daily run-parts /etc/cron.daily

7 25 cron.weekly run-parts /etc/cron.weekly

@monthly 45 cron.monthly run-parts /etc/cron.monthly

`period` 가 일, `delay` 가 부트 후 대기 분, `job-identifier` 가 마지막 실행 추적용 이름. systemd timers의 `Persistent=true` 가 같은 기능을 더 깔끔하게 한다.

fcron

cron + anacron 의 통합 시도. 1999년부터 개발됐고, "다양한 환경에서 한 도구로" 가 목표.

fcrontab 예시

@daily,mailto=ops backup.sh

@reboot,nice=10 startup-checks.sh

%every 10 minutes after boot * * * * /usr/local/bin/poll.sh

fcron 의 차별점.

- 자체 `@daily`, `%hours`, `%mins` 등 사람이 읽기 쉬운 표기.

- 시작 후 N분 같은 상대 시각 지원.

- 박스가 꺼졌었던 잡을 따라잡기.

- 잡 동시 실행 제한 같은 자원 관리.

2026년 fcron 은 틈새 도구다. systemd timers 가 사실상 같은 기능을 제공하면서 더 깊은 시스템 통합을 갖기 때문. 그러나 일부 Debian / Gentoo 환경에서 여전히 쓰인다.

15장 · Healthchecks.io / Cronitor.io — 모니터링

cron 의 "조용한 실패" 문제를 푸는 dead man's switch 서비스. 잡이 "끝났다" 라고 ping 을 보내야 하고, 안 보내면 알림. 이미 별도 글에서 더 깊이 다뤘으니 여기서는 사용 패턴 위주.

Healthchecks.io

OSS + 매니지드. 무료 플랜이 후함 (20개 체크).

crontab

0 9 * * 1-5 /usr/local/bin/report.sh && curl -fsS --retry 3 https://hc-ping.com/PROJECT_ID/daily-report > /dev/null

또는 시작 / 끝 / 실패 셋을 분리해서 보내기.

#!/bin/bash

URL="https://hc-ping.com/PROJECT_ID/daily-report"

curl -fsS --retry 3 "$URL/start"

if /usr/local/bin/report.sh; then

curl -fsS --retry 3 "$URL"

else

curl -fsS --retry 3 "$URL/fail"

fi

`/start` 와 종료 ping 사이 시간으로 작업 길이를 추적. `/fail` 로 실패 알림. Slack · 이메일 · PagerDuty 통합.

Cronitor.io

상업 SaaS. 좀 더 정교한 메트릭, 알림 라우팅. Healthchecks.io의 매니지드 경쟁자.

Cronitor도 패턴은 같다

0 9 * * 1-5 cronitor exec daily-report /usr/local/bin/report.sh

`cronitor` CLI 가 시작 / 끝 / 실패 ping 을 자동으로 보낸다.

운영 권고: **모든 중요 cron 에 모니터링을 건다.** 2026년의 SRE 합의는 "ping 없는 cron 은 운영 부채" 다.

16장 · Nomad periodic jobs

HashiCorp Nomad 의 cron 등가물. 컨테이너 / 바이너리 / Java / Docker / QEMU 등 다양한 워크로드 타입을 지원하는 게 차별점.

job "daily-cleanup" {

type = "batch"

periodic {

cron = "0 2 * * *"

time_zone = "Asia/Seoul"

prohibit_overlap = true

}

group "cleanup" {

task "run" {

driver = "docker"

config {

image = "registry.example.com/cleanup:1.4.2"

command = "/app/cleanup.sh"

}

resources {

cpu = 500

memory = 256

}

}

}

}

Nomad 의 강점.

- **단일 바이너리**: HashiCorp 표준 운영 모델.

- **다양한 driver**: Docker 만이 아니라 raw_exec, java, qemu, podman 등.

- **Consul / Vault 통합**: 서비스 디스커버리, 시크릿.

- **k8s 보다 단순**: 작은 팀에서 운영하기 쉬움.

쿠버네티스 CronJob 과 비교하면 Nomad 가 **작은 / 중간 환경** 에 더 적합하다. 대규모 SaaS 는 k8s 가 우세.

17장 · 한국 / 일본 — 토스, 카카오, 메르카리

토스 — cron 인프라

토스는 SLASH 컨퍼런스에서 "분산 cron 인프라" 라는 발표를 여러 번 했다. 핵심.

- **자체 ScheduleJob 플랫폼**: 토스 내부의 cron 매니저. 수천 개의 cron 잡을 중앙에서 관리.

- **분산 락**: 같은 잡이 두 박스에서 도는 걸 막기 위해 Redis 또는 ZooKeeper 분산 락.

- **k8s CronJob 위에 추상화 레이어**: 개발자가 yaml 안 쓰고 UI 로 등록.

- **모니터링 자동화**: 잡 등록 시 자동으로 dead man's switch 까지 만듦.

- **시간대 통합**: 모든 잡이 KST 기준. UTC 변환은 플랫폼이.

토스의 핵심 통찰: "cron 잡이 수백 개를 넘으면 도구가 아니라 **플랫폼** 이 필요하다."

카카오 — Task Scheduling

카카오는 if(kakao) 컨퍼런스에서 task scheduling 인프라를 공유했다.

- **카카오톡 푸시**: 정시 발송 푸시는 별도 스케줄러. Quartz 위에 자체 빌드.

- **데이터 파이프라인**: Airflow + 자체 wrapper.

- **광고 정산**: Sidekiq 풍의 잡 큐 + cron 조합.

카카오의 교훈: "한 도구로 다 안 된다. 워크로드 별로 가장 잘 맞는 도구를 모자이크" 가 현실.

메르카리 — Scheduled Jobs

메르카리 일본은 Microservices 운영의 모범 사례로 유명하다.

- **Google Cloud Tasks + Cloud Scheduler**: GCP 중심 인프라.

- **k8s CronJob**: 인프라 정리 작업.

- **각 마이크로서비스 안의 인프로세스 스케줄**: 가벼운 잡은 서비스 자체가.

메르카리 엔지니어링 블로그(en) 가 정기적으로 운영 케이스 스터디를 공개한다.

18장 · 누가 무엇을 골라야 하나 — 단순 / 분산 / 백그라운드 / 신뢰성

2026년 cron 선택을 결정 트리로 정리하면 이렇다.

잡이 한 박스에서, 한 명령 실행으로 끝?

├── YES → systemd timers (cron 대신)

│ + Healthchecks.io ping

└── NO →

인프라가 서버리스?

├── Vercel 기반 → Vercel Cron

├── AWS 기반 → EventBridge Scheduler

├── Cloudflare → Workers Cron Triggers

└── 컨테이너 → k8s CronJob 또는 Nomad periodic

잡이 앱 안 백그라운드의 일부?

├── Node → BullMQ

├── Ruby → Sidekiq

├── Python → Celery Beat (큰 앱) / RQ (작은 앱) / dramatiq (중간)

├── Java → Quartz / Spring @Scheduled

└── .NET → Hangfire

잡이 다단계 워크플로?

├── 데이터 → Airflow / Dagster / Prefect

├── 신뢰성 → Temporal

├── 서버리스 → Inngest / Trigger.dev v3

└── Postgres만 → Hatchet

CI 활동에 묶임?

└── GitHub Actions schedule / GitLab schedule pipelines

**규칙으로 정리**.

1. **단순한 잡 + 한 박스** → systemd timers.

2. **클라우드 매니지드** → Vercel / EventBridge / Cloudflare 인프라 종속.

3. **앱 인테그레이션** → 언어 별 표준 (BullMQ / Sidekiq / Celery Beat / Quartz / Hangfire).

4. **워크플로 신뢰성** → Temporal / Hatchet.

5. **데이터 파이프라인** → Airflow / Dagster / Prefect.

6. **무엇을 쓰든** → Healthchecks.io 또는 Cronitor.io 모니터링.

2026년의 cron 운영은 "한 도구로 다 한다" 가 아니라 **"각 워크로드에 가장 잘 맞는 도구를 선택하고, 모두 모니터링한다"** 가 표준이다.

참고 / References

- [cron(8) — Unix manual page](https://man7.org/linux/man-pages/man8/cron.8.html)

- [crontab(5) — format specification](https://man7.org/linux/man-pages/man5/crontab.5.html)

- [systemd.timer documentation](https://www.freedesktop.org/software/systemd/man/systemd.timer.html)

- [systemd-analyze calendar](https://www.freedesktop.org/software/systemd/man/systemd-analyze.html)

- [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs)

- [AWS EventBridge Scheduler announcement (Nov 2022)](https://aws.amazon.com/blogs/compute/introducing-amazon-eventbridge-scheduler/)

- [AWS EventBridge Scheduler documentation](https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html)

- [Cloudflare Workers Cron Triggers](https://developers.cloudflare.com/workers/configuration/cron-triggers/)

- [GitHub Actions schedule events](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule)

- [GitLab Pipeline schedules](https://docs.gitlab.com/ee/ci/pipelines/schedules.html)

- [Kubernetes CronJob](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/)

- [BullMQ documentation](https://docs.bullmq.io/)

- [BullMQ Pro](https://bullmq.io/pro/)

- [Sidekiq documentation](https://github.com/sidekiq/sidekiq/wiki)

- [Sidekiq Pro / Enterprise](https://sidekiq.org/products/pro.html)

- [Celery documentation](https://docs.celeryq.dev/)

- [Celery Beat periodic tasks](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html)

- [RQ — Redis Queue](https://python-rq.org/)

- [dramatiq documentation](https://dramatiq.io/)

- [APScheduler documentation](https://apscheduler.readthedocs.io/)

- [Apache Airflow](https://airflow.apache.org/)

- [Dagster schedules](https://docs.dagster.io/concepts/automation/schedules)

- [Prefect schedules](https://docs.prefect.io/v3/automate/add-schedules)

- [Temporal Schedules](https://docs.temporal.io/develop/typescript/schedules)

- [Inngest crons](https://www.inngest.com/docs/functions/triggers#cron)

- [Trigger.dev v3 Scheduled tasks](https://trigger.dev/docs/tasks/scheduled)

- [Hatchet documentation](https://docs.hatchet.run/)

- [Quartz Scheduler](https://www.quartz-scheduler.org/)

- [Spring @Scheduled](https://docs.spring.io/spring-framework/reference/integration/scheduling.html)

- [Hangfire documentation](https://docs.hangfire.io/)

- [fcron homepage](https://fcron.free.fr/)

- [anacron documentation](https://man7.org/linux/man-pages/man8/anacron.8.html)

- [Healthchecks.io](https://healthchecks.io/)

- [Cronitor](https://cronitor.io/)

- [HashiCorp Nomad periodic jobs](https://developer.hashicorp.com/nomad/docs/job-specification/periodic)

- [Toss SLASH](https://toss.tech/slash)

- [Kakao if(kakao) conference](https://if.kakao.com/)

- [Mercari Engineering](https://engineering.mercari.com/en/blog/)

현재 단락 (1/656)

2026년 어느 팀의 분석.

작성 글자: 0원문 글자: 24,135작성 단락: 0/656