ADR-0014: Recovery via Temporal Workflow¶
Umbra 의 Recovery 도메인 워크플로우(Restore, AntiNuke)는 Temporal 워크플로우 엔진 위에서 실행한다. 장기 실행, 장애 복원, 시그널 기반 조정이 필요한 영역에 국한하여 사용한다.
Status¶
Accepted
- Decided at — 2026-04-13
- Decided by — Pablo
Context¶
Umbra 의 Restore 작업은 다음 특성을 가진다.
- 수십 초에서 수 분이 걸리는 장기 실행 작업
- 수백~수천 개의 Discord API 호출 (역할 생성, 채널 복구, 권한 오버라이드 적용)
- Discord API rate limit 에 걸리면 자동 백오프
- 중간에 워커 프로세스가 죽어도 이어서 진행되어야 함 (결제가 유료 기능)
- 여러 단계(역할 → 채널 → 권한 → 알림)가 순차적이지만 각 단계 내부는 병렬
- 진행 상태를 사용자에게 실시간 조회 가능해야 함
- AntiNuke 가 복구 중간에 signal 을 보내 추가 스냅샷 생성 가능
AntiNuke 는 다음 특성을 가진다.
- Discord 이벤트를 관찰하며 임계값 기반 패턴 감지
- 패턴 감지 시 즉시 스냅샷 + 알림 + (선택적) 자동 대응
- 수 분~수십 분에 걸친 상태 집계 (rolling window)
- Recovery workflow 와 signal 로 조정
이런 요구는 일반 asynq 작업으로 처리하기에 부적합하다. Retry, graceful restart, long-running state 관리를 수동 구현하면 복잡도가 폭증한다.
Decision¶
Recovery 도메인의 Restore 와 AntiNuke 는 Temporal 워크플로우로 실행 한다.
- Temporal Server — MVP 는 self-hosted on Fly.io nrt. Persistence 는 Neon Postgres 의
temporalschema 공유 - Worker —
apps/worker/프로세스 안에서 Temporal worker 고루틴으로 실행 - Workflow 범위 — Restore, AntiNuke 만. 다른 모든 비동기 작업은 asynq (ADR-0015)
- Activity — Discord API 호출, DB 저장 등 비결정적 작업은 모두 Activity 로 격리
선택 근거:
- 워크플로우 장애 복원 내장 — 워커 프로세스가 죽어도 Temporal Server 가 상태를 보존하고 다른 워커가 이어서 진행
- Rate limit 대응 — Activity retry policy 로 Discord API 429 응답 자동 백오프
- Signal 패턴 — 외부 이벤트(AntiNuke 트리거, 사용자 cancel)를 진행 중 워크플로우에 전달 가능
- 장기 상태 추적 — 워크플로우 실행 중 상태를 Temporal Web UI 와 API 로 조회
- 결정론적 보장 — 워크플로우 함수가 순수하게 작성되므로 재실행 시 같은 경로로 진행
Consequences¶
Positive¶
- 복구 중 프로세스 장애가 발생해도 완주 보장 (결제 유료 기능의 핵심)
- Discord rate limit 을 구조적으로 처리
- AntiNuke 와 Restore 간 조정이 signal 로 깔끔
- Temporal Web UI 로 진행 상황 디버깅 용이
- "얼마나 진행됐는지" 사용자에게 실시간 제공 가능
Negative¶
- Temporal 인프라 운영 부담 추가 (self-hosted)
- 학습 곡선 (워크플로우 결정론 규칙, Activity vs Workflow 분리)
- 워크플로우 코드가 일반 Go 코드와 제약이 다름 (시간/랜덤/IO 금지)
- 모든 비동기 작업을 Temporal 로 몰면 overkill — asynq 병행 필수
Neutral¶
- Temporal 은 Recovery 에만 사용 → 다른 도메인은 영향 없음
- Temporal Cloud 전환은 운영 부담이 커지면 재평가 (한국 리전 미제공 이슈 존재)
Workflow design¶
Restore workflow¶
RestoreWorkflow(snapshot_id, options):
1. ValidateSnapshot activity
2. (optional) CreatePreRestoreSnapshot activity
3. SignalLiveSyncPause activity → Live Sync 일시 중지
4. Phase: Roles → CreateOrUpdateRoles activities (병렬)
5. Phase: Channels → CreateOrUpdateChannels activities (병렬)
6. Phase: PermissionOverrides → ApplyOverrides activities
7. Phase: GuildSettings → UpdateGuildSettings activity
8. SignalLiveSyncResume activity
9. PublishRestoreCompleted event
각 단계는 독립 Activity 로 retry policy 를 가진다. 실패 시 Temporal 이 자동 재시도하며, 최대 재시도 초과 시 워크플로우가 실패 상태로 마감되고 사용자에게 알림.
AntiNuke workflow¶
AntiNukeWorkflow(guild_id):
loop:
await event signals
aggregate into rolling window
if threshold_exceeded:
TriggerSnapshot activity
TriggerNotification activity
if plan == Enterprise:
RevokePerpetratorRoles activity
SignalRestoreWorkflow (if active) → 추가 조치 전달
check for shutdown signal
AntiNuke 는 길드별로 장기 실행되며, Discord Gateway 에서 수신한 이벤트가 signal 로 전달된다.
Determinism rules¶
Temporal workflow 함수는 순수해야 한다.
- 직접 IO 금지 → Activity 로 격리
time.Now()금지 →workflow.Now(ctx)사용rand금지 →workflow.SideEffect사용- 외부 함수 호출은 Activity 만 허용
이 규칙이 깨지면 워크플로우 재실행 시 이전과 다른 경로로 진행되어 상태 손상.
Alternatives considered¶
Alternative 1: asynq 로 구현 (이벤트 큐 + 자체 상태)¶
Pros
- 인프라 단순 (Temporal 서버 불필요)
- asynq 이미 사용 중
Cons
- 워커 장애 복원을 수동 구현 (checkpoint, resume)
- Rate limit 백오프 수동 구현
- 단계별 signal 조정이 복잡
- 결과적으로 Temporal 의 구현을 수동으로 재현
Why rejected — 복구는 결제 유료 기능의 핵심. "실패해도 완주" 보장을 수동 구현하는 비용이 Temporal 학습 비용을 초과.
Alternative 2: AWS Step Functions¶
Pros
- 관리형 워크플로우 엔진
Cons
- AWS 종속 (Fly.io 사용 중)
- Go SDK 는 있지만 Temporal 대비 DX 열세
- 서울 리전 latency 부담
Why rejected — 인프라 선택과 맞지 않음.
Alternative 3: 자체 워크플로우 엔진¶
Pros
- 완전 제어
Cons
- 장기 실행 상태 관리, retry, signal 을 전부 자체 구현
- 초기 투자 수 주
- MVP 범위 초과
Why rejected — Temporal 이 이미 이 문제를 해결한 검증된 도구.
Alternative 4: Cadence (Temporal 전신)¶
Pros
- 오픈소스, 유사 구조
Cons
- 커뮤니티가 Temporal 로 이동
- 최신 기능 업데이트 느림
Why rejected — Temporal 이 우위.
Compliance¶
engine/recovery/subdomain/restore/와.../antinuke/만 Temporal 의존- Workflow 함수는 결정론 규칙을 엄격히 따름 (코드 리뷰에서 확인)
- Activity 는 idempotent 하게 작성 (Temporal 재시도 대비)
- Temporal namespace 는 환경별 분리 (
umbra-dev,umbra-prod)
Revisit triggers¶
- Temporal self-hosted 운영 부담이 커지면 Temporal Cloud 또는 단순화 검토
- Temporal 에 심각한 버그/보안 이슈 발생 시 평가
- 팀이 Temporal 학습에 심각한 어려움을 겪으면 대안 검토