DOCS/journey/ideas/250905_APScheduler_PostgreSQL_alarm_system.md
Claude-51124 22557e7132 docs: 오래된 트러블슈팅 아카이브 및 구조 정리
- 7-8월 초기 구축 문서 12개를 _archive/troubleshooting/2025_07-08_initial_setup/로 이동
- book/300_architecture/390_human_in_the_loop_intent_learning.md를 journey/research/intent_classification/로 이동 (개발 여정 문서)
- 빈 폴더 제거 (journey/assets/*)
2025-11-17 14:06:05 +09:00

7.1 KiB

APScheduler + PostgreSQL 조합으로 알림 시스템 구현

작성일: 2025-09-05

작성자: admin

상태: 💡 아이디어

목적: rb8001이 사용자 요청 기반 알림/스케줄 관리


1. 개요

사용자가 "내일 9시에 알려줘", "회의 30분 전 알림" 같은 요청을 하면, rb8001이 정확한 시간에 실행할 수 있는 시스템 구현.

핵심 개념

  • 크론잡 한계: 최소 1분 단위, 일회성 작업 비효율적
  • APScheduler: Python 기반 작업 스케줄러, 초 단위 정확도
  • PostgreSQL JobStore: 스케줄 영속성 보장, 서버 재시작 시에도 유지

2. 기본 구조

2.1 초기 설정

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

# PostgreSQL을 JobStore로 사용
jobstores = {
    'default': SQLAlchemyJobStore(url='postgresql://robeings:pass@localhost/main_db')
}

# 스케줄러 생성
scheduler = AsyncIOScheduler(
    jobstores=jobstores,
    timezone='Asia/Seoul'  # KST 설정
)

2.2 자동 생성 테이블

-- APScheduler가 자동으로 생성하는 테이블
CREATE TABLE apscheduler_jobs (
    id VARCHAR(191) PRIMARY KEY,
    next_run_time FLOAT,
    job_state BYTEA  -- pickle 형태로 저장된 job 정보
);

3. 주요 기능 구현

3.1 일회성 알림

async def handle_alarm_request(message: str, user_id: str):
    """사용자: '내일 9시에 알려줘'"""
    
    # 자연어 시간 파싱
    run_time = parse_time(message)  # → datetime(2025, 9, 6, 9, 0, 0)
    
    # 스케줄 등록
    scheduler.add_job(
        func=send_slack_message,
        trigger='date',  # 일회성 트리거
        run_date=run_time,
        args=[user_id, "요청하신 알림입니다"],
        id=f"alarm_{user_id}_{timestamp}",
        replace_existing=True
    )
    
    return f"네, {run_time.strftime('%m월 %d일 %H시 %M분')}에 알려드릴게요"

3.2 반복 알림

# 매일 오전 9시 브리핑
scheduler.add_job(
    func=daily_briefing,
    trigger='cron',
    hour=9,
    minute=0,
    args=[user_id],
    id=f"daily_briefing_{user_id}"
)

# 30분마다 상태 체크
scheduler.add_job(
    func=check_status,
    trigger='interval',
    minutes=30,
    args=[user_id]
)

3.3 상대 시간 알림

async def set_reminder(hours_later: int, user_id: str, message: str):
    """사용자: '3시간 후 다시 알려줘'"""
    
    run_time = datetime.now() + timedelta(hours=hours_later)
    
    scheduler.add_job(
        func=send_reminder,
        trigger='date',
        run_date=run_time,
        kwargs={'user_id': user_id, 'message': message}
    )

4. rb8001 통합 방안

4.1 의도 분석 확장

# rb8001/app/intent_analyzer.py
def analyze_schedule_intent(message: str):
    patterns = {
        r"(\d+)시간 후": "hours_later",
        r"내일 (\d+)시": "tomorrow_at",
        r"(\d+)분 전": "minutes_before",
        r"매일|매주|매월": "recurring"
    }
    # 패턴 매칭으로 스케줄 타입 결정

4.2 Slack 명령어

@app.message(re.compile(r"알려줘|알림|리마인더"))
async def handle_reminder(message, say):
    # 메시지 파싱
    schedule_type, run_time = parse_schedule(message['text'])
    
    # APScheduler 등록
    job = scheduler.add_job(...)
    
    # 사용자 응답
    await say(f"✅ 알림이 설정되었습니다: {job.id}")

5. 알림 관리 기능

5.1 알림 조회

def get_user_alarms(user_id: str):
    """내 알림 목록 보기"""
    jobs = scheduler.get_jobs()
    user_jobs = [job for job in jobs if user_id in job.id]
    
    return [{
        'id': job.id,
        'next_run': job.next_run_time,
        'name': job.name
    } for job in user_jobs]

5.2 알림 취소

def cancel_alarm(job_id: str):
    """알림 취소"""
    try:
        scheduler.remove_job(job_id)
        return "알림이 취소되었습니다"
    except JobLookupError:
        return "해당 알림을 찾을 수 없습니다"

5.3 알림 수정

def modify_alarm(job_id: str, new_time: datetime):
    """알림 시간 변경"""
    scheduler.reschedule_job(
        job_id,
        trigger='date',
        run_date=new_time
    )

6. 트리거 타입별 사용 예시

트리거 사용 사례 예시
date 일회성 알림 "내일 3시 회의 알려줘"
interval 정기 체크 "30분마다 서버 상태 확인"
cron 정기 작업 "매주 월요일 9시 주간 리포트"

7. 구현 시 고려사항

7.1 장점

  • 초 단위 정확도
  • 서버 재시작 시 자동 복구
  • 다양한 스케줄 패턴 지원
  • PostgreSQL 통합으로 별도 인프라 불필요

7.2 주의점

  • ⚠️ job_state가 pickle이므로 함수 시그니처 변경 시 주의
  • ⚠️ 대량 작업 시 메모리 사용량 모니터링 필요
  • ⚠️ timezone 설정 필수 (KST)
  • ⚠️ 실패한 작업 재시도 로직 구현 필요

7.3 제한사항

  • 분산 환경에서는 별도 고려 필요
  • 매우 정밀한 시간(밀리초)은 지원 안됨

8. 대안 기술 스택

기술 장점 단점
Celery + Redis 분산 처리, 확장성 복잡한 설정
AWS EventBridge 관리형 서비스 비용, 외부 의존성
Kubernetes CronJob 컨테이너 네이티브 k8s 필요
systemd timer OS 레벨 통합 프로그래밍 어려움

9. 구현 우선순위

  1. Phase 1: 일회성 알림 (date trigger)

    • "N시간 후 알려줘"
    • "내일 오전 9시 알림"
  2. Phase 2: 반복 알림 (cron trigger)

    • "매일 아침 브리핑"
    • "매주 월요일 리포트"
  3. Phase 3: 고급 기능

    • 알림 수정/취소
    • 알림 목록 조회
    • 실패 재시도

10. 예상 사용 시나리오

사용자: "내일 오후 2시 클라이언트 미팅 있다고 30분 전에 알려줘"
rb8001: "네, 내일 오후 1시 30분에 미팅 알림 드릴게요 📅"

사용자: "매주 금요일마다 주간 보고서 작성하라고 알려줘"
rb8001: "매주 금요일 알림을 설정했습니다 📝"

사용자: "내 알림 목록 보여줘"
rb8001: 
  • 내일 13:30 - 클라이언트 미팅 알림
  • 매주 금요일 09:00 - 주간 보고서 작성
  • 매일 09:00 - 일일 브리핑

사용자: "미팅 알림 취소해줘"
rb8001: "미팅 알림이 취소되었습니다"

11. 참고 자료


작성자 메모: 현재 rb8001은 Gateway 크론에 의존하고 있으나, APScheduler 도입 시 더 유연한 스케줄링이 가능할 것으로 예상. 특히 사용자 개별 요청 기반 알림은 크론으로는 구현이 어려워 이 방식이 적합함.