codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
노트›backend

스케줄 잡과 APScheduler

2026-04-28 게시· 2026-05-18 갱신·0회 조회

스케줄 잡과 APScheduler

정기 작업은 어느 백엔드든 등장합니다. 야간 집계·외부 데이터 수집·만료 토큰 정리. 작은 규모에서는 cron 이나 인프로세스 스케줄러로 충분하고, 커지면 분산 큐와 워커가 등장합니다.

1. APScheduler 에 대한 이야기

APScheduler 는 Alex Grönholm 이 시작한 Python 라이브러리로 2008 년경 첫 공개된 오래된 프로젝트입니다. 인프로세스 스케줄러로 cron 표현식·간격·날짜 트리거를 한 API 로 묶습니다.

트리거 의미
cron "매일 03:00" 같은 cron 식.
interval "30 초마다" 같은 고정 간격.
date 특정 시각 한 번만.

스케줄러 종류로 BlockingScheduler (메인 스레드 점유) · BackgroundScheduler (별도 스레드) · AsyncIOScheduler (이벤트 루프) 등이 있습니다. 잡 저장소 (jobstore) 로 메모리·SQLAlchemy·MongoDB·Redis 를 선택할 수 있어 재시작 후 잡 복원이 가능합니다.

2. 트리거와 잡

from apscheduler.schedulers.background import BackgroundScheduler

sched = BackgroundScheduler(timezone='Asia/Seoul')

@sched.scheduled_job('cron', hour=3, minute=0, id='daily-aggregate')
def daily_aggregate():
    ...

@sched.scheduled_job('interval', seconds=30, id='heartbeat')
def heartbeat():
    ...

sched.start()

id 를 명시하면 같은 잡의 중복 등록을 막을 수 있습니다 (replace_existing=True). 잡 저장소에 보존된 상태에서 코드가 변경되어도 같은 id 를 통해 갱신됩니다.

3. 단일 인스턴스 가정과 멱등성

APScheduler 는 같은 잡 저장소를 여러 프로세스가 공유하면 잡 분배를 자동으로 보장하지 않습니다 (별도 분산 락이 필요합니다). 단일 워커로 운영하는 것이 가장 단순한 가정입니다. 다중 워커가 필요하면 다음 중 하나를 선택합니다.

  • 잡 저장소 + DB 행 락으로 한 번에 한 워커만 실행.
  • Redis 기반 분산 락 (Redlock 같은 알고리즘).
  • 운영상 "한 인스턴스에서만 스케줄러를 켠다" 는 컨벤션.

설계상 같은 잡이 두 번 실행될 가능성을 가정하고 본문은 멱등 하게 작성합니다 (같은 입력에 같은 결과 또는 안전한 무동작).

4. misfire 와 coalesce

스케줄된 시각에 워커가 죽어 있다 깨어났을 때 미실행 잡을 어떻게 다룰지의 정책입니다.

  • misfire_grace_time — 늦어도 이 시간 안에 실행되면 OK.
  • coalesce — 누적된 실행을 한 번으로 합칠지.

기본값은 보수적이며 누적 실행이 시스템에 부담을 주지 않도록 명시 설정이 권장됩니다.

5. 다른 도구들

도구 첫 등장 모델
cron (Unix) 1975 OS 수준 스케줄러. 가장 단순.
systemd timers 2010s systemd 의 cron 대체. 단일 호스트.
APScheduler 2008 Python 인프로세스.
Celery 2009, Ask Solem Python 분산 태스크 큐 (브로커: RabbitMQ/Redis). celery beat 가 스케줄.
RQ 2012 Python + Redis. Celery 보다 단순.
Sidekiq 2012, Mike Perham Ruby + Redis. 대규모 운영에서 표준급.
BullMQ 2018 (전 Bull) Node + Redis.
Quartz 2001 JVM 의 오래된 스케줄러. Spring 통합 표준.
Temporal 2019 (Cadence 포크) 워크플로 엔진. 상태·재시도·타이머가 1 급.
AWS EventBridge / GCP Cloud Scheduler 2010s 후반 매니지드 cron.

선택 기준의 한 축은 "실행이 멀티 호스트로 분산되어야 하는가" 입니다. 분산이 필요하면 큐 모델, 단일 호스트로 충분하면 인프로세스 스케줄러로도 무방합니다.

6. FastAPI 와의 결합

from contextlib import asynccontextmanager
from fastapi import FastAPI
from apscheduler.schedulers.asyncio import AsyncIOScheduler

sched = AsyncIOScheduler()

@asynccontextmanager
async def lifespan(app: FastAPI):
    sched.start()
    yield
    sched.shutdown(wait=False)

app = FastAPI(lifespan=lifespan)

lifespan 이벤트로 스케줄러의 수명과 앱의 수명을 맞춥니다.

7. 잡 본문의 가드

def daily_aggregate():
    if already_done(date.today()):
        return
    do_work()
    mark_done(date.today())

이 형태가 멱등성의 출발점입니다. "같은 날짜로 두 번 깨어나도 한 번만 동작" 을 단순한 DB 플래그로 보장합니다.

8. 분산 락 (Redis)

여러 인스턴스가 같이 스케줄러를 돌릴 때 한 번에 한 잡만 실행되게 하려면 Redis SET key NX EX <ttl> 패턴이 흔합니다. Martin Kleppmann 의 글과 Redis 저자 antirez 의 응답으로 시작된 Redlock 논쟁이 잘 알려져 있습니다 (2016). 강일관성이 필수면 ZooKeeper · etcd · DB 행 락 같은 합의 기반 도구가 더 적합하다는 견해도 있습니다.

9. 자주 걸리는 자리

개발 환경의 자동 리로드 — uvicorn --reload 같은 모드는 워커를 두 개로 띄우는 효과를 낼 수 있어 잡이 두 번 등록됩니다. 개발 시 스케줄러를 끄거나 잡 저장소에 replace_existing=True 를 명시합니다.

시간대 (timezone) 설정 누락 — 스케줄 시각이 UTC 기준으로 해석되어 의도와 어긋납니다. 스케줄러 생성 시 timezone 을 명시합니다.

장기 실행 잡과 다음 트리거 충돌 — 직전 실행이 끝나기 전에 다음이 트리거됩니다. max_instances=1 · coalesce=True 같은 옵션을 검토합니다.

분산 락의 TTL 과 작업 시간 역전 — 락 TTL 보다 작업이 길어지면 다른 워커가 락을 가져가 두 번 실행될 수 있습니다. 작업 시간 분포를 측정하고 TTL 을 보수적으로 잡습니다.

하고픈 말

스케줄러는 큰 시스템이 등장하기 전 단계의 가장 효율적인 자리입니다. APScheduler 한 줄로 시작해 멱등 본문 + 단일 인스턴스 가정만 지키면 운영 부담이 매우 작습니다. 분산이 필요한 시점에는 Celery · Temporal 같은 도구로 옮겨가는 흐름이 자연스럽습니다.

Next

  • typeorm-readonly
  • crawler-ethics

APScheduler 공식 · APScheduler GitHub · Celery 공식 · Sidekiq 공식 · Quartz 공식 · Temporal 공식 · Redlock 논쟁 (Martin Kleppmann) 을 참고합니다.

backend 카테고리의 다른 글

카테고리 전체 보기 →
  • 공공 OpenAPI 는 자체 BFF 로 한 번 감싼다
  • 이메일 발송과 OTP — SMTP
  • 감사로그 — logAdminAction 패턴
  • WebSocket · SSE — 실시간 통신
  • REST API 입문
  • OpenAPI 사양