ADR-0015: asynq vs Temporal Split¶
Umbra 는 두 개의 비동기 처리 엔진을 사용한다: 짧은 비동기 작업은 asynq, 장기 실행 워크플로우는 Temporal. 둘의 역할 경계를 명확히 분리한다.
Status¶
Accepted
- Decided at — 2026-04-13
- Decided by — Pablo
Context¶
Umbra 는 다양한 비동기 작업이 존재한다.
- 짧은 작업 — 결제 cron 트리거, Toss API 호출, 알림 발송, Live Sync 배치, 빌링키 발급 후속 처리
- 장기 워크플로우 — Restore 복구, AntiNuke 감지 및 대응
처음에는 "하나의 도구로 통일" 유혹이 있다. asynq 만으로 workflow 까지 구현하거나, Temporal 로 cron 까지 실행하는 방식.
현실 관찰:
- asynq 로 장기 워크플로우 구현 → 자체 상태 관리, retry, signal 을 전부 수동. Temporal 재구현.
- Temporal 로 모든 비동기 처리 → Temporal 워크플로우 규칙(결정론, Activity 분리) 부담이 결제 cron 같은 단순 작업에 오버킬
즉 도구의 강점이 작업 특성에 따라 다르다.
Decision¶
두 도구를 역할에 따라 분리 하여 사용한다.
asynq 의 책임¶
- 결제 cron (매 정각 결제 대상 조회 및 큐잉)
- 결제 재시도 (24h/48h/72h delayed task)
- Live Sync 배치 처리
- 알림 발송 (Discord DM, 채널 알림)
- Toss 웹훅 후속 처리
- Snapshot 자동 생성 스케줄 (일별/주별)
- Outbox 이벤트 archive cron
Temporal 의 책임¶
- Restore workflow (복구 실행)
- AntiNuke workflow (이상 감지 및 대응)
구분 기준¶
작업을 Temporal vs asynq 로 분류하는 결정 트리:
- 실행 시간이 5분 이상 예상되는가? → Temporal
- 프로세스 장애 시 중간에서 재개 가 필요한가? → Temporal
- 여러 단계가 signal 로 조정 되어야 하는가? → Temporal
- 위 모두 아니라면 → asynq
선택 근거:
- 도구별 강점 활용 — asynq 는 단순 큐잉에 최적, Temporal 은 복잡 워크플로우에 최적
- 학습 부담 최소화 — 단순 작업까지 Temporal 을 쓰면 워크플로우 결정론 규칙 부담이 커짐
- 인프라 재활용 — asynq 는 Redis(이미 사용 중) 백엔드, Temporal 은 Postgres(이미 사용 중) persistence
Consequences¶
Positive¶
- 각 도구의 강점을 활용해 작업별 최적 경험
- 간단한 작업(결제 cron)은 간단한 코드로 구현
- 복잡한 워크플로우(Restore)는 Temporal 의 복원력 활용
- 개발자가 작업 복잡도에 맞는 도구를 선택 가능
Negative¶
- 두 도구 학습 필요
- 운영 대시보드 분리 (asynq web UI + Temporal Web UI)
- "언제 무엇을 써야 하지" 판단 필요 (구분 기준으로 완화)
- 같은 worker 프로세스 안에서 두 종류 워커가 공존 → 그레이스풀 셧다운 순서 주의
Neutral¶
- Outbox poller 는 둘 다 아닌 별도 고루틴 (ADR-0016)
- 둘 다 Worker 프로세스(
apps/worker/) 안에서 실행
Workload assignment rationale¶
| Workload | Tool | Why |
|---|---|---|
| 정기 결제 cron | asynq | 단순 스케줄 + 짧은 실행 |
| 결제 실패 재시도 | asynq | delayed task 로 24h 뒤 실행 |
| Live Sync 배치 | asynq | 주기적 배치, 짧은 실행 |
| Discord DM 발송 | asynq | 단일 API 호출 |
| Toss 웹훅 후속 처리 | asynq | 웹훅 응답 후 비동기 처리 |
| Snapshot 자동 생성 | asynq | cron 스케줄 |
| Outbox archive | asynq | nightly cron |
| Restore workflow | Temporal | 장기 실행, 다단계, 재시작 |
| AntiNuke workflow | Temporal | 장기 실행, signal 기반 |
Alternatives considered¶
Alternative 1: asynq 만 (Temporal 제외)¶
Pros
- 인프라 단순 (Temporal 서버 없음)
- 학습 부담 ↓
Cons
- Restore 같은 장기 워크플로우의 장애 복원을 수동 구현
- 결과적으로 Temporal 의 부분 재구현
- 복구는 유료 기능 핵심이라 신뢰성 결정적
Why rejected — Recovery 의 신뢰성 요구가 Temporal 도입 비용을 정당화.
Alternative 2: Temporal 만 (asynq 제외)¶
Pros
- 도구 통일
Cons
- 결제 cron 같은 단순 작업에 결정론 규칙 부담
- 수십 줄 간단한 작업이 Activity 분리로 복잡도 ↑
- Temporal 인프라를 모든 작업의 경로에 배치 → 단일 장애 지점 확장
Why rejected — 짧은 작업에 Temporal 은 과함. asynq 의 경량 실행이 더 적합.
Alternative 3: River (PostgreSQL 기반 큐)¶
Pros
- PostgreSQL 만으로 큐 가능 (Redis 제거 가능)
- 트랜잭션 일관성 우수
Cons
- Redis 가 이미 세션/idempotency 용도로 필수 → 제거 불가
- asynq 대비 커뮤니티 덜 성숙
- 추가 이점 없이 도구 교체 비용
Why rejected — Redis 는 다른 용도로 유지되므로 asynq 의 Redis 백엔드가 자연스러움. River 전환 시 별 이점 없음.
Alternative 4: Cloud Tasks / Pub/Sub¶
Pros
- 관리형
Cons
- AWS/GCP 종속
- Fly.io 와의 통합 복잡
- 지연 작업(delayed task) 지원 약함
Why rejected — 현재 인프라 선택과 맞지 않음.
Compliance¶
- Restore, AntiNuke 외 작업은 Temporal 로 만들지 않음 (코드 리뷰에서 확인)
- asynq task 는 짧은 실행(수 초~수 분) 기준 유지, 초과 시 Temporal 이전 검토
- Worker 프로세스의 그레이스풀 셧다운은 asynq → Temporal → Outbox poller 순 중단
- 작업 추가 시 구분 기준(결정 트리)을 팀 리뷰에서 적용
Revisit triggers¶
- asynq task 가 장기 실행으로 늘어나 Temporal 적합성이 커지면 이전 검토
- Temporal 만 사용해도 되는 규모(팀/복잡도)면 도구 통일 재평가
- River 가 의미 있게 성숙하여 Redis 이점을 압도하면 asynq → River 검토