⚙️Backend

APScheduler 일일 작업이 재시작 때 누락되는 문제 — misfire_grace_time + 부팅 catch-up

스케줄러 작업 지연 및 미실행 문제, misfire_grace_time 설정만으로는 부족합니다. coalesce 옵션과 함께 근본적인 해결책을 제시합니다. 2026년 기준.

📅 2026년 6월 24일·📖 4분 읽기·👁 118

매일 한 번 도는 스케줄러 작업이, 어느 날부터 며칠씩 조용히 실행되지 않는 문제를 겪었습니다. 에러도 안 나고, 스케줄러도 살아있고, 작업 함수를 직접 호출하면 멀쩡히 도는데 — 그냥 그날의 자동 실행만 빠지는 거였죠. 실제로 추적해서 잡은 과정과 해결 코드를 그대로 공유합니다.

증상 — 작업은 멀쩡한데 며칠치가 비어있다

매일 새벽에 커밋을 요약해 페이지에 쌓는 작업이 있었는데, 데이터가 사흘째 멈춰 있었습니다. 진단해보니:

  • 스케줄러(APScheduler)는 살아있고 리더도 정상
  • 다른 일일 작업들(여러 일일 배치 작업)은 매일 잘 돎
  • 작업 함수를 수동 호출하면 즉시 정상 생성

즉 코드가 아니라 "그날 그 시각에 발화가 안 된 것"이 문제였습니다.

원인 — misfire 창을 재시작 타이밍이 벗어났다

APScheduler의 기본 misfire_grace_time1초입니다. cron 발화 시각에 워커가 1~2초만 바빠도(배포·재시작 등) 그 실행은 "놓친 것(misfire)"으로 간주돼 그냥 건너뜁니다.

저처럼 배포가 잦은 환경에선 더 잘 터집니다. 일일 작업이 21:00(UTC)에 발화하는데, 그 근처에 백엔드가 재시작되면 그날치를 통째로 놓치는 거죠. 실제 로그로 확인하니 리더 워커가 발화 시각 6시간 뒤에 시작돼, 뒤에 붙여둔 유예 창(6시간)마저 막 벗어난 상태였습니다.

해결 1 — misfire 유예 + coalesce

먼저 스케줄러 전역 기본값으로 유예 시간을 넉넉히 주고, 밀린 실행은 한 번으로 합치게 했습니다.

from apscheduler.schedulers.asyncio import AsyncIOScheduler

scheduler = AsyncIOScheduler( # 기본 misfire_grace_time=1초라 cron 시각에 잠깐만 늦어도 건너뜀. # 6시간 유예 + 밀린 실행은 1회로 합침(coalesce). job_defaults={"misfire_grace_time": 6 * 3600, "coalesce": True}, )

하지만 이걸로도 부족했습니다. 재시작이 유예 창(6시간)보다 더 늦게 일어나면 여전히 그날을 놓치니까요.

해결 2 — 부팅 + 주기 catch-up (자기치유)

그래서 "발화를 놓쳤든 말든, 부팅 직후 + 주기적으로 누락분을 메우는" 자기치유 작업을 별도로 얹었습니다. 핵심은 멱등(idempotent) — 이미 있으면 아무것도 안 하고, 빈 날만 채웁니다.

from datetime import datetime, timedelta

async def run_catchup(): # backfill 은 이미 있는 날은 건너뛰고, 빠진 날만 생성(멱등) → 평소 비용 0 await build_log_service.backfill(days=3, force=False)

scheduler.add_job( run_catchup, "interval", hours=6, # 6시간마다 갭 점검 next_run_time=datetime.now() + timedelta(seconds=90), # 부팅 90초 후 1회 id="build_log_catchup", replace_existing=True, )

이렇게 하면 잦은 재시작이 오히려 갭을 빨리 메우는 구조가 됩니다. 일일 cron이 한 번 새도, 늦어도 6시간 안에 catch-up이 빈 날을 채워주거든요.

한 가지 함정 — 한 번 실행 트리거의 리더 의존성

처음엔 catch-up을 "date" 트리거(부팅 후 1회)로 만들었는데, 멀티 워커 + 리더 선출 환경에선 "그 작업을 등록한 워커가 마침 리더일 때만" 발화해서 잘 안 떴습니다. "interval"로 바꾸니, 리더가 누구든 6시간마다 확실히 도는 다른 일일 작업들과 똑같이 안정적으로 돌았습니다.

정리

  • 일일 작업이 "조용히 며칠 누락"되면 — 코드보다 발화 타이밍(misfire)을 먼저 의심하세요.
  • misfire_grace_time은 기본 1초라, 잦은 재시작 환경에선 반드시 늘려야 합니다(+coalesce).
  • 중요한 일일 작업은 유예만 믿지 말고, 멱등 backfill을 주기 interval로 얹어 자기치유하게 하면 어떤 재시작 타이밍에도 갭이 안 생깁니다.

태그

#스케줄러#Quartz#misfire_grace_time#작업 지연#작업 누락#coalesce#백엔드