콘텐츠로 이동

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 의 temporal schema 공유
  • Workerapps/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 학습에 심각한 어려움을 겪으면 대안 검토

References

  • ADR-0015 — asynq 와 Temporal 역할 분담
  • ADR-0017 — Worker 프로세스에 Temporal worker 포함
  • ADR-0024 — Restore 동작 방식
  • ADR-0026 — AntiNuke MVP 포함 결정