
스케줄러를 1분마다 돌리더라도 하루 실행 한도는 DB에서 ‘실행 성공 횟수’만 원자적으로 세면 넘기지 않습니다. 핵심은 틱 횟수를 세지 말고, 작업 직전에 DB에서 quota를 선점하고, 성공·실패 처리 기준을 분리하는 것입니다.
현재 자동화 작업의 API 호출 직전에 quota 선점 쿼리를 붙이고, 하루 기준 시간대부터 먼저 정해보세요.
APScheduler나 Celery beat처럼 틱이 자주 오는 구조에서는 “오늘 100번만 호출” 같은 제한이 쉽게 깨집니다. 특히 서버가 2대 이상이거나 재시도 로직이 있으면 메모리 카운터는 거의 바로 위험해집니다.
아래 quota 테이블과 원자적 UPDATE 패턴을 먼저 붙여보세요. 스케줄러 주기는 그대로 두고, 실행 허가만 DB가 결정하게 만들면 됩니다.
정답은 ‘틱’이 아니라 ‘권한 획득’을 세는 구조
많이 막히는 지점은 스케줄러 콜백이 호출될 때마다 카운트를 올리는 방식입니다. 이러면 실제 외부 API를 호출하지 않았는데도 한도가 줄거나, 반대로 동시 실행에서 두 작업이 같은 잔여 한도를 보고 동시에 통과합니다.
제가 파이썬 자동화 작업을 만들 때 가장 안정적으로 쓴 흐름은 이렇습니다. 스케줄러 틱 → DB에서 오늘 quota 1개 선점 → 작업 실행 → 실패 유형에 따라 사용량 유지 또는 되돌림입니다. OpenAI API, Slack 알림, 크롤링 작업처럼 하루 호출량을 제한해야 하는 작업에 잘 맞습니다.
추천 테이블: 하루·작업 단위로 한 줄만 관리
CREATE TABLE daily_quota_usage (
quota_day date NOT NULL,
job_name text NOT NULL,
used_count integer NOT NULL DEFAULT 0,
daily_limit integer NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (quota_day, job_name)
);
날짜는 서비스 기준 시간대로 결정하세요. 한국 사용자 대상이면 UTC가 아니라 KST 기준 날짜를 애플리케이션에서 만들어 넣는 편이 실수도 적습니다.
PostgreSQL에서 안전하게 1회 선점하기
동시 실행이 있는 서비스라면 PostgreSQL이나 Supabase Postgres처럼 트랜잭션이 강한 DB를 추천합니다. 아래는 이미 오늘 row가 있다고 가정한 가장 단순한 패턴입니다.
UPDATE daily_quota_usage
SET used_count = used_count + 1,
updated_at = now()
WHERE quota_day = CURRENT_DATE
AND job_name = 'send_daily_report'
AND used_count < daily_limit
RETURNING used_count, daily_limit;
결과 row가 돌아오면 실행 허가를 받은 것입니다. 아무 row도 없으면 오늘 한도 초과라서 작업을 건너뜁니다. 여러 워커가 동시에 이 쿼리를 날려도 DB가 같은 row 업데이트를 직렬화하므로 초과 실행 가능성이 크게 줄어듭니다.
오늘 row 생성까지 자동화하려면 시작 시점에 INSERT ... ON CONFLICT DO NOTHING을 먼저 호출하면 됩니다. APScheduler job 내부에서는 이 쿼리를 통과한 경우에만 실제 API 호출을 넣으세요.
구현 방식 비교: 어디까지 안전해야 하나
| 방식 | 장점 | 주의점 | 추천 상황 |
|---|---|---|---|
| 파이썬 메모리 변수 | 가장 빠르고 코드가 짧음 | 재시작·멀티프로세스에서 깨짐 | 로컬 테스트 |
| 파일 카운터 | DB 없이 가능 | 락 처리와 날짜 전환이 번거로움 | 개인용 단일 서버 |
| DB quota row | 재시작·동시 실행에 강함 | 트랜잭션 설계 필요 | 운영 서비스, SaaS 자동화 |
| Redis INCR + EXPIRE | 속도가 빠르고 TTL이 쉬움 | 영속성·자정 기준 관리 확인 필요 | 고빈도 이벤트 제한 |
하루 10~1,000회 수준의 작업이면 DB 방식이 관리하기 가장 쉽습니다. 이미 Supabase, Neon, RDS, PostgreSQL을 쓰고 있다면 별도 인프라를 늘릴 이유가 거의 없습니다.
가상의 예: 리포트 생성 작업을 5분마다 확인할 때
예를 들어 Notion DB에서 새 요청을 읽고 ChatGPT API로 요약 리포트를 만드는 작업이 있다고 가정해보겠습니다. APScheduler는 5분마다 틱을 발생시키지만, 하루 최대 80건만 처리해야 합니다.
이때 스케줄러는 “요청이 있는지 확인”만 자주 합니다. 실제 요약 요청을 보내기 직전에 daily_quota_usage에서 report_summary quota를 1개 선점합니다. 선점 실패라면 Slack에 “오늘 한도 도달” 메시지만 보내고 종료합니다.
중요한 결정은 실패 처리입니다. 외부 API가 429로 막힌 경우에는 이미 quota를 쓴 것으로 볼지, 되돌릴지 정책을 정해야 합니다. 비용이 발생하지 않은 명백한 사전 검증 실패라면 used_count = used_count - 1 보정이 가능하지만, 네트워크 타임아웃처럼 실제 호출 여부가 애매한 경우에는 보수적으로 사용 처리하는 편이 안전합니다.
이런 경우엔 이렇게 고르세요
혼자 쓰는 자동화라면 SQLite로 시작해도 됩니다. 다만 GitHub Actions처럼 실행 환경이 매번 바뀌면 파일 DB가 유지되지 않을 수 있으니 외부 DB를 쓰는 게 낫습니다.
팀용 SaaS 기능이라면 PostgreSQL row 업데이트 방식이 기본값입니다. 사용자별 한도까지 필요하면 primary key를 (quota_day, user_id, job_name)으로 확장하면 됩니다.
초당 수십~수백 건 제한이 필요하면 Redis를 검토하세요. 하지만 “하루 몇 번”을 막는 목적이라면 DB 테이블이 감사 로그와 운영 확인에 더 좋습니다.
자주 터지는 함정
가장 흔한 실수는 자정 리셋을 배치로 따로 만드는 것입니다. 매일 0시에 모든 카운터를 0으로 바꾸는 방식은 시간대, 배치 실패, 중복 실행 문제가 생깁니다. 날짜를 primary key에 넣으면 리셋 배치가 필요 없습니다.
또 하나는 작업 완료 후에 카운트를 올리는 방식입니다. 동시 실행에서 한도 1개가 남았을 때 두 작업이 모두 시작될 수 있습니다. 한도 초과를 막는 목적이라면 실행 전에 선점해야 합니다.
FAQ
스케줄러 틱이 1분마다 오면 DB 부하가 크지 않나요?
대부분의 자동화 작업에서는 매우 작습니다. 1분마다 단일 row UPDATE를 하는 수준이면 일반 PostgreSQL에서 부담이 적습니다. 초 단위 고빈도라면 Redis나 배치 큐 구조를 고려하세요.
APScheduler와 Celery beat 중 어느 쪽이 더 맞나요?
단일 앱 안에서 간단히 돌릴 작업은 APScheduler가 편합니다. 워커 여러 대, 재시도, 큐 분산이 필요하면 Celery beat와 Celery worker 조합이 낫습니다. 어느 쪽이든 quota 판정은 DB에 두는 것이 안전합니다.
SQLite로도 원자적 카운트가 가능한가요?
가능하지만 운영 환경의 동시 쓰기와 파일 잠금 특성을 확인해야 합니다. 로컬 도구나 개인 프로젝트는 괜찮고, 여러 서버에서 동시에 접근하는 구조라면 PostgreSQL을 권합니다.
정리하면, 파이썬 스케줄러 틱을 자주 호출해도 하루 한도 안 넘게 DB로 세는 법은 “날짜별 quota row를 만들고, 실행 직전에 조건부 UPDATE로 1개를 선점하는 것”입니다. 오늘 할 일은 간단합니다. 현재 작업 코드에서 외부 API 호출 바로 위에 quota 획득 함수를 하나 끼워 넣으세요.
관련 링크
- .env 시크릿을 깃에 안 올리고 관리하기 gitignore 실수 복구까지, AI 코딩 프로젝트에서 바로 쓰는 셋업
- AutoWP 개발기 — OpenAI로 WordPress 콘텐츠 파이프라인을 만들고 사람이 검수하는 반자동 시스템
- PyInstaller onefile exe가 윈도우 디펜더에 오탐 차단될 때 뚫은 방법: 우회 말고 신뢰도부터 올렸다
- 관련 태그 더 보기
- 카테고리 더 보기
- 검색 결과 더 보기
- 참고 링크
