7.2 KiB
7.2 KiB
DB 기반 동적 스케줄러 관리 시스템
날짜: 2026-01-02 작성자: happybell80 관련 서비스: rb8001 상태: 계획
목적
main.py 732줄 중 130줄이 스케줄 관련 코드. DB로 이동하여 동적 관리.
현재 스케줄러 현황
| 스케줄러 | 위치 | 환경변수 | 기본값 | 상태 |
|---|---|---|---|---|
| naverworks_briefing | app/scheduler/jobs/naverworks_briefing.py |
NAVERWORKS_BRIEFING_ENABLED |
false | 환경변수 기반 |
| coldmail_briefing | app/scheduler/jobs/coldmail_briefing.py |
COLDMAIL_BRIEFING_ENABLED |
false | 환경변수 기반 |
| lunch_worldcup | app/scheduler/jobs/lunch_worldcup.py |
LUNCH_WORLDCUP_ENABLED |
false | 12시 스케줄, 비활성화 예정 |
| diary_generator | app/scheduler/jobs/diary_generator.py |
DIARY_GENERATOR_ENABLED |
true | 환경변수 기반 |
| daily_headlines | main.py:164-205 |
HEADLINES_SCHEDULE_ENABLED |
true | main.py 직접 구현 |
| companyx_news | main.py:208-248 |
COMPANY_X_NEWS_ENABLED |
true | main.py 직접 구현 |
| scheduler_status_check | main.py:146-162 |
없음 | 항상 활성 | main.py 직접 구현 |
테이블 구조
CREATE TABLE IF NOT EXISTS scheduled_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) UNIQUE NOT NULL,
job_type VARCHAR(50) NOT NULL,
cron_expression VARCHAR(50) NOT NULL,
enabled BOOLEAN DEFAULT true,
config JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_scheduled_jobs_enabled ON scheduled_jobs(enabled);
CREATE INDEX IF NOT EXISTS idx_scheduled_jobs_job_type ON scheduled_jobs(job_type);
Job Type 매핑
| job_type | 함수 경로 | 비고 |
|---|---|---|
naverworks_briefing |
app.services.skills.naverworks_briefing.NanerWorksBriefingSkill.process_briefing |
async 함수 |
coldmail_briefing |
app.scheduler.jobs.coldmail_briefing._run_coldmail_briefing |
async 함수 |
diary_generator |
app.scheduler.jobs.diary_generator._run_diary_generator_with_logging |
sync 래퍼 |
lunch_worldcup |
app.notifications.send_lunch_worldcup_notification |
async 함수 |
companyx_news |
app.services.skills.news_posting_skill.NewsPostingSkill.process_news_batch |
async 함수 |
daily_headlines |
app.services.skills.startup_news_skill.run_headlines_job |
async 함수 |
마이그레이션 단계
Phase 1: 테이블 및 Repository 생성
app/state/scheduler_repository.py생성_ensure_table()함수로 테이블 생성 (다른 repository 패턴 참고)- CRUD 함수 구현:
get_enabled_jobs(),create_job(),update_job(),delete_job()
Phase 2: 12시 스케줄 비활성화 (즉시)
lunch_worldcup.py:16기본값 확인: 이미"false"이므로 실행 안됨- DB 마이그레이션 전 명시적 비활성화: 환경변수 설정 또는 코드에서 주석 처리
Phase 3: DB 기반 스케줄러 로더 구현
app/scheduler/db_loader.py생성load_jobs_from_db(scheduler)함수: DB 조회 → job_type 매핑 → APScheduler 등록- async 함수 처리:
asyncio.run()또는 래퍼 함수 사용
Phase 4: main.py 리팩토링
- 기존 환경변수 기반 등록 코드 제거 (131-144줄)
db_loader.load_jobs_from_db(scheduler)호출로 대체- 헤드라인/뉴스 스케줄도 DB 기반으로 전환
Phase 5: API 엔드포인트 구현
app/router/scheduler_endpoint.py생성 (기존schedule_endpoint.py와 분리)POST /api/scheduler/jobs- 새 잡 추가PATCH /api/scheduler/jobs/{name}- 스케줄 수정 (enabled, cron_expression, config)DELETE /api/scheduler/jobs/{name}- 잡 삭제GET /api/scheduler/jobs- 목록 조회
Phase 6: 기존 데이터 마이그레이션
- 환경변수 기반 스케줄러를 DB에 초기 데이터로 삽입
- 마이그레이션 스크립트:
scripts/migrate_schedules_to_db.py
Phase 7: 환경변수 관리 원칙 준수 (선택, 권장)
os.getenv()직접 호출 제거:app/scheduler/jobs/*.py에서 DB 조회로 대체- Pydantic Settings 전환:
app/core/config.py에 스케줄러 관련 설정 추가 (하위 호환성 유지) .env파일 정리: 스케줄러 관련 환경변수 제거 또는 주석 처리- 참고:
311_FastAPI_구조_원칙.md:241-252환경변수 관리 원칙
동작 방식
- 서버 시작 시
startup_event()에서db_loader.load_jobs_from_db(scheduler)호출 - DB에서
enabled=true조회 job_type으로 함수 매핑 (매핑 테이블 사용)- APScheduler에 등록
- 런타임 중 API로 추가/수정/삭제 시 즉시 반영 (scheduler.add_job/remove_job)
API 엔드포인트 상세
POST /api/scheduler/jobs
{
"name": "lunch_worldcup",
"job_type": "lunch_worldcup",
"cron_expression": "0 12 * * mon-fri",
"enabled": false,
"config": {"channel_id": "C09CP4MDX71"}
}
PATCH /api/scheduler/jobs/{name}
{
"enabled": true,
"cron_expression": "0 13 * * mon-fri",
"config": {"channel_id": "C09CP4MDX71"}
}
초기 데이터 (마이그레이션용)
| name | job_type | cron_expression | enabled | config |
|---|---|---|---|---|
| naverworks_daily | naverworks_briefing | 0 9 * * mon-fri | false | {} |
| coldmail_daily | coldmail_briefing | 5 9 * * mon-fri | false | {} |
| lunch_worldcup | lunch_worldcup | 0 12 * * mon-fri | false | {"channel_id": "C09CP4MDX71"} |
| daily_diary | diary_generator | 0 2 * * * | true | {} |
| daily_headlines | daily_headlines | 0 9 * * * | true | {"channel_id": "환경변수에서"} |
| companyx_news | companyx_news | 0 10 * * mon-fri | true | {"channel_id": "C09CP4MDX71"} |
환경변수 하드코딩 문제 해결
현재 문제점
- 이중 정의: 코드 기본값(
os.getenv("LUNCH_WORLDCUP_ENABLED", "false"))과.env파일 하드코딩(LUNCH_WORLDCUP_ENABLED=true) 불일치 - 원칙 위반:
311_FastAPI_구조_원칙.md:241-252의 "단일 소스 원칙" 위반 - 코드에서os.getenv()직접 호출,.env에 하드코딩 - 설정 관리 체계 부재: Pydantic Settings 미사용으로 환경변수 관리 일관성 없음
DB 마이그레이션으로의 해결
- 스케줄러 관련 환경변수 하드코딩 문제 해결: DB에서
enabled컬럼으로 관리하여.env하드코딩 제거 가능 - 코드 개선:
os.getenv()직접 호출 제거, DB 조회로 대체 - 부분적 해결: 스케줄러 관련 문제만 해결, 전체 환경변수 관리 체계 개선은 별도 작업 필요
마이그레이션 시 주의사항
- 기존
.env파일의 스케줄러 관련 환경변수는 DB 초기 데이터로 마이그레이션 후 제거 - 코드에서
os.getenv()직접 호출하는 부분을 DB 조회로 변경 - 하위 호환성을 위해 환경변수 체크 로직은 유지하되 DB 우선 적용
주의사항
scheduler_status_check는 시스템 내부용이므로 DB 관리 대상 아님- async 함수는 래퍼 함수 필요 (
_run_*_with_logging패턴) - cron 표현식 파싱 로직 재사용 (
_parse_cron()함수) - 기존 환경변수는 하위 호환성을 위해 유지하되 DB 우선