Add APScheduler + PostgreSQL alarm system idea document
- 사용자 요청 기반 알림 시스템 설계 - 일회성/반복/상대시간 알림 구현 방법 - rb8001 통합 방안 및 구현 우선순위 - 크론잡 대비 장점과 주의사항 정리
This commit is contained in:
parent
c34dc177fd
commit
d424110726
271
ideas/250905_APScheduler_PostgreSQL_alarm_system.md
Normal file
271
ideas/250905_APScheduler_PostgreSQL_alarm_system.md
Normal file
@ -0,0 +1,271 @@
|
||||
# APScheduler + PostgreSQL 조합으로 알림 시스템 구현
|
||||
|
||||
## 작성일: 2025-09-05
|
||||
## 작성자: admin
|
||||
## 상태: 💡 아이디어
|
||||
## 목적: rb8001이 사용자 요청 기반 알림/스케줄 관리
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
사용자가 "내일 9시에 알려줘", "회의 30분 전 알림" 같은 요청을 하면, rb8001이 정확한 시간에 실행할 수 있는 시스템 구현.
|
||||
|
||||
### 핵심 개념
|
||||
- **크론잡 한계**: 최소 1분 단위, 일회성 작업 비효율적
|
||||
- **APScheduler**: Python 기반 작업 스케줄러, 초 단위 정확도
|
||||
- **PostgreSQL JobStore**: 스케줄 영속성 보장, 서버 재시작 시에도 유지
|
||||
|
||||
---
|
||||
|
||||
## 2. 기본 구조
|
||||
|
||||
### 2.1 초기 설정
|
||||
```python
|
||||
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 자동 생성 테이블
|
||||
```sql
|
||||
-- APScheduler가 자동으로 생성하는 테이블
|
||||
CREATE TABLE apscheduler_jobs (
|
||||
id VARCHAR(191) PRIMARY KEY,
|
||||
next_run_time FLOAT,
|
||||
job_state BYTEA -- pickle 형태로 저장된 job 정보
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 주요 기능 구현
|
||||
|
||||
### 3.1 일회성 알림
|
||||
```python
|
||||
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 반복 알림
|
||||
```python
|
||||
# 매일 오전 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 상대 시간 알림
|
||||
```python
|
||||
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 의도 분석 확장
|
||||
```python
|
||||
# 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 명령어
|
||||
```python
|
||||
@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 알림 조회
|
||||
```python
|
||||
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 알림 취소
|
||||
```python
|
||||
def cancel_alarm(job_id: str):
|
||||
"""알림 취소"""
|
||||
try:
|
||||
scheduler.remove_job(job_id)
|
||||
return "알림이 취소되었습니다"
|
||||
except JobLookupError:
|
||||
return "해당 알림을 찾을 수 없습니다"
|
||||
```
|
||||
|
||||
### 5.3 알림 수정
|
||||
```python
|
||||
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. 참고 자료
|
||||
|
||||
- [APScheduler 공식 문서](https://apscheduler.readthedocs.io/)
|
||||
- [PostgreSQL JobStore 설정](https://apscheduler.readthedocs.io/en/stable/modules/jobstores/sqlalchemy.html)
|
||||
- 기존 크론 시스템: `/home/admin/DOCS/troubleshooting/250824_rb8001_daily_summary_cron_failure.md`
|
||||
|
||||
---
|
||||
|
||||
**작성자 메모**: 현재 rb8001은 Gateway 크론에 의존하고 있으나, APScheduler 도입 시 더 유연한 스케줄링이 가능할 것으로 예상. 특히 사용자 개별 요청 기반 알림은 크론으로는 구현이 어려워 이 방식이 적합함.
|
||||
Loading…
x
Reference in New Issue
Block a user